35 array_walk($from_lines, array($this,
'_trimNewlines'));
36 array_walk($to_lines, array($this,
'_trimNewlines'));
38 if (extension_loaded(
'xdiff')) {
44 $this->_edits =
$engine->diff($from_lines, $to_lines);
73 $rev->_edits = array();
74 foreach ($this->_edits as $edit) {
75 $rev->_edits[] = $edit->reverse();
87 foreach ($this->_edits as $edit) {
105 foreach ($this->_edits as $edit) {
107 $lcs += count($edit->orig);
123 foreach ($this->_edits as $edit) {
125 array_splice($lines, count($lines), 0, $edit->orig);
141 foreach ($this->_edits as $edit) {
143 array_splice($lines, count($lines), 0, $edit->final);
158 $line = str_replace(array(
"\n",
"\r"),
'', $line);
166 public function _check($from_lines, $to_lines)
168 if (serialize($from_lines) != serialize($this->
getOriginal())) {
169 trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
171 if (serialize($to_lines) != serialize($this->
getFinal())) {
172 trigger_error(
"Reconstructed final doesn't match", E_USER_ERROR);
176 if (serialize($to_lines) != serialize($rev->getOriginal())) {
177 trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
179 if (serialize($from_lines) != serialize($rev->getFinal())) {
180 trigger_error(
"Reversed final doesn't match", E_USER_ERROR);
184 foreach ($this->_edits as $edit) {
185 if ($prevtype == get_class($edit)) {
186 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
188 $prevtype = get_class($edit);
226 assert(count($from_lines) == count($mapped_from_lines));
227 assert(count($to_lines) == count($mapped_to_lines));
229 parent::__construct($mapped_from_lines, $mapped_to_lines);
232 for (
$i = 0;
$i < count($this->_edits);
$i++) {
233 $orig = &$this->_edits[
$i]->orig;
234 if (is_array($orig)) {
235 $orig = array_slice($from_lines, $xi, count($orig));
239 $final = &$this->_edits[
$i]->final;
240 if (is_array($final)) {
241 $final = array_slice($to_lines, $yi, count($final));
242 $yi += count($final);
260 public function diff($from_lines, $to_lines)
263 $from_string = implode(
"\n", $from_lines);
264 $to_string = implode(
"\n", $to_lines);
267 $diff = xdiff_string_diff($from_string, $to_string, count($to_lines));
268 $diff = explode(
"\n", $diff);
279 foreach ($diff as $line) {
325 public function diff($from_lines, $to_lines)
327 $n_from = count($from_lines);
328 $n_to = count($to_lines);
330 $this->xchanged = $this->ychanged = array();
331 $this->xv = $this->yv = array();
332 $this->xind = $this->yind = array();
334 unset($this->in_seq);
338 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
339 if ($from_lines[$skip] != $to_lines[$skip]) {
342 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
348 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
349 if ($from_lines[$xi] != $to_lines[$yi]) {
352 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
356 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
357 $xhash[$from_lines[$xi]] = 1;
359 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
360 $line = $to_lines[$yi];
361 if (($this->ychanged[$yi] = empty($xhash[$line]))) {
368 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
369 $line = $from_lines[$xi];
370 if (($this->xchanged[$xi] = empty($yhash[$line]))) {
378 $this->_compareseq(0, count($this->xv), 0, count($this->yv));
381 $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged);
382 $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged);
387 while ($xi < $n_from || $yi < $n_to) {
388 assert($yi < $n_to || $this->xchanged[$xi]);
389 assert($xi < $n_from || $this->ychanged[$yi]);
393 while ($xi < $n_from && $yi < $n_to
394 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
395 $copy[] = $from_lines[$xi++];
404 while ($xi < $n_from && $this->xchanged[$xi]) {
405 $delete[] = $from_lines[$xi++];
409 while ($yi < $n_to && $this->ychanged[$yi]) {
410 $add[] = $to_lines[$yi++];
413 if ($delete && $add) {
441 public function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)
445 if ($xlim - $xoff > $ylim - $yoff) {
449 list($xoff, $xlim, $yoff, $ylim)
450 = array($yoff, $ylim, $xoff, $xlim);
454 for (
$i = $ylim - 1;
$i >= $yoff;
$i--) {
455 $ymatches[$this->xv[
$i]][] =
$i;
458 for (
$i = $ylim - 1;
$i >= $yoff;
$i--) {
459 $ymatches[$this->yv[
$i]][] =
$i;
464 $this->seq[0] = $yoff - 1;
465 $this->in_seq = array();
468 $numer = $xlim - $xoff + $nchunks - 1;
470 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
472 for (
$i = 0;
$i <= $this->lcs;
$i++) {
473 $ymids[
$i][$chunk - 1] = $this->seq[
$i];
477 $x1 = $xoff + (int) (($numer + ($xlim - $xoff) * $chunk) / $nchunks);
478 for (;
$x < $x1;
$x++) {
479 $line = $flip ? $this->yv[
$x] : $this->xv[
$x];
480 if (empty($ymatches[$line])) {
483 $matches = $ymatches[$line];
484 foreach ($matches as
$y) {
485 if (empty($this->in_seq[$y])) {
486 $k = $this->_lcsPos($y);
488 $ymids[$k] = $ymids[$k - 1];
493 while (list($junk, $y) = each($matches)) {
494 if ($y > $this->seq[$k - 1]) {
495 assert($y < $this->seq[$k]);
498 $this->in_seq[$this->seq[$k]] =
false;
500 $this->in_seq[
$y] = 1;
501 } elseif (empty($this->in_seq[$y])) {
502 $k = $this->_lcsPos($y);
504 $ymids[$k] = $ymids[$k - 1];
510 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
511 $ymid = $ymids[$this->lcs];
512 for (
$n = 0;
$n < $nchunks - 1;
$n++) {
513 $x1 = $xoff + (int) (($numer + ($xlim - $xoff) *
$n) / $nchunks);
515 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
517 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
519 return array($this->
lcs, $seps);
525 if (
$end == 0 || $ypos > $this->seq[
$end]) {
526 $this->seq[++$this->lcs] = $ypos;
527 $this->in_seq[$ypos] = 1;
532 while ($beg < $end) {
533 $mid = (int) (($beg + $end) / 2);
534 if ($ypos > $this->seq[$mid]) {
541 assert($ypos != $this->seq[$end]);
543 $this->in_seq[$this->seq[
$end]] =
false;
544 $this->seq[
$end] = $ypos;
545 $this->in_seq[$ypos] = 1;
564 while ($xoff < $xlim && $yoff < $ylim
565 && $this->xv[$xoff] == $this->yv[$yoff]) {
571 while ($xlim > $xoff && $ylim > $yoff
572 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
577 if ($xoff == $xlim || $yoff == $ylim) {
583 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
585 = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
591 while ($yoff < $ylim) {
592 $this->ychanged[$this->yind[$yoff++]] = 1;
594 while ($xoff < $xlim) {
595 $this->xchanged[$this->xind[$xoff++]] = 1;
601 while ($pt2 = next($seps)) {
602 $this->_compareseq($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
625 assert(count($lines) == count(
$changed));
626 $len = count($lines);
627 $other_len = count($other_changed);
641 while ($j < $other_len && $other_changed[$j]) {
646 assert($j < $other_len && !$other_changed[$j]);
649 while ($j < $other_len && $other_changed[$j]) {
661 while (++$i < $len &&
$changed[$i]) {
673 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
676 while ($start > 0 &&
$changed[$start - 1]) {
680 while ($other_changed[--$j]) {
683 assert($j >= 0 && !$other_changed[$j]);
690 $corresponding = $j < $other_len ?
$i : $len;
697 while ($i < $len && $lines[$start] == $lines[$i]) {
704 assert($j < $other_len && !$other_changed[$j]);
706 if ($j < $other_len && $other_changed[$j]) {
708 while ($j < $other_len && $other_changed[$j]) {
713 }
while ($runlength != $i -
$start);
717 while ($corresponding < $i) {
721 while ($other_changed[--$j]) {
724 assert($j >= 0 && !$other_changed[$j]);
743 trigger_error(
'Abstract method', E_USER_ERROR);
748 return $this->orig ? count($this->orig) : 0;
753 return $this->
final ? count($this->
final) : 0;
767 if (!is_array($final)) {
771 $this->
final = $final;
790 $this->orig = $lines;
791 $this->
final =
false;
810 $this->
final = $lines;
831 $this->
final = $final;
getDiff()
Returns the array of differences.
_diag($xoff, $xlim, $yoff, $ylim, $nchunks)
Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized segments.
_compareseq($xoff, $xlim, $yoff, $ylim)
Finds LCS of two sequences.
lcs()
Computes the length of the Longest Common Subsequence (LCS).
__construct( $from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines)
Computes a diff between sequences of strings.
_check($from_lines, $to_lines)
Checks a diff for validity.
_trimNewlines(&$line, $key)
Removes trailing newlines from a line of text.
getOriginal()
Gets the original set of lines.
__construct($from_lines, $to_lines)
Computes diffs between sequences of strings.
diff($from_lines, $to_lines)
diff($from_lines, $to_lines)
getFinal()
Gets the final set of lines.
__construct($orig, $final=false)
reverse()
Computes a reversed diff.
isEmpty()
Checks for an empty diff.
_shiftBoundaries($lines, &$changed, $other_changed)
Adjusts inserts/deletes of identical lines to join changes as much as possible.
__construct($orig, $final)