array('line' => 0, 'count' => 0), 'new' => array('line' => 0, 'count' => 0)); /** * The parsed diff output * @var array */ private $diff = array(); // ################################################################### /** * Constructor * * @param string Raw unified diff output */ public function __construct($raw) { $this->raw = $raw; } // ################################################################### /** * This function initiates the parsing process and returns the output * in hunks, line-by-line * * @return array Array of outputted hunked information */ public function fetch() { $this->diff = array(); for ($i = 0; $i < sizeof($this->raw); $i++) { $line = $this->raw[$i]; if (preg_match('/^Index: (.*)/', $line, $index)) { $this->_processFile(array_slice($this->raw, $this->captureStart, $i - $this->captureStart)); $this->captureStart = $i; $this->index = $index[1]; } else if (preg_match('/Property changes on: (.*)/', $line, $index)) { $this->index = $index[1]; $this->_processProperties(array_slice($this->raw, 2)); } } $this->_processFile(array_slice($this->raw, $this->captureStart)); return $this->diff; } // ################################################################### /** * Processes a file part of a diff * * @param array An array of lines that make up a file chunk */ private function _processFile($lines) { preg_match_all('/[\-\+]{3}\s+(.*)\s+\(revision ([0-9]*)\)/', $lines[2] . "\n" . $lines[3], $revs); $this->diff[$this->index]['revision']['low'] = $revs[2][0]; $this->diff[$this->index]['revision']['high'] = $revs[2][1]; $this->hunkId = 0; $captureStart = 4; for ($i = 4; $i < sizeof($lines); $i++) { $line = $lines[$i]; if (preg_match('/^@@ \-([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@/', $line, $hunkHead)) { $this->hunkLines = array('old' => array('line' => $hunkHead[1], 'count' => $hunkHead[2]), 'new' => array('line' => $hunkHead[3], 'count' => $hunkHead[4])); $this->_processHunk(array_slice($lines, $captureStart, $i - $captureStart)); $captureStart = $i; $this->hunkId++; } else if (preg_match('#^__*_$#', $line)) { $this->_processHunk(array_slice($lines, $captureStart, $i - $captureStart)); $this->_processProperties(array_slice($lines, $i)); return; } } $this->_processHunk(array_slice($lines, $captureStart)); } // ################################################################### /** * This processes an individual hunk of a file * * @param array Array of lines */ private function _processHunk($lines) { $delstack = array(); for ($i = 1; $i < sizeof($lines); $i++) { $line = $lines[$i]; if (preg_match('/^([\+\- ])(.*)/', $line, $matches)) { $action = $matches[1]; $content = $matches[2]; // leader line if ($action == ' ') { $this->diff[$this->index]['lines'][$this->hunkId][] = array( 'line' => $content, 'act' => '', 'oldlineno' => ++$this->hunkLines['old']['line'], 'newlineno' => ++$this->hunkLines['new']['line'] ); $delstack = array(); } // addition else if ($action == '+') { // potential line delta if (sizeof($delstack) > 0) { $lastline = array_shift($delstack); if ($delta = @$this->_fetchDiffExtent($lastline['line'], $content)) { if (strlen($lastline['line']) > ($delta['start'] - $delta['end'])) { $end = strlen($lastline['line']) + $delta['end']; $change = '{@-' . '-}' . substr($lastline['line'], $delta['start'], $end - $delta['start']) . '{/@-' . '-}'; $this->diff[$this->index]['lines'][$this->hunkId][$lastline['index']]['line'] = substr($lastline['line'], 0, $delta['start']) . $change . substr($lastline['line'], $end); } if (strlen($content) > $delta['start'] - $delta['end']) { $end = strlen($content) + $delta['end']; $change = '{@+' . '+}' . substr($content, $delta['start'], $end - $delta['start']) . '{/@+' . '+}'; $content = substr($content, 0, $delta['start']) . $change . substr($content, $end); } } } $this->diff[$this->index]['lines'][$this->hunkId][] = array( 'line' => $content, 'act' => '+', 'oldlineno' => 0, 'newlineno' => ++$this->hunkLines['new']['line'] ); } // deletion else if ($action == '-') { $this->diff[$this->index]['lines'][$this->hunkId][] = $temp = array( 'line' => $content, 'act' => '-', 'oldlineno' => ++$this->hunkLines['old']['line'], 'newlineno' => 0 ); $temp['index'] = sizeof($this->diff[$this->index]['lines'][$this->hunkId]) - 1; array_push($delstack, $temp); } } } } // ################################################################### /** * Processes properties * * @param array Lines of properties */ private function _processProperties($lines) { $curprop = ''; $mode = ''; $captureStart = 1; for ($i = 1; $i < sizeof($lines); $i++) { $line = $lines[$i]; if (preg_match('#Name: (.*?)$#', $line, $matches)) { $curprop = $matches[1]; BSRegister::Debug("prop: $curprop"); } else { if (preg_match('#^\s+?\+(.*)#', $line, $matches)) { $mode = 'add'; $this->diff[$this->index]['props'][$curprop]['add'] .= $matches[1]; } else if (preg_match('#^\s+?\-(.*)#', $line, $matches)) { $mode = 'del'; $this->diff[$this->index]['props'][$curprop]['del'] .= $matches[1]; } else if (!preg_match('#^\s+[\+\- ](.*)#', $line) AND trim($line) != '') { $this->diff[$this->index]['props'][$curprop][$mode] .= "\n" . $line; } } } } // ################################################################### /** * Returns the amount of change that occured between two lines * * @param string Old line * @param string New line * * @return array Difference of positions */ private function _fetchDiffExtent($old, $new) { $start = 0; $min = min(strlen($old), strlen($new)); while ($start < $min AND $old[$start] == $new[$start]) { $start++; } $end = -1; $min = $min - $start; while (-$end <= $min AND $old[strlen($old) + $end] == $new[strlen($new) + $end]) { $end--; } return array('start' => $start, 'end' => $end + 1); } } /*=====================================================================*\ || ################################################################### || # $HeadURL$ || # $Id$ || ################################################################### \*=====================================================================*/ ?>