Alexander Makarov
11 years ago
22 changed files with 8 additions and 2182 deletions
@ -1,233 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* General API for generating and formatting diffs - the differences between |
|
||||||
* two sequences of strings. |
|
||||||
* |
|
||||||
* The original PHP version of this code was written by Geoffrey T. Dairiki |
|
||||||
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
|
||||||
* |
|
||||||
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Array of changes. |
|
||||||
* |
|
||||||
* @var array |
|
||||||
*/ |
|
||||||
protected $_edits; |
|
||||||
|
|
||||||
/** |
|
||||||
* Computes diffs between sequences of strings. |
|
||||||
* |
|
||||||
* @param string $engine Name of the diffing engine to use. 'auto' |
|
||||||
* will automatically select the best. |
|
||||||
* @param array $params Parameters to pass to the diffing engine. |
|
||||||
* Normally an array of two arrays, each |
|
||||||
* containing the lines from a file. |
|
||||||
*/ |
|
||||||
public function __construct($engine, $params) |
|
||||||
{ |
|
||||||
if ($engine == 'auto') { |
|
||||||
$engine = extension_loaded('xdiff') ? 'Xdiff' : 'Native'; |
|
||||||
} else { |
|
||||||
$engine = ucfirst(basename($engine)); |
|
||||||
} |
|
||||||
|
|
||||||
$class = 'Horde_Text_Diff_Engine_' . $engine; |
|
||||||
$diff_engine = new $class(); |
|
||||||
|
|
||||||
$this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the array of differences. |
|
||||||
*/ |
|
||||||
public function getDiff() |
|
||||||
{ |
|
||||||
return $this->_edits; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* returns the number of new (added) lines in a given diff. |
|
||||||
* |
|
||||||
* @return integer The number of new lines |
|
||||||
*/ |
|
||||||
public function countAddedLines() |
|
||||||
{ |
|
||||||
$count = 0; |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if ($edit instanceof Horde_Text_Diff_Op_Add || |
|
||||||
$edit instanceof Horde_Text_Diff_Op_Change) { |
|
||||||
$count += $edit->nfinal(); |
|
||||||
} |
|
||||||
} |
|
||||||
return $count; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the number of deleted (removed) lines in a given diff. |
|
||||||
* |
|
||||||
* @return integer The number of deleted lines |
|
||||||
*/ |
|
||||||
public function countDeletedLines() |
|
||||||
{ |
|
||||||
$count = 0; |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if ($edit instanceof Horde_Text_Diff_Op_Delete || |
|
||||||
$edit instanceof Horde_Text_Diff_Op_Change) { |
|
||||||
$count += $edit->norig(); |
|
||||||
} |
|
||||||
} |
|
||||||
return $count; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Computes a reversed diff. |
|
||||||
* |
|
||||||
* Example: |
|
||||||
* <code> |
|
||||||
* $diff = new Horde_Text_Diff($lines1, $lines2); |
|
||||||
* $rev = $diff->reverse(); |
|
||||||
* </code> |
|
||||||
* |
|
||||||
* @return Horde_Text_Diff A Diff object representing the inverse of the |
|
||||||
* original diff. Note that we purposely don't return a |
|
||||||
* reference here, since this essentially is a clone() |
|
||||||
* method. |
|
||||||
*/ |
|
||||||
public function reverse() |
|
||||||
{ |
|
||||||
if (version_compare(zend_version(), '2', '>')) { |
|
||||||
$rev = clone($this); |
|
||||||
} else { |
|
||||||
$rev = $this; |
|
||||||
} |
|
||||||
$rev->_edits = array(); |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
$rev->_edits[] = $edit->reverse(); |
|
||||||
} |
|
||||||
return $rev; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Checks for an empty diff. |
|
||||||
* |
|
||||||
* @return boolean True if two sequences were identical. |
|
||||||
*/ |
|
||||||
public function isEmpty() |
|
||||||
{ |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if (!($edit instanceof Horde_Text_Diff_Op_Copy)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Computes the length of the Longest Common Subsequence (LCS). |
|
||||||
* |
|
||||||
* This is mostly for diagnostic purposes. |
|
||||||
* |
|
||||||
* @return integer The length of the LCS. |
|
||||||
*/ |
|
||||||
public function lcs() |
|
||||||
{ |
|
||||||
$lcs = 0; |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if ($edit instanceof Horde_Text_Diff_Op_Copy) { |
|
||||||
$lcs += count($edit->orig); |
|
||||||
} |
|
||||||
} |
|
||||||
return $lcs; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Gets the original set of lines. |
|
||||||
* |
|
||||||
* This reconstructs the $from_lines parameter passed to the constructor. |
|
||||||
* |
|
||||||
* @return array The original sequence of strings. |
|
||||||
*/ |
|
||||||
public function getOriginal() |
|
||||||
{ |
|
||||||
$lines = array(); |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if ($edit->orig) { |
|
||||||
array_splice($lines, count($lines), 0, $edit->orig); |
|
||||||
} |
|
||||||
} |
|
||||||
return $lines; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Gets the final set of lines. |
|
||||||
* |
|
||||||
* This reconstructs the $to_lines parameter passed to the constructor. |
|
||||||
* |
|
||||||
* @return array The sequence of strings. |
|
||||||
*/ |
|
||||||
public function getFinal() |
|
||||||
{ |
|
||||||
$lines = array(); |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if ($edit->final) { |
|
||||||
array_splice($lines, count($lines), 0, $edit->final); |
|
||||||
} |
|
||||||
} |
|
||||||
return $lines; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Removes trailing newlines from a line of text. This is meant to be used |
|
||||||
* with array_walk(). |
|
||||||
* |
|
||||||
* @param string $line The line to trim. |
|
||||||
* @param integer $key The index of the line in the array. Not used. |
|
||||||
*/ |
|
||||||
static public function trimNewlines(&$line, $key) |
|
||||||
{ |
|
||||||
$line = str_replace(array("\n", "\r"), '', $line); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Checks a diff for validity. |
|
||||||
* |
|
||||||
* This is here only for debugging purposes. |
|
||||||
*/ |
|
||||||
protected function _check($from_lines, $to_lines) |
|
||||||
{ |
|
||||||
if (serialize($from_lines) != serialize($this->getOriginal())) { |
|
||||||
trigger_error("Reconstructed original doesn't match", E_USER_ERROR); |
|
||||||
} |
|
||||||
if (serialize($to_lines) != serialize($this->getFinal())) { |
|
||||||
trigger_error("Reconstructed final doesn't match", E_USER_ERROR); |
|
||||||
} |
|
||||||
|
|
||||||
$rev = $this->reverse(); |
|
||||||
if (serialize($to_lines) != serialize($rev->getOriginal())) { |
|
||||||
trigger_error("Reversed original doesn't match", E_USER_ERROR); |
|
||||||
} |
|
||||||
if (serialize($from_lines) != serialize($rev->getFinal())) { |
|
||||||
trigger_error("Reversed final doesn't match", E_USER_ERROR); |
|
||||||
} |
|
||||||
|
|
||||||
$prevtype = null; |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if ($prevtype == get_class($edit)) { |
|
||||||
trigger_error("Edit sequence is non-optimal", E_USER_ERROR); |
|
||||||
} |
|
||||||
$prevtype = get_class($edit); |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
@ -1,435 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Class used internally by Horde_Text_Diff to actually compute the diffs. |
|
||||||
* |
|
||||||
* This class is implemented using native PHP code. |
|
||||||
* |
|
||||||
* The algorithm used here is mostly lifted from the perl module |
|
||||||
* Algorithm::Diff (version 1.06) by Ned Konz, which is available at: |
|
||||||
* http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip |
|
||||||
* |
|
||||||
* More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html |
|
||||||
* |
|
||||||
* Some ideas (and a bit of code) are taken from analyze.c, of GNU |
|
||||||
* diffutils-2.7, which can be found at: |
|
||||||
* ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz |
|
||||||
* |
|
||||||
* Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from |
|
||||||
* Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this |
|
||||||
* code was written by him, and is used/adapted with his permission. |
|
||||||
* |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Engine_Native |
|
||||||
{ |
|
||||||
public function diff($from_lines, $to_lines) |
|
||||||
{ |
|
||||||
array_walk($from_lines, array('Horde_Text_Diff', 'trimNewlines')); |
|
||||||
array_walk($to_lines, array('Horde_Text_Diff', 'trimNewlines')); |
|
||||||
|
|
||||||
$n_from = count($from_lines); |
|
||||||
$n_to = count($to_lines); |
|
||||||
|
|
||||||
$this->xchanged = $this->ychanged = array(); |
|
||||||
$this->xv = $this->yv = array(); |
|
||||||
$this->xind = $this->yind = array(); |
|
||||||
unset($this->seq); |
|
||||||
unset($this->in_seq); |
|
||||||
unset($this->lcs); |
|
||||||
|
|
||||||
// Skip leading common lines. |
|
||||||
for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { |
|
||||||
if ($from_lines[$skip] !== $to_lines[$skip]) { |
|
||||||
break; |
|
||||||
} |
|
||||||
$this->xchanged[$skip] = $this->ychanged[$skip] = false; |
|
||||||
} |
|
||||||
|
|
||||||
// Skip trailing common lines. |
|
||||||
$xi = $n_from; $yi = $n_to; |
|
||||||
for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { |
|
||||||
if ($from_lines[$xi] !== $to_lines[$yi]) { |
|
||||||
break; |
|
||||||
} |
|
||||||
$this->xchanged[$xi] = $this->ychanged[$yi] = false; |
|
||||||
} |
|
||||||
|
|
||||||
// Ignore lines which do not exist in both files. |
|
||||||
for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { |
|
||||||
$xhash[$from_lines[$xi]] = 1; |
|
||||||
} |
|
||||||
for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { |
|
||||||
$line = $to_lines[$yi]; |
|
||||||
if (($this->ychanged[$yi] = empty($xhash[$line]))) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
$yhash[$line] = 1; |
|
||||||
$this->yv[] = $line; |
|
||||||
$this->yind[] = $yi; |
|
||||||
} |
|
||||||
for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { |
|
||||||
$line = $from_lines[$xi]; |
|
||||||
if (($this->xchanged[$xi] = empty($yhash[$line]))) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
$this->xv[] = $line; |
|
||||||
$this->xind[] = $xi; |
|
||||||
} |
|
||||||
|
|
||||||
// Find the LCS. |
|
||||||
$this->_compareseq(0, count($this->xv), 0, count($this->yv)); |
|
||||||
|
|
||||||
// Merge edits when possible. |
|
||||||
$this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged); |
|
||||||
$this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged); |
|
||||||
|
|
||||||
// Compute the edit operations. |
|
||||||
$edits = array(); |
|
||||||
$xi = $yi = 0; |
|
||||||
while ($xi < $n_from || $yi < $n_to) { |
|
||||||
assert($yi < $n_to || $this->xchanged[$xi]); |
|
||||||
assert($xi < $n_from || $this->ychanged[$yi]); |
|
||||||
|
|
||||||
// Skip matching "snake". |
|
||||||
$copy = array(); |
|
||||||
while ($xi < $n_from && $yi < $n_to |
|
||||||
&& !$this->xchanged[$xi] && !$this->ychanged[$yi]) { |
|
||||||
$copy[] = $from_lines[$xi++]; |
|
||||||
++$yi; |
|
||||||
} |
|
||||||
if ($copy) { |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Copy($copy); |
|
||||||
} |
|
||||||
|
|
||||||
// Find deletes & adds. |
|
||||||
$delete = array(); |
|
||||||
while ($xi < $n_from && $this->xchanged[$xi]) { |
|
||||||
$delete[] = $from_lines[$xi++]; |
|
||||||
} |
|
||||||
|
|
||||||
$add = array(); |
|
||||||
while ($yi < $n_to && $this->ychanged[$yi]) { |
|
||||||
$add[] = $to_lines[$yi++]; |
|
||||||
} |
|
||||||
|
|
||||||
if ($delete && $add) { |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Change($delete, $add); |
|
||||||
} elseif ($delete) { |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Delete($delete); |
|
||||||
} elseif ($add) { |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Add($add); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $edits; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, |
|
||||||
* XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized |
|
||||||
* segments. |
|
||||||
* |
|
||||||
* Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of |
|
||||||
* NCHUNKS+1 (X, Y) indexes giving the diving points between sub |
|
||||||
* sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), |
|
||||||
* the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == |
|
||||||
* (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). |
|
||||||
* |
|
||||||
* This public function assumes that the first lines of the specified portions of |
|
||||||
* the two files do not match, and likewise that the last lines do not |
|
||||||
* match. The caller must trim matching lines from the beginning and end |
|
||||||
* of the portions it is going to specify. |
|
||||||
*/ |
|
||||||
protected function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) |
|
||||||
{ |
|
||||||
$flip = false; |
|
||||||
|
|
||||||
if ($xlim - $xoff > $ylim - $yoff) { |
|
||||||
/* Things seems faster (I'm not sure I understand why) when the |
|
||||||
* shortest sequence is in X. */ |
|
||||||
$flip = true; |
|
||||||
list ($xoff, $xlim, $yoff, $ylim) |
|
||||||
= array($yoff, $ylim, $xoff, $xlim); |
|
||||||
} |
|
||||||
|
|
||||||
if ($flip) { |
|
||||||
for ($i = $ylim - 1; $i >= $yoff; $i--) { |
|
||||||
$ymatches[$this->xv[$i]][] = $i; |
|
||||||
} |
|
||||||
} else { |
|
||||||
for ($i = $ylim - 1; $i >= $yoff; $i--) { |
|
||||||
$ymatches[$this->yv[$i]][] = $i; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
$this->lcs = 0; |
|
||||||
$this->seq[0]= $yoff - 1; |
|
||||||
$this->in_seq = array(); |
|
||||||
$ymids[0] = array(); |
|
||||||
|
|
||||||
$numer = $xlim - $xoff + $nchunks - 1; |
|
||||||
$x = $xoff; |
|
||||||
for ($chunk = 0; $chunk < $nchunks; $chunk++) { |
|
||||||
if ($chunk > 0) { |
|
||||||
for ($i = 0; $i <= $this->lcs; $i++) { |
|
||||||
$ymids[$i][$chunk - 1] = $this->seq[$i]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
$x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); |
|
||||||
for (; $x < $x1; $x++) { |
|
||||||
$line = $flip ? $this->yv[$x] : $this->xv[$x]; |
|
||||||
if (empty($ymatches[$line])) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
$matches = $ymatches[$line]; |
|
||||||
reset($matches); |
|
||||||
while (list(, $y) = each($matches)) { |
|
||||||
if (empty($this->in_seq[$y])) { |
|
||||||
$k = $this->_lcsPos($y); |
|
||||||
assert($k > 0); |
|
||||||
$ymids[$k] = $ymids[$k - 1]; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
while (list(, $y) = each($matches)) { |
|
||||||
if ($y > $this->seq[$k - 1]) { |
|
||||||
assert($y <= $this->seq[$k]); |
|
||||||
/* Optimization: this is a common case: next match is |
|
||||||
* just replacing previous match. */ |
|
||||||
$this->in_seq[$this->seq[$k]] = false; |
|
||||||
$this->seq[$k] = $y; |
|
||||||
$this->in_seq[$y] = 1; |
|
||||||
} elseif (empty($this->in_seq[$y])) { |
|
||||||
$k = $this->_lcsPos($y); |
|
||||||
assert($k > 0); |
|
||||||
$ymids[$k] = $ymids[$k - 1]; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
$seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); |
|
||||||
$ymid = $ymids[$this->lcs]; |
|
||||||
for ($n = 0; $n < $nchunks - 1; $n++) { |
|
||||||
$x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); |
|
||||||
$y1 = $ymid[$n] + 1; |
|
||||||
$seps[] = $flip ? array($y1, $x1) : array($x1, $y1); |
|
||||||
} |
|
||||||
$seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); |
|
||||||
|
|
||||||
return array($this->lcs, $seps); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _lcsPos($ypos) |
|
||||||
{ |
|
||||||
$end = $this->lcs; |
|
||||||
if ($end == 0 || $ypos > $this->seq[$end]) { |
|
||||||
$this->seq[++$this->lcs] = $ypos; |
|
||||||
$this->in_seq[$ypos] = 1; |
|
||||||
return $this->lcs; |
|
||||||
} |
|
||||||
|
|
||||||
$beg = 1; |
|
||||||
while ($beg < $end) { |
|
||||||
$mid = (int)(($beg + $end) / 2); |
|
||||||
if ($ypos > $this->seq[$mid]) { |
|
||||||
$beg = $mid + 1; |
|
||||||
} else { |
|
||||||
$end = $mid; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
assert($ypos != $this->seq[$end]); |
|
||||||
|
|
||||||
$this->in_seq[$this->seq[$end]] = false; |
|
||||||
$this->seq[$end] = $ypos; |
|
||||||
$this->in_seq[$ypos] = 1; |
|
||||||
return $end; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Finds LCS of two sequences. |
|
||||||
* |
|
||||||
* The results are recorded in the vectors $this->{x,y}changed[], by |
|
||||||
* storing a 1 in the element for each line that is an insertion or |
|
||||||
* deletion (ie. is not in the LCS). |
|
||||||
* |
|
||||||
* The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. |
|
||||||
* |
|
||||||
* Note that XLIM, YLIM are exclusive bounds. All line numbers are |
|
||||||
* origin-0 and discarded lines are not counted. |
|
||||||
*/ |
|
||||||
protected function _compareseq ($xoff, $xlim, $yoff, $ylim) |
|
||||||
{ |
|
||||||
/* Slide down the bottom initial diagonal. */ |
|
||||||
while ($xoff < $xlim && $yoff < $ylim |
|
||||||
&& $this->xv[$xoff] == $this->yv[$yoff]) { |
|
||||||
++$xoff; |
|
||||||
++$yoff; |
|
||||||
} |
|
||||||
|
|
||||||
/* Slide up the top initial diagonal. */ |
|
||||||
while ($xlim > $xoff && $ylim > $yoff |
|
||||||
&& $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { |
|
||||||
--$xlim; |
|
||||||
--$ylim; |
|
||||||
} |
|
||||||
|
|
||||||
if ($xoff == $xlim || $yoff == $ylim) { |
|
||||||
$lcs = 0; |
|
||||||
} else { |
|
||||||
/* This is ad hoc but seems to work well. $nchunks = |
|
||||||
* sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks = |
|
||||||
* max(2,min(8,(int)$nchunks)); */ |
|
||||||
$nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; |
|
||||||
list($lcs, $seps) |
|
||||||
= $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); |
|
||||||
} |
|
||||||
|
|
||||||
if ($lcs == 0) { |
|
||||||
/* X and Y sequences have no common subsequence: mark all |
|
||||||
* changed. */ |
|
||||||
while ($yoff < $ylim) { |
|
||||||
$this->ychanged[$this->yind[$yoff++]] = 1; |
|
||||||
} |
|
||||||
while ($xoff < $xlim) { |
|
||||||
$this->xchanged[$this->xind[$xoff++]] = 1; |
|
||||||
} |
|
||||||
} else { |
|
||||||
/* Use the partitions to split this problem into subproblems. */ |
|
||||||
reset($seps); |
|
||||||
$pt1 = $seps[0]; |
|
||||||
while ($pt2 = next($seps)) { |
|
||||||
$this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); |
|
||||||
$pt1 = $pt2; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adjusts inserts/deletes of identical lines to join changes as much as |
|
||||||
* possible. |
|
||||||
* |
|
||||||
* We do something when a run of changed lines include a line at one end |
|
||||||
* and has an excluded, identical line at the other. We are free to |
|
||||||
* choose which identical line is included. `compareseq' usually chooses |
|
||||||
* the one at the beginning, but usually it is cleaner to consider the |
|
||||||
* following identical line to be the "change". |
|
||||||
* |
|
||||||
* This is extracted verbatim from analyze.c (GNU diffutils-2.7). |
|
||||||
*/ |
|
||||||
protected function _shiftBoundaries($lines, &$changed, $other_changed) |
|
||||||
{ |
|
||||||
$i = 0; |
|
||||||
$j = 0; |
|
||||||
|
|
||||||
assert('count($lines) == count($changed)'); |
|
||||||
$len = count($lines); |
|
||||||
$other_len = count($other_changed); |
|
||||||
|
|
||||||
while (1) { |
|
||||||
/* Scan forward to find the beginning of another run of |
|
||||||
* changes. Also keep track of the corresponding point in the |
|
||||||
* other file. |
|
||||||
* |
|
||||||
* Throughout this code, $i and $j are adjusted together so that |
|
||||||
* the first $i elements of $changed and the first $j elements of |
|
||||||
* $other_changed both contain the same number of zeros (unchanged |
|
||||||
* lines). |
|
||||||
* |
|
||||||
* Furthermore, $j is always kept so that $j == $other_len or |
|
||||||
* $other_changed[$j] == false. */ |
|
||||||
while ($j < $other_len && $other_changed[$j]) { |
|
||||||
$j++; |
|
||||||
} |
|
||||||
|
|
||||||
while ($i < $len && ! $changed[$i]) { |
|
||||||
assert('$j < $other_len && ! $other_changed[$j]'); |
|
||||||
$i++; $j++; |
|
||||||
while ($j < $other_len && $other_changed[$j]) { |
|
||||||
$j++; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if ($i == $len) { |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
$start = $i; |
|
||||||
|
|
||||||
/* Find the end of this run of changes. */ |
|
||||||
while (++$i < $len && $changed[$i]) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
do { |
|
||||||
/* Record the length of this run of changes, so that we can |
|
||||||
* later determine whether the run has grown. */ |
|
||||||
$runlength = $i - $start; |
|
||||||
|
|
||||||
/* Move the changed region back, so long as the previous |
|
||||||
* unchanged line matches the last changed one. This merges |
|
||||||
* with previous changed regions. */ |
|
||||||
while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { |
|
||||||
$changed[--$start] = 1; |
|
||||||
$changed[--$i] = false; |
|
||||||
while ($start > 0 && $changed[$start - 1]) { |
|
||||||
$start--; |
|
||||||
} |
|
||||||
assert('$j > 0'); |
|
||||||
while ($other_changed[--$j]) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
assert('$j >= 0 && !$other_changed[$j]'); |
|
||||||
} |
|
||||||
|
|
||||||
/* Set CORRESPONDING to the end of the changed run, at the |
|
||||||
* last point where it corresponds to a changed run in the |
|
||||||
* other file. CORRESPONDING == LEN means no such point has |
|
||||||
* been found. */ |
|
||||||
$corresponding = $j < $other_len ? $i : $len; |
|
||||||
|
|
||||||
/* Move the changed region forward, so long as the first |
|
||||||
* changed line matches the following unchanged one. This |
|
||||||
* merges with following changed regions. Do this second, so |
|
||||||
* that if there are no merges, the changed region is moved |
|
||||||
* forward as far as possible. */ |
|
||||||
while ($i < $len && $lines[$start] == $lines[$i]) { |
|
||||||
$changed[$start++] = false; |
|
||||||
$changed[$i++] = 1; |
|
||||||
while ($i < $len && $changed[$i]) { |
|
||||||
$i++; |
|
||||||
} |
|
||||||
|
|
||||||
assert('$j < $other_len && ! $other_changed[$j]'); |
|
||||||
$j++; |
|
||||||
if ($j < $other_len && $other_changed[$j]) { |
|
||||||
$corresponding = $i; |
|
||||||
while ($j < $other_len && $other_changed[$j]) { |
|
||||||
$j++; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} while ($runlength != $i - $start); |
|
||||||
|
|
||||||
/* If possible, move the fully-merged run of changes back to a |
|
||||||
* corresponding run in the other file. */ |
|
||||||
while ($corresponding < $i) { |
|
||||||
$changed[--$start] = 1; |
|
||||||
$changed[--$i] = 0; |
|
||||||
assert('$j > 0'); |
|
||||||
while ($other_changed[--$j]) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
assert('$j >= 0 && !$other_changed[$j]'); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,158 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Class used internally by Diff to actually compute the diffs. |
|
||||||
* |
|
||||||
* This class uses the Unix `diff` program via shell_exec to compute the |
|
||||||
* differences between the two input arrays. |
|
||||||
* |
|
||||||
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @author Milian Wolff <mail@milianw.de> |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Engine_Shell |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Path to the diff executable |
|
||||||
* |
|
||||||
* @var string |
|
||||||
*/ |
|
||||||
protected $_diffCommand = 'diff'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the array of differences. |
|
||||||
* |
|
||||||
* @param array $from_lines lines of text from old file |
|
||||||
* @param array $to_lines lines of text from new file |
|
||||||
* |
|
||||||
* @return array all changes made (array with Horde_Text_Diff_Op_* objects) |
|
||||||
*/ |
|
||||||
public function diff($from_lines, $to_lines) |
|
||||||
{ |
|
||||||
array_walk($from_lines, array('Horde_Text_Diff', 'trimNewlines')); |
|
||||||
array_walk($to_lines, array('Horde_Text_Diff', 'trimNewlines')); |
|
||||||
|
|
||||||
// Execute gnu diff or similar to get a standard diff file. |
|
||||||
$from_file = Horde_Util::getTempFile('Horde_Text_Diff'); |
|
||||||
$to_file = Horde_Util::getTempFile('Horde_Text_Diff'); |
|
||||||
$fp = fopen($from_file, 'w'); |
|
||||||
fwrite($fp, implode("\n", $from_lines)); |
|
||||||
fclose($fp); |
|
||||||
$fp = fopen($to_file, 'w'); |
|
||||||
fwrite($fp, implode("\n", $to_lines)); |
|
||||||
fclose($fp); |
|
||||||
$diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); |
|
||||||
unlink($from_file); |
|
||||||
unlink($to_file); |
|
||||||
|
|
||||||
if (is_null($diff)) { |
|
||||||
// No changes were made |
|
||||||
return array(new Horde_Text_Diff_Op_Copy($from_lines)); |
|
||||||
} |
|
||||||
|
|
||||||
$from_line_no = 1; |
|
||||||
$to_line_no = 1; |
|
||||||
$edits = array(); |
|
||||||
|
|
||||||
// Get changed lines by parsing something like: |
|
||||||
// 0a1,2 |
|
||||||
// 1,2c4,6 |
|
||||||
// 1,5d6 |
|
||||||
preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, |
|
||||||
$matches, PREG_SET_ORDER); |
|
||||||
|
|
||||||
foreach ($matches as $match) { |
|
||||||
if (!isset($match[5])) { |
|
||||||
// This paren is not set every time (see regex). |
|
||||||
$match[5] = false; |
|
||||||
} |
|
||||||
|
|
||||||
if ($match[3] == 'a') { |
|
||||||
$from_line_no--; |
|
||||||
} |
|
||||||
|
|
||||||
if ($match[3] == 'd') { |
|
||||||
$to_line_no--; |
|
||||||
} |
|
||||||
|
|
||||||
if ($from_line_no < $match[1] || $to_line_no < $match[4]) { |
|
||||||
// copied lines |
|
||||||
assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); |
|
||||||
array_push($edits, |
|
||||||
new Horde_Text_Diff_Op_Copy( |
|
||||||
$this->_getLines($from_lines, $from_line_no, $match[1] - 1), |
|
||||||
$this->_getLines($to_lines, $to_line_no, $match[4] - 1))); |
|
||||||
} |
|
||||||
|
|
||||||
switch ($match[3]) { |
|
||||||
case 'd': |
|
||||||
// deleted lines |
|
||||||
array_push($edits, |
|
||||||
new Horde_Text_Diff_Op_Delete( |
|
||||||
$this->_getLines($from_lines, $from_line_no, $match[2]))); |
|
||||||
$to_line_no++; |
|
||||||
break; |
|
||||||
|
|
||||||
case 'c': |
|
||||||
// changed lines |
|
||||||
array_push($edits, |
|
||||||
new Horde_Text_Diff_Op_Change( |
|
||||||
$this->_getLines($from_lines, $from_line_no, $match[2]), |
|
||||||
$this->_getLines($to_lines, $to_line_no, $match[5]))); |
|
||||||
break; |
|
||||||
|
|
||||||
case 'a': |
|
||||||
// added lines |
|
||||||
array_push($edits, |
|
||||||
new Horde_Text_Diff_Op_Add( |
|
||||||
$this->_getLines($to_lines, $to_line_no, $match[5]))); |
|
||||||
$from_line_no++; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (!empty($from_lines)) { |
|
||||||
// Some lines might still be pending. Add them as copied |
|
||||||
array_push($edits, |
|
||||||
new Horde_Text_Diff_Op_Copy( |
|
||||||
$this->_getLines($from_lines, $from_line_no, |
|
||||||
$from_line_no + count($from_lines) - 1), |
|
||||||
$this->_getLines($to_lines, $to_line_no, |
|
||||||
$to_line_no + count($to_lines) - 1))); |
|
||||||
} |
|
||||||
|
|
||||||
return $edits; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Get lines from either the old or new text |
|
||||||
* |
|
||||||
* @access private |
|
||||||
* |
|
||||||
* @param array &$text_lines Either $from_lines or $to_lines |
|
||||||
* @param int &$line_no Current line number |
|
||||||
* @param int $end Optional end line, when we want to chop more |
|
||||||
* than one line. |
|
||||||
* |
|
||||||
* @return array The chopped lines |
|
||||||
*/ |
|
||||||
protected function _getLines(&$text_lines, &$line_no, $end = false) |
|
||||||
{ |
|
||||||
if (!empty($end)) { |
|
||||||
$lines = array(); |
|
||||||
// We can shift even more |
|
||||||
while ($line_no <= $end) { |
|
||||||
array_push($lines, array_shift($text_lines)); |
|
||||||
$line_no++; |
|
||||||
} |
|
||||||
} else { |
|
||||||
$lines = array(array_shift($text_lines)); |
|
||||||
$line_no++; |
|
||||||
} |
|
||||||
|
|
||||||
return $lines; |
|
||||||
} |
|
||||||
} |
|
@ -1,247 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Parses unified or context diffs output from eg. the diff utility. |
|
||||||
* |
|
||||||
* Example: |
|
||||||
* <code> |
|
||||||
* $patch = file_get_contents('example.patch'); |
|
||||||
* $diff = new Horde_Text_Diff('string', array($patch)); |
|
||||||
* $renderer = new Horde_Text_Diff_Renderer_inline(); |
|
||||||
* echo $renderer->render($diff); |
|
||||||
* </code> |
|
||||||
* |
|
||||||
* Copyright 2005 Örjan Persson <o@42mm.org> |
|
||||||
* Copyright 2005-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @author Örjan Persson <o@42mm.org> |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Engine_String |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Parses a unified or context diff. |
|
||||||
* |
|
||||||
* First param contains the whole diff and the second can be used to force |
|
||||||
* a specific diff type. If the second parameter is 'autodetect', the |
|
||||||
* diff will be examined to find out which type of diff this is. |
|
||||||
* |
|
||||||
* @param string $diff The diff content. |
|
||||||
* @param string $mode The diff mode of the content in $diff. One of |
|
||||||
* 'context', 'unified', or 'autodetect'. |
|
||||||
* |
|
||||||
* @return array List of all diff operations. |
|
||||||
* @throws Horde_Text_Diff_Exception |
|
||||||
*/ |
|
||||||
public function diff($diff, $mode = 'autodetect') |
|
||||||
{ |
|
||||||
// Detect line breaks. |
|
||||||
$lnbr = "\n"; |
|
||||||
if (strpos($diff, "\r\n") !== false) { |
|
||||||
$lnbr = "\r\n"; |
|
||||||
} elseif (strpos($diff, "\r") !== false) { |
|
||||||
$lnbr = "\r"; |
|
||||||
} |
|
||||||
|
|
||||||
// Make sure we have a line break at the EOF. |
|
||||||
if (substr($diff, -strlen($lnbr)) != $lnbr) { |
|
||||||
$diff .= $lnbr; |
|
||||||
} |
|
||||||
|
|
||||||
if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { |
|
||||||
throw new Horde_Text_Diff_Exception('Type of diff is unsupported'); |
|
||||||
} |
|
||||||
|
|
||||||
if ($mode == 'autodetect') { |
|
||||||
$context = strpos($diff, '***'); |
|
||||||
$unified = strpos($diff, '---'); |
|
||||||
if ($context === $unified) { |
|
||||||
throw new Horde_Text_Diff_Exception('Type of diff could not be detected'); |
|
||||||
} elseif ($context === false || $unified === false) { |
|
||||||
$mode = $context !== false ? 'context' : 'unified'; |
|
||||||
} else { |
|
||||||
$mode = $context < $unified ? 'context' : 'unified'; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Split by new line and remove the diff header, if there is one. |
|
||||||
$diff = explode($lnbr, $diff); |
|
||||||
if (($mode == 'context' && strpos($diff[0], '***') === 0) || |
|
||||||
($mode == 'unified' && strpos($diff[0], '---') === 0)) { |
|
||||||
array_shift($diff); |
|
||||||
array_shift($diff); |
|
||||||
} |
|
||||||
|
|
||||||
if ($mode == 'context') { |
|
||||||
return $this->parseContextDiff($diff); |
|
||||||
} else { |
|
||||||
return $this->parseUnifiedDiff($diff); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Parses an array containing the unified diff. |
|
||||||
* |
|
||||||
* @param array $diff Array of lines. |
|
||||||
* |
|
||||||
* @return array List of all diff operations. |
|
||||||
*/ |
|
||||||
public function parseUnifiedDiff($diff) |
|
||||||
{ |
|
||||||
$edits = array(); |
|
||||||
$end = count($diff) - 1; |
|
||||||
for ($i = 0; $i < $end;) { |
|
||||||
$diff1 = array(); |
|
||||||
switch (substr($diff[$i], 0, 1)) { |
|
||||||
case ' ': |
|
||||||
do { |
|
||||||
$diff1[] = substr($diff[$i], 1); |
|
||||||
} while (++$i < $end && substr($diff[$i], 0, 1) == ' '); |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Copy($diff1); |
|
||||||
break; |
|
||||||
|
|
||||||
case '+': |
|
||||||
// get all new lines |
|
||||||
do { |
|
||||||
$diff1[] = substr($diff[$i], 1); |
|
||||||
} while (++$i < $end && substr($diff[$i], 0, 1) == '+'); |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Add($diff1); |
|
||||||
break; |
|
||||||
|
|
||||||
case '-': |
|
||||||
// get changed or removed lines |
|
||||||
$diff2 = array(); |
|
||||||
do { |
|
||||||
$diff1[] = substr($diff[$i], 1); |
|
||||||
} while (++$i < $end && substr($diff[$i], 0, 1) == '-'); |
|
||||||
|
|
||||||
while ($i < $end && substr($diff[$i], 0, 1) == '+') { |
|
||||||
$diff2[] = substr($diff[$i++], 1); |
|
||||||
} |
|
||||||
if (count($diff2) == 0) { |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Delete($diff1); |
|
||||||
} else { |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2); |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
default: |
|
||||||
$i++; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $edits; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Parses an array containing the context diff. |
|
||||||
* |
|
||||||
* @param array $diff Array of lines. |
|
||||||
* |
|
||||||
* @return array List of all diff operations. |
|
||||||
*/ |
|
||||||
public function parseContextDiff(&$diff) |
|
||||||
{ |
|
||||||
$edits = array(); |
|
||||||
$i = $max_i = $j = $max_j = 0; |
|
||||||
$end = count($diff) - 1; |
|
||||||
while ($i < $end && $j < $end) { |
|
||||||
while ($i >= $max_i && $j >= $max_j) { |
|
||||||
// Find the boundaries of the diff output of the two files |
|
||||||
for ($i = $j; |
|
||||||
$i < $end && substr($diff[$i], 0, 3) == '***'; |
|
||||||
$i++); |
|
||||||
for ($max_i = $i; |
|
||||||
$max_i < $end && substr($diff[$max_i], 0, 3) != '---'; |
|
||||||
$max_i++); |
|
||||||
for ($j = $max_i; |
|
||||||
$j < $end && substr($diff[$j], 0, 3) == '---'; |
|
||||||
$j++); |
|
||||||
for ($max_j = $j; |
|
||||||
$max_j < $end && substr($diff[$max_j], 0, 3) != '***'; |
|
||||||
$max_j++); |
|
||||||
} |
|
||||||
|
|
||||||
// find what hasn't been changed |
|
||||||
$array = array(); |
|
||||||
while ($i < $max_i && |
|
||||||
$j < $max_j && |
|
||||||
strcmp($diff[$i], $diff[$j]) == 0) { |
|
||||||
$array[] = substr($diff[$i], 2); |
|
||||||
$i++; |
|
||||||
$j++; |
|
||||||
} |
|
||||||
|
|
||||||
while ($i < $max_i && ($max_j-$j) <= 1) { |
|
||||||
if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { |
|
||||||
break; |
|
||||||
} |
|
||||||
$array[] = substr($diff[$i++], 2); |
|
||||||
} |
|
||||||
|
|
||||||
while ($j < $max_j && ($max_i-$i) <= 1) { |
|
||||||
if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { |
|
||||||
break; |
|
||||||
} |
|
||||||
$array[] = substr($diff[$j++], 2); |
|
||||||
} |
|
||||||
if (count($array) > 0) { |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Copy($array); |
|
||||||
} |
|
||||||
|
|
||||||
if ($i < $max_i) { |
|
||||||
$diff1 = array(); |
|
||||||
switch (substr($diff[$i], 0, 1)) { |
|
||||||
case '!': |
|
||||||
$diff2 = array(); |
|
||||||
do { |
|
||||||
$diff1[] = substr($diff[$i], 2); |
|
||||||
if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { |
|
||||||
$diff2[] = substr($diff[$j++], 2); |
|
||||||
} |
|
||||||
} while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2); |
|
||||||
break; |
|
||||||
|
|
||||||
case '+': |
|
||||||
do { |
|
||||||
$diff1[] = substr($diff[$i], 2); |
|
||||||
} while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Add($diff1); |
|
||||||
break; |
|
||||||
|
|
||||||
case '-': |
|
||||||
do { |
|
||||||
$diff1[] = substr($diff[$i], 2); |
|
||||||
} while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Delete($diff1); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if ($j < $max_j) { |
|
||||||
$diff2 = array(); |
|
||||||
switch (substr($diff[$j], 0, 1)) { |
|
||||||
case '+': |
|
||||||
do { |
|
||||||
$diff2[] = substr($diff[$j++], 2); |
|
||||||
} while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Add($diff2); |
|
||||||
break; |
|
||||||
|
|
||||||
case '-': |
|
||||||
do { |
|
||||||
$diff2[] = substr($diff[$j++], 2); |
|
||||||
} while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Delete($diff2); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $edits; |
|
||||||
} |
|
||||||
} |
|
@ -1,67 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Class used internally by Diff to actually compute the diffs. |
|
||||||
* |
|
||||||
* This class uses the xdiff PECL package (http://pecl.php.net/package/xdiff) |
|
||||||
* to compute the differences between the two input arrays. |
|
||||||
* |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @author Jon Parise <jon@horde.org> |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Engine_Xdiff |
|
||||||
{ |
|
||||||
/** |
|
||||||
*/ |
|
||||||
public function diff($from_lines, $to_lines) |
|
||||||
{ |
|
||||||
if (!extension_loaded('xdiff')) { |
|
||||||
throw new Horde_Text_Diff_Exception('The xdiff extension is required for this diff engine'); |
|
||||||
} |
|
||||||
|
|
||||||
array_walk($from_lines, array('Horde_Text_Diff', 'trimNewlines')); |
|
||||||
array_walk($to_lines, array('Horde_Text_Diff', 'trimNewlines')); |
|
||||||
|
|
||||||
/* Convert the two input arrays into strings for xdiff processing. */ |
|
||||||
$from_string = implode("\n", $from_lines); |
|
||||||
$to_string = implode("\n", $to_lines); |
|
||||||
|
|
||||||
/* Diff the two strings and convert the result to an array. */ |
|
||||||
$diff = xdiff_string_diff($from_string, $to_string, count($to_lines)); |
|
||||||
$diff = explode("\n", $diff); |
|
||||||
|
|
||||||
/* Walk through the diff one line at a time. We build the $edits |
|
||||||
* array of diff operations by reading the first character of the |
|
||||||
* xdiff output (which is in the "unified diff" format). |
|
||||||
* |
|
||||||
* Note that we don't have enough information to detect "changed" |
|
||||||
* lines using this approach, so we can't add Horde_Text_Diff_Op_Changed |
|
||||||
* instances to the $edits array. The result is still perfectly |
|
||||||
* valid, albeit a little less descriptive and efficient. */ |
|
||||||
$edits = array(); |
|
||||||
foreach ($diff as $line) { |
|
||||||
if (!strlen($line)) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
switch ($line[0]) { |
|
||||||
case ' ': |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Copy(array(substr($line, 1))); |
|
||||||
break; |
|
||||||
|
|
||||||
case '+': |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Add(array(substr($line, 1))); |
|
||||||
break; |
|
||||||
|
|
||||||
case '-': |
|
||||||
$edits[] = new Horde_Text_Diff_Op_Delete(array(substr($line, 1))); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $edits; |
|
||||||
} |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Exception handler for the Text_Diff package. |
|
||||||
* |
|
||||||
* Copyright 2011-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you |
|
||||||
* did not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @author Jan Schneider <jan@horde.org> |
|
||||||
* @category Horde |
|
||||||
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Exception extends Horde_Exception_Wrapped |
|
||||||
{ |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Mapped extends Horde_Text_Diff |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Computes a diff between sequences of strings. |
|
||||||
* |
|
||||||
* This can be used to compute things like case-insensitve diffs, or diffs |
|
||||||
* which ignore changes in white-space. |
|
||||||
* |
|
||||||
* @param array $from_lines An array of strings. |
|
||||||
* @param array $to_lines An array of strings. |
|
||||||
* @param array $mapped_from_lines This array should have the same size |
|
||||||
* number of elements as $from_lines. The |
|
||||||
* elements in $mapped_from_lines and |
|
||||||
* $mapped_to_lines are what is actually |
|
||||||
* compared when computing the diff. |
|
||||||
* @param array $mapped_to_lines This array should have the same number |
|
||||||
* of elements as $to_lines. |
|
||||||
*/ |
|
||||||
public function __construct($from_lines, $to_lines, |
|
||||||
$mapped_from_lines, $mapped_to_lines) |
|
||||||
{ |
|
||||||
assert(count($from_lines) == count($mapped_from_lines)); |
|
||||||
assert(count($to_lines) == count($mapped_to_lines)); |
|
||||||
|
|
||||||
parent::__construct($mapped_from_lines, $mapped_to_lines); |
|
||||||
|
|
||||||
$xi = $yi = 0; |
|
||||||
for ($i = 0; $i < count($this->_edits); $i++) { |
|
||||||
$orig = &$this->_edits[$i]->orig; |
|
||||||
if (is_array($orig)) { |
|
||||||
$orig = array_slice($from_lines, $xi, count($orig)); |
|
||||||
$xi += count($orig); |
|
||||||
} |
|
||||||
|
|
||||||
$final = &$this->_edits[$i]->final; |
|
||||||
if (is_array($final)) { |
|
||||||
$final = array_slice($to_lines, $yi, count($final)); |
|
||||||
$yi += count($final); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,27 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* The original PHP version of this code was written by Geoffrey T. Dairiki |
|
||||||
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
|
||||||
* |
|
||||||
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Op_Add extends Horde_Text_Diff_Op_Base |
|
||||||
{ |
|
||||||
public function __construct($lines) |
|
||||||
{ |
|
||||||
$this->final = $lines; |
|
||||||
$this->orig = false; |
|
||||||
} |
|
||||||
|
|
||||||
public function reverse() |
|
||||||
{ |
|
||||||
return new Horde_Text_Diff_Op_Delete($this->final); |
|
||||||
} |
|
||||||
} |
|
@ -1,31 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* The original PHP version of this code was written by Geoffrey T. Dairiki |
|
||||||
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
|
||||||
* |
|
||||||
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
abstract class Horde_Text_Diff_Op_Base |
|
||||||
{ |
|
||||||
public $orig; |
|
||||||
public $final; |
|
||||||
|
|
||||||
abstract public function reverse(); |
|
||||||
|
|
||||||
public function norig() |
|
||||||
{ |
|
||||||
return $this->orig ? count($this->orig) : 0; |
|
||||||
} |
|
||||||
|
|
||||||
public function nfinal() |
|
||||||
{ |
|
||||||
return $this->final ? count($this->final) : 0; |
|
||||||
} |
|
||||||
} |
|
@ -1,27 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* The original PHP version of this code was written by Geoffrey T. Dairiki |
|
||||||
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
|
||||||
* |
|
||||||
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Op_Change extends Horde_Text_Diff_Op_Base |
|
||||||
{ |
|
||||||
public function __construct($orig, $final) |
|
||||||
{ |
|
||||||
$this->orig = $orig; |
|
||||||
$this->final = $final; |
|
||||||
} |
|
||||||
|
|
||||||
public function reverse() |
|
||||||
{ |
|
||||||
return new Horde_Text_Diff_Op_Change($this->final, $this->orig); |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* The original PHP version of this code was written by Geoffrey T. Dairiki |
|
||||||
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
|
||||||
* |
|
||||||
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Op_Copy extends Horde_Text_Diff_Op_Base |
|
||||||
{ |
|
||||||
public function __construct($orig, $final = false) |
|
||||||
{ |
|
||||||
if (!is_array($final)) { |
|
||||||
$final = $orig; |
|
||||||
} |
|
||||||
$this->orig = $orig; |
|
||||||
$this->final = $final; |
|
||||||
} |
|
||||||
|
|
||||||
public function reverse() |
|
||||||
{ |
|
||||||
return new Horde_Text_Diff_Op_Copy($this->final, $this->orig); |
|
||||||
} |
|
||||||
} |
|
@ -1,27 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* The original PHP version of this code was written by Geoffrey T. Dairiki |
|
||||||
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
|
||||||
* |
|
||||||
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Op_Delete extends Horde_Text_Diff_Op_Base |
|
||||||
{ |
|
||||||
public function __construct($lines) |
|
||||||
{ |
|
||||||
$this->orig = $lines; |
|
||||||
$this->final = false; |
|
||||||
} |
|
||||||
|
|
||||||
public function reverse() |
|
||||||
{ |
|
||||||
return new Horde_Text_Diff_Op_Add($this->orig); |
|
||||||
} |
|
||||||
} |
|
@ -1,234 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* A class to render Diffs in different formats. |
|
||||||
* |
|
||||||
* This class renders the diff in classic diff format. It is intended that |
|
||||||
* this class be customized via inheritance, to obtain fancier outputs. |
|
||||||
* |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Renderer |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Number of leading context "lines" to preserve. |
|
||||||
* |
|
||||||
* This should be left at zero for this class, but subclasses may want to |
|
||||||
* set this to other values. |
|
||||||
*/ |
|
||||||
protected $_leading_context_lines = 0; |
|
||||||
|
|
||||||
/** |
|
||||||
* Number of trailing context "lines" to preserve. |
|
||||||
* |
|
||||||
* This should be left at zero for this class, but subclasses may want to |
|
||||||
* set this to other values. |
|
||||||
*/ |
|
||||||
protected $_trailing_context_lines = 0; |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor. |
|
||||||
*/ |
|
||||||
public function __construct($params = array()) |
|
||||||
{ |
|
||||||
foreach ($params as $param => $value) { |
|
||||||
$v = '_' . $param; |
|
||||||
if (isset($this->$v)) { |
|
||||||
$this->$v = $value; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Get any renderer parameters. |
|
||||||
* |
|
||||||
* @return array All parameters of this renderer object. |
|
||||||
*/ |
|
||||||
public function getParams() |
|
||||||
{ |
|
||||||
$params = array(); |
|
||||||
foreach (get_object_vars($this) as $k => $v) { |
|
||||||
if ($k[0] == '_') { |
|
||||||
$params[substr($k, 1)] = $v; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $params; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Renders a diff. |
|
||||||
* |
|
||||||
* @param Horde_Text_Diff $diff A Horde_Text_Diff object. |
|
||||||
* |
|
||||||
* @return string The formatted output. |
|
||||||
*/ |
|
||||||
public function render($diff) |
|
||||||
{ |
|
||||||
$xi = $yi = 1; |
|
||||||
$block = false; |
|
||||||
$context = array(); |
|
||||||
|
|
||||||
$nlead = $this->_leading_context_lines; |
|
||||||
$ntrail = $this->_trailing_context_lines; |
|
||||||
|
|
||||||
$output = $this->_startDiff(); |
|
||||||
|
|
||||||
$diffs = $diff->getDiff(); |
|
||||||
foreach ($diffs as $i => $edit) { |
|
||||||
/* If these are unchanged (copied) lines, and we want to keep |
|
||||||
* leading or trailing context lines, extract them from the copy |
|
||||||
* block. */ |
|
||||||
if ($edit instanceof Horde_Text_Diff_Op_Copy) { |
|
||||||
/* Do we have any diff blocks yet? */ |
|
||||||
if (is_array($block)) { |
|
||||||
/* How many lines to keep as context from the copy |
|
||||||
* block. */ |
|
||||||
$keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; |
|
||||||
if (count($edit->orig) <= $keep) { |
|
||||||
/* We have less lines in the block than we want for |
|
||||||
* context => keep the whole block. */ |
|
||||||
$block[] = $edit; |
|
||||||
} else { |
|
||||||
if ($ntrail) { |
|
||||||
/* Create a new block with as many lines as we need |
|
||||||
* for the trailing context. */ |
|
||||||
$context = array_slice($edit->orig, 0, $ntrail); |
|
||||||
$block[] = new Horde_Text_Diff_Op_Copy($context); |
|
||||||
} |
|
||||||
/* @todo */ |
|
||||||
$output .= $this->_block($x0, $ntrail + $xi - $x0, |
|
||||||
$y0, $ntrail + $yi - $y0, |
|
||||||
$block); |
|
||||||
$block = false; |
|
||||||
} |
|
||||||
} |
|
||||||
/* Keep the copy block as the context for the next block. */ |
|
||||||
$context = $edit->orig; |
|
||||||
} else { |
|
||||||
/* Don't we have any diff blocks yet? */ |
|
||||||
if (!is_array($block)) { |
|
||||||
/* Extract context lines from the preceding copy block. */ |
|
||||||
$context = array_slice($context, count($context) - $nlead); |
|
||||||
$x0 = $xi - count($context); |
|
||||||
$y0 = $yi - count($context); |
|
||||||
$block = array(); |
|
||||||
if ($context) { |
|
||||||
$block[] = new Horde_Text_Diff_Op_Copy($context); |
|
||||||
} |
|
||||||
} |
|
||||||
$block[] = $edit; |
|
||||||
} |
|
||||||
|
|
||||||
if ($edit->orig) { |
|
||||||
$xi += count($edit->orig); |
|
||||||
} |
|
||||||
if ($edit->final) { |
|
||||||
$yi += count($edit->final); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (is_array($block)) { |
|
||||||
$output .= $this->_block($x0, $xi - $x0, |
|
||||||
$y0, $yi - $y0, |
|
||||||
$block); |
|
||||||
} |
|
||||||
|
|
||||||
return $output . $this->_endDiff(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) |
|
||||||
{ |
|
||||||
$output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen)); |
|
||||||
|
|
||||||
foreach ($edits as $edit) { |
|
||||||
switch (get_class($edit)) { |
|
||||||
case 'Horde_Text_Diff_Op_Copy': |
|
||||||
$output .= $this->_context($edit->orig); |
|
||||||
break; |
|
||||||
|
|
||||||
case 'Horde_Text_Diff_Op_Add': |
|
||||||
$output .= $this->_added($edit->final); |
|
||||||
break; |
|
||||||
|
|
||||||
case 'Horde_Text_Diff_Op_Delete': |
|
||||||
$output .= $this->_deleted($edit->orig); |
|
||||||
break; |
|
||||||
|
|
||||||
case 'Horde_Text_Diff_Op_Change': |
|
||||||
$output .= $this->_changed($edit->orig, $edit->final); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $output . $this->_endBlock(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _startDiff() |
|
||||||
{ |
|
||||||
return ''; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _endDiff() |
|
||||||
{ |
|
||||||
return ''; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
|
||||||
{ |
|
||||||
if ($xlen > 1) { |
|
||||||
$xbeg .= ',' . ($xbeg + $xlen - 1); |
|
||||||
} |
|
||||||
if ($ylen > 1) { |
|
||||||
$ybeg .= ',' . ($ybeg + $ylen - 1); |
|
||||||
} |
|
||||||
|
|
||||||
// this matches the GNU Diff behaviour |
|
||||||
if ($xlen && !$ylen) { |
|
||||||
$ybeg--; |
|
||||||
} elseif (!$xlen) { |
|
||||||
$xbeg--; |
|
||||||
} |
|
||||||
|
|
||||||
return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _startBlock($header) |
|
||||||
{ |
|
||||||
return $header . "\n"; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _endBlock() |
|
||||||
{ |
|
||||||
return ''; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _lines($lines, $prefix = ' ') |
|
||||||
{ |
|
||||||
return $prefix . implode("\n$prefix", $lines) . "\n"; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _context($lines) |
|
||||||
{ |
|
||||||
return $this->_lines($lines, ' '); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _added($lines) |
|
||||||
{ |
|
||||||
return $this->_lines($lines, '> '); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _deleted($lines) |
|
||||||
{ |
|
||||||
return $this->_lines($lines, '< '); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _changed($orig, $final) |
|
||||||
{ |
|
||||||
return $this->_deleted($orig) . "---\n" . $this->_added($final); |
|
||||||
} |
|
||||||
} |
|
@ -1,68 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* "Context" diff renderer. |
|
||||||
* |
|
||||||
* This class renders the diff in classic "context diff" format. |
|
||||||
* |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Renderer_Context extends Horde_Text_Diff_Renderer |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Number of leading context "lines" to preserve. |
|
||||||
*/ |
|
||||||
protected $_leading_context_lines = 4; |
|
||||||
|
|
||||||
/** |
|
||||||
* Number of trailing context "lines" to preserve. |
|
||||||
*/ |
|
||||||
protected $_trailing_context_lines = 4; |
|
||||||
|
|
||||||
protected $_second_block = ''; |
|
||||||
|
|
||||||
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
|
||||||
{ |
|
||||||
if ($xlen != 1) { |
|
||||||
$xbeg .= ',' . $xlen; |
|
||||||
} |
|
||||||
if ($ylen != 1) { |
|
||||||
$ybeg .= ',' . $ylen; |
|
||||||
} |
|
||||||
$this->_second_block = "--- $ybeg ----\n"; |
|
||||||
return "***************\n*** $xbeg ****"; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _endBlock() |
|
||||||
{ |
|
||||||
return $this->_second_block; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _context($lines) |
|
||||||
{ |
|
||||||
$this->_second_block .= $this->_lines($lines, ' '); |
|
||||||
return $this->_lines($lines, ' '); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _added($lines) |
|
||||||
{ |
|
||||||
$this->_second_block .= $this->_lines($lines, '+ '); |
|
||||||
return ''; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _deleted($lines) |
|
||||||
{ |
|
||||||
return $this->_lines($lines, '- '); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _changed($orig, $final) |
|
||||||
{ |
|
||||||
$this->_second_block .= $this->_lines($final, '! '); |
|
||||||
return $this->_lines($orig, '! '); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,193 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* "Inline" diff renderer. |
|
||||||
* |
|
||||||
* This class renders diffs in the Wiki-style "inline" format. |
|
||||||
* |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @author Ciprian Popovici |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Renderer_Inline extends Horde_Text_Diff_Renderer |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Number of leading context "lines" to preserve. |
|
||||||
* |
|
||||||
* @var integer |
|
||||||
*/ |
|
||||||
protected $_leading_context_lines = 10000; |
|
||||||
|
|
||||||
/** |
|
||||||
* Number of trailing context "lines" to preserve. |
|
||||||
* |
|
||||||
* @var integer |
|
||||||
*/ |
|
||||||
protected $_trailing_context_lines = 10000; |
|
||||||
|
|
||||||
/** |
|
||||||
* Prefix for inserted text. |
|
||||||
* |
|
||||||
* @var string |
|
||||||
*/ |
|
||||||
protected $_ins_prefix = '<ins>'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Suffix for inserted text. |
|
||||||
* |
|
||||||
* @var string |
|
||||||
*/ |
|
||||||
protected $_ins_suffix = '</ins>'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Prefix for deleted text. |
|
||||||
* |
|
||||||
* @var string |
|
||||||
*/ |
|
||||||
protected $_del_prefix = '<del>'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Suffix for deleted text. |
|
||||||
* |
|
||||||
* @var string |
|
||||||
*/ |
|
||||||
protected $_del_suffix = '</del>'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Header for each change block. |
|
||||||
* |
|
||||||
* @var string |
|
||||||
*/ |
|
||||||
protected $_block_header = ''; |
|
||||||
|
|
||||||
/** |
|
||||||
* Whether to split down to character-level. |
|
||||||
* |
|
||||||
* @var boolean |
|
||||||
*/ |
|
||||||
protected $_split_characters = false; |
|
||||||
|
|
||||||
/** |
|
||||||
* What are we currently splitting on? Used to recurse to show word-level |
|
||||||
* or character-level changes. |
|
||||||
* |
|
||||||
* @var string |
|
||||||
*/ |
|
||||||
protected $_split_level = 'lines'; |
|
||||||
|
|
||||||
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
|
||||||
{ |
|
||||||
return $this->_block_header; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _startBlock($header) |
|
||||||
{ |
|
||||||
return $header; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _lines($lines, $prefix = ' ', $encode = true) |
|
||||||
{ |
|
||||||
if ($encode) { |
|
||||||
array_walk($lines, array(&$this, '_encode')); |
|
||||||
} |
|
||||||
|
|
||||||
if ($this->_split_level == 'lines') { |
|
||||||
return implode("\n", $lines) . "\n"; |
|
||||||
} else { |
|
||||||
return implode('', $lines); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected function _added($lines) |
|
||||||
{ |
|
||||||
array_walk($lines, array(&$this, '_encode')); |
|
||||||
$lines[0] = $this->_ins_prefix . $lines[0]; |
|
||||||
$lines[count($lines) - 1] .= $this->_ins_suffix; |
|
||||||
return $this->_lines($lines, ' ', false); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _deleted($lines, $words = false) |
|
||||||
{ |
|
||||||
array_walk($lines, array(&$this, '_encode')); |
|
||||||
$lines[0] = $this->_del_prefix . $lines[0]; |
|
||||||
$lines[count($lines) - 1] .= $this->_del_suffix; |
|
||||||
return $this->_lines($lines, ' ', false); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _changed($orig, $final) |
|
||||||
{ |
|
||||||
/* If we've already split on characters, just display. */ |
|
||||||
if ($this->_split_level == 'characters') { |
|
||||||
return $this->_deleted($orig) |
|
||||||
. $this->_added($final); |
|
||||||
} |
|
||||||
|
|
||||||
/* If we've already split on words, just display. */ |
|
||||||
if ($this->_split_level == 'words') { |
|
||||||
$prefix = ''; |
|
||||||
while ($orig[0] !== false && $final[0] !== false && |
|
||||||
substr($orig[0], 0, 1) == ' ' && |
|
||||||
substr($final[0], 0, 1) == ' ') { |
|
||||||
$prefix .= substr($orig[0], 0, 1); |
|
||||||
$orig[0] = substr($orig[0], 1); |
|
||||||
$final[0] = substr($final[0], 1); |
|
||||||
} |
|
||||||
return $prefix . $this->_deleted($orig) . $this->_added($final); |
|
||||||
} |
|
||||||
|
|
||||||
$text1 = implode("\n", $orig); |
|
||||||
$text2 = implode("\n", $final); |
|
||||||
|
|
||||||
/* Non-printing newline marker. */ |
|
||||||
$nl = "\0"; |
|
||||||
|
|
||||||
if ($this->_split_characters) { |
|
||||||
$diff = new Horde_Text_Diff('native', |
|
||||||
array(preg_split('//', $text1), |
|
||||||
preg_split('//', $text2))); |
|
||||||
} else { |
|
||||||
/* We want to split on word boundaries, but we need to preserve |
|
||||||
* whitespace as well. Therefore we split on words, but include |
|
||||||
* all blocks of whitespace in the wordlist. */ |
|
||||||
$diff = new Horde_Text_Diff('native', |
|
||||||
array($this->_splitOnWords($text1, $nl), |
|
||||||
$this->_splitOnWords($text2, $nl))); |
|
||||||
} |
|
||||||
|
|
||||||
/* Get the diff in inline format. */ |
|
||||||
$renderer = new Horde_Text_Diff_Renderer_inline |
|
||||||
(array_merge($this->getParams(), |
|
||||||
array('split_level' => $this->_split_characters ? 'characters' : 'words'))); |
|
||||||
|
|
||||||
/* Run the diff and get the output. */ |
|
||||||
return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _splitOnWords($string, $newlineEscape = "\n") |
|
||||||
{ |
|
||||||
// Ignore \0; otherwise the while loop will never finish. |
|
||||||
$string = str_replace("\0", '', $string); |
|
||||||
|
|
||||||
$words = array(); |
|
||||||
$length = strlen($string); |
|
||||||
$pos = 0; |
|
||||||
|
|
||||||
while ($pos < $length) { |
|
||||||
// Eat a word with any preceding whitespace. |
|
||||||
$spaces = strspn(substr($string, $pos), " \n"); |
|
||||||
$nextpos = strcspn(substr($string, $pos + $spaces), " \n"); |
|
||||||
$words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); |
|
||||||
$pos += $spaces + $nextpos; |
|
||||||
} |
|
||||||
|
|
||||||
return $words; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _encode(&$string) |
|
||||||
{ |
|
||||||
$string = htmlspecialchars($string); |
|
||||||
} |
|
||||||
} |
|
@ -1,57 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* "Unified" diff renderer. |
|
||||||
* |
|
||||||
* This class renders the diff in classic "unified diff" format. |
|
||||||
* |
|
||||||
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @author Ciprian Popovici |
|
||||||
* @package Text_Diff |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_Renderer_Unified extends Horde_Text_Diff_Renderer |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Number of leading context "lines" to preserve. |
|
||||||
*/ |
|
||||||
protected $_leading_context_lines = 4; |
|
||||||
|
|
||||||
/** |
|
||||||
* Number of trailing context "lines" to preserve. |
|
||||||
*/ |
|
||||||
protected $_trailing_context_lines = 4; |
|
||||||
|
|
||||||
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
|
||||||
{ |
|
||||||
if ($xlen != 1) { |
|
||||||
$xbeg .= ',' . $xlen; |
|
||||||
} |
|
||||||
if ($ylen != 1) { |
|
||||||
$ybeg .= ',' . $ylen; |
|
||||||
} |
|
||||||
return "@@ -$xbeg +$ybeg @@"; |
|
||||||
} |
|
||||||
|
|
||||||
protected function _context($lines) |
|
||||||
{ |
|
||||||
return $this->_lines($lines, ' '); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _added($lines) |
|
||||||
{ |
|
||||||
return $this->_lines($lines, '+'); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _deleted($lines) |
|
||||||
{ |
|
||||||
return $this->_lines($lines, '-'); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _changed($orig, $final) |
|
||||||
{ |
|
||||||
return $this->_deleted($orig) . $this->_added($final); |
|
||||||
} |
|
||||||
} |
|
@ -1,143 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* A class for computing three way merges. |
|
||||||
* |
|
||||||
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_ThreeWay |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Array of changes. |
|
||||||
* |
|
||||||
* @var array |
|
||||||
*/ |
|
||||||
protected $_edits; |
|
||||||
|
|
||||||
/** |
|
||||||
* Conflict counter. |
|
||||||
* |
|
||||||
* @var integer |
|
||||||
*/ |
|
||||||
protected $_conflictingBlocks = 0; |
|
||||||
|
|
||||||
/** |
|
||||||
* Computes diff between 3 sequences of strings. |
|
||||||
* |
|
||||||
* @param array $orig The original lines to use. |
|
||||||
* @param array $final1 The first version to compare to. |
|
||||||
* @param array $final2 The second version to compare to. |
|
||||||
*/ |
|
||||||
public function __construct($orig, $final1, $final2) |
|
||||||
{ |
|
||||||
if (extension_loaded('xdiff')) { |
|
||||||
$engine = new Horde_Text_Diff_Engine_Xdiff(); |
|
||||||
} else { |
|
||||||
$engine = new Horde_Text_Diff_Engine_Native(); |
|
||||||
} |
|
||||||
|
|
||||||
$this->_edits = $this->_diff3($engine->diff($orig, $final1), |
|
||||||
$engine->diff($orig, $final2)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*/ |
|
||||||
public function mergedOutput($label1 = false, $label2 = false) |
|
||||||
{ |
|
||||||
$lines = array(); |
|
||||||
foreach ($this->_edits as $edit) { |
|
||||||
if ($edit->isConflict()) { |
|
||||||
/* FIXME: this should probably be moved somewhere else. */ |
|
||||||
$lines = array_merge($lines, |
|
||||||
array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), |
|
||||||
$edit->final1, |
|
||||||
array("======="), |
|
||||||
$edit->final2, |
|
||||||
array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); |
|
||||||
$this->_conflictingBlocks++; |
|
||||||
} else { |
|
||||||
$lines = array_merge($lines, $edit->merged()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $lines; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
*/ |
|
||||||
protected function _diff3($edits1, $edits2) |
|
||||||
{ |
|
||||||
$edits = array(); |
|
||||||
$bb = new Horde_Text_Diff_ThreeWay_BlockBuilder(); |
|
||||||
|
|
||||||
$e1 = current($edits1); |
|
||||||
$e2 = current($edits2); |
|
||||||
while ($e1 || $e2) { |
|
||||||
if ($e1 && $e2 && |
|
||||||
$e1 instanceof Horde_Text_Diff_Op_Copy && |
|
||||||
$e2 instanceof Horde_Text_Diff_Op_Copy) { |
|
||||||
/* We have copy blocks from both diffs. This is the (only) |
|
||||||
* time we want to emit a diff3 copy block. Flush current |
|
||||||
* diff3 diff block, if any. */ |
|
||||||
if ($edit = $bb->finish()) { |
|
||||||
$edits[] = $edit; |
|
||||||
} |
|
||||||
|
|
||||||
$ncopy = min($e1->norig(), $e2->norig()); |
|
||||||
assert($ncopy > 0); |
|
||||||
$edits[] = new Horde_Text_Diff_ThreeWay_Op_Copy(array_slice($e1->orig, 0, $ncopy)); |
|
||||||
|
|
||||||
if ($e1->norig() > $ncopy) { |
|
||||||
array_splice($e1->orig, 0, $ncopy); |
|
||||||
array_splice($e1->final, 0, $ncopy); |
|
||||||
} else { |
|
||||||
$e1 = next($edits1); |
|
||||||
} |
|
||||||
|
|
||||||
if ($e2->norig() > $ncopy) { |
|
||||||
array_splice($e2->orig, 0, $ncopy); |
|
||||||
array_splice($e2->final, 0, $ncopy); |
|
||||||
} else { |
|
||||||
$e2 = next($edits2); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if ($e1 && $e2) { |
|
||||||
if ($e1->orig && $e2->orig) { |
|
||||||
$norig = min($e1->norig(), $e2->norig()); |
|
||||||
$orig = array_splice($e1->orig, 0, $norig); |
|
||||||
array_splice($e2->orig, 0, $norig); |
|
||||||
$bb->input($orig); |
|
||||||
} |
|
||||||
|
|
||||||
if ($e1 instanceof Horde_Text_Diff_Op_Copy) { |
|
||||||
$bb->out1(array_splice($e1->final, 0, $norig)); |
|
||||||
} |
|
||||||
|
|
||||||
if ($e2 instanceof Horde_Text_Diff_Op_Copy) { |
|
||||||
$bb->out2(array_splice($e2->final, 0, $norig)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if ($e1 && ! $e1->orig) { |
|
||||||
$bb->out1($e1->final); |
|
||||||
$e1 = next($edits1); |
|
||||||
} |
|
||||||
if ($e2 && ! $e2->orig) { |
|
||||||
$bb->out2($e2->final); |
|
||||||
$e2 = next($edits2); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if ($edit = $bb->finish()) { |
|
||||||
$edits[] = $edit; |
|
||||||
} |
|
||||||
|
|
||||||
return $edits; |
|
||||||
} |
|
||||||
} |
|
@ -1,64 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_ThreeWay_BlockBuilder |
|
||||||
{ |
|
||||||
public function __construct() |
|
||||||
{ |
|
||||||
$this->_init(); |
|
||||||
} |
|
||||||
|
|
||||||
public function input($lines) |
|
||||||
{ |
|
||||||
if ($lines) { |
|
||||||
$this->_append($this->orig, $lines); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public function out1($lines) |
|
||||||
{ |
|
||||||
if ($lines) { |
|
||||||
$this->_append($this->final1, $lines); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public function out2($lines) |
|
||||||
{ |
|
||||||
if ($lines) { |
|
||||||
$this->_append($this->final2, $lines); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public function isEmpty() |
|
||||||
{ |
|
||||||
return !$this->orig && !$this->final1 && !$this->final2; |
|
||||||
} |
|
||||||
|
|
||||||
public function finish() |
|
||||||
{ |
|
||||||
if ($this->isEmpty()) { |
|
||||||
return false; |
|
||||||
} else { |
|
||||||
$edit = new Horde_Text_Diff_ThreeWay_Op_Base($this->orig, $this->final1, $this->final2); |
|
||||||
$this->_init(); |
|
||||||
return $edit; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected function _init() |
|
||||||
{ |
|
||||||
$this->orig = $this->final1 = $this->final2 = array(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function _append(&$array, $lines) |
|
||||||
{ |
|
||||||
array_splice($array, sizeof($array), 0, $lines); |
|
||||||
} |
|
||||||
} |
|
@ -1,41 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_ThreeWay_Op_Base |
|
||||||
{ |
|
||||||
public function __construct($orig = false, $final1 = false, $final2 = false) |
|
||||||
{ |
|
||||||
$this->orig = $orig ? $orig : array(); |
|
||||||
$this->final1 = $final1 ? $final1 : array(); |
|
||||||
$this->final2 = $final2 ? $final2 : array(); |
|
||||||
} |
|
||||||
|
|
||||||
public function merged() |
|
||||||
{ |
|
||||||
if (!isset($this->_merged)) { |
|
||||||
if ($this->final1 === $this->final2) { |
|
||||||
$this->_merged = &$this->final1; |
|
||||||
} elseif ($this->final1 === $this->orig) { |
|
||||||
$this->_merged = &$this->final2; |
|
||||||
} elseif ($this->final2 === $this->orig) { |
|
||||||
$this->_merged = &$this->final1; |
|
||||||
} else { |
|
||||||
$this->_merged = false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return $this->_merged; |
|
||||||
} |
|
||||||
|
|
||||||
public function isConflict() |
|
||||||
{ |
|
||||||
return $this->merged() === false; |
|
||||||
} |
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
|
||||||
* |
|
||||||
* See the enclosed file COPYING for license information (LGPL). If you did |
|
||||||
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
|
||||||
* |
|
||||||
* @package Text_Diff |
|
||||||
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|
||||||
*/ |
|
||||||
class Horde_Text_Diff_ThreeWay_Op_Copy extends Horde_Text_Diff_ThreeWay_Op_Base |
|
||||||
{ |
|
||||||
public function __construct($lines = false) |
|
||||||
{ |
|
||||||
$this->orig = $lines ? $lines : array(); |
|
||||||
$this->final1 = &$this->orig; |
|
||||||
$this->final2 = &$this->orig; |
|
||||||
} |
|
||||||
|
|
||||||
public function merged() |
|
||||||
{ |
|
||||||
return $this->orig; |
|
||||||
} |
|
||||||
|
|
||||||
public function isConflict() |
|
||||||
{ |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue