72 if ( array_key_exists( $i, $this->closing ) ) {
73 return $this->closing[$i];
78 abstract public function reverse();
83 public function norig() {
84 return $this->orig ? count( $this->orig ) : 0;
91 return $this->closing ? count( $this->closing ) : 0;
101 public $type =
'copy';
115 return new DiffOpCopy( $this->closing, $this->orig );
125 public $type =
'delete';
129 $this->closing =
false;
146 public $type =
'add';
167 public $type =
'change';
224 public function diff( $from_lines, $to_lines ) {
228 $this->
diffLocal( $from_lines, $to_lines );
231 $this->
shiftBoundaries( $from_lines, $this->xchanged, $this->ychanged );
235 $n_from = count( $from_lines );
236 $n_to = count( $to_lines );
240 while ( $xi < $n_from || $yi < $n_to ) {
241 assert(
'$yi < $n_to || $this->xchanged[$xi]' );
242 assert(
'$xi < $n_from || $this->ychanged[$yi]' );
246 while ( $xi < $n_from && $yi < $n_to
247 && !$this->xchanged[$xi] && !$this->ychanged[$yi]
249 $copy[] = $from_lines[$xi++];
258 while ( $xi < $n_from && $this->xchanged[$xi] ) {
259 $delete[] = $from_lines[$xi++];
263 while ( $yi < $n_to && $this->ychanged[$yi] ) {
264 $add[] = $to_lines[$yi++];
267 if ( $delete && $add ) {
269 } elseif ( $delete ) {
284 private function diffLocal( $from_lines, $to_lines ) {
285 global $wgExternalDiffEngine;
288 if ( $wgExternalDiffEngine ==
'wikidiff3' ) {
291 $wikidiff3->diff( $from_lines, $to_lines );
292 $this->xchanged = $wikidiff3->removed;
293 $this->ychanged = $wikidiff3->added;
297 $n_from = count( $from_lines );
298 $n_to = count( $to_lines );
299 $this->xchanged = $this->ychanged =
array();
300 $this->xv = $this->yv =
array();
301 $this->xind = $this->yind =
array();
303 unset( $this->in_seq );
307 for ( $skip = 0; $skip < $n_from && $skip < $n_to; $skip++ ) {
308 if ( $from_lines[$skip] !== $to_lines[$skip] ) {
311 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
316 for ( $endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++ ) {
317 if ( $from_lines[$xi] !== $to_lines[$yi] ) {
320 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
324 for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) {
325 $xhash[$this->
lineHash( $from_lines[$xi] )] = 1;
328 for ( $yi = $skip; $yi < $n_to - $endskip; $yi++ ) {
329 $line = $to_lines[$yi];
330 if ( ( $this->ychanged[$yi] = empty( $xhash[$this->
lineHash(
$line )] ) ) ) {
337 for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) {
338 $line = $from_lines[$xi];
339 if ( ( $this->xchanged[$xi] = empty( $yhash[$this->
lineHash(
$line )] ) ) ) {
347 $this->
compareSeq( 0, count( $this->xv ), 0, count( $this->yv ) );
360 if ( strlen(
$line ) > self::MAX_XREF_LENGTH ) {
392 private function diag( $xoff, $xlim, $yoff, $ylim, $nchunks ) {
395 if ( $xlim - $xoff > $ylim - $yoff ) {
399 list( $xoff, $xlim, $yoff, $ylim ) =
array( $yoff, $ylim, $xoff, $xlim );
403 for ( $i = $ylim - 1; $i >= $yoff; $i-- ) {
404 $ymatches[$this->xv[$i]][] = $i;
407 for ( $i = $ylim - 1; $i >= $yoff; $i-- ) {
408 $ymatches[$this->yv[$i]][] = $i;
413 $this->seq[0] = $yoff - 1;
414 $this->in_seq =
array();
417 $numer = $xlim - $xoff + $nchunks - 1;
419 for ( $chunk = 0; $chunk < $nchunks; $chunk++ ) {
422 $ymids[$i][$chunk - 1] = $this->seq[$i];
426 $x1 = $xoff + (int)( ( $numer + ( $xlim - $xoff ) * $chunk ) / $nchunks );
427 for ( ; $x < $x1; $x++ ) {
428 $line = $flip ? $this->yv[$x] : $this->xv[$x];
429 if ( empty( $ymatches[
$line] ) ) {
437 if ( empty( $this->in_seq[$y] ) ) {
440 $ymids[$k] = $ymids[$k - 1];
446 if ( $y > $this->seq[$k - 1] ) {
447 assert(
'$y < $this->seq[$k]' );
450 $this->in_seq[$this->seq[$k]] =
false;
452 $this->in_seq[$y] = 1;
453 } elseif ( empty( $this->in_seq[$y] ) ) {
456 $ymids[$k] = $ymids[$k - 1];
462 $seps[] = $flip ?
array( $yoff, $xoff ) :
array( $xoff, $yoff );
464 for (
$n = 0;
$n < $nchunks - 1;
$n++ ) {
465 $x1 = $xoff + (int)( ( $numer + ( $xlim - $xoff ) *
$n ) / $nchunks );
467 $seps[] = $flip ?
array( $y1, $x1 ) :
array( $x1, $y1 );
469 $seps[] = $flip ?
array( $ylim, $xlim ) :
array( $xlim, $ylim );
471 return array( $this->lcs, $seps );
479 private function lcsPos( $ypos ) {
481 if ( $end == 0 || $ypos > $this->seq[$end] ) {
483 $this->in_seq[$ypos] = 1;
489 while ( $beg < $end ) {
490 $mid = (int)( ( $beg + $end ) / 2 );
491 if ( $ypos > $this->seq[$mid] ) {
498 assert(
'$ypos != $this->seq[$end]' );
500 $this->in_seq[$this->seq[$end]] =
false;
501 $this->seq[$end] = $ypos;
502 $this->in_seq[$ypos] = 1;
524 private function compareSeq( $xoff, $xlim, $yoff, $ylim ) {
526 while ( $xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff] ) {
532 while ( $xlim > $xoff && $ylim > $yoff
533 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]
539 if ( $xoff == $xlim || $yoff == $ylim ) {
545 $nchunks = min( 7, $xlim - $xoff, $ylim - $yoff ) + 1;
546 list(
$lcs, $seps ) = $this->
diag( $xoff, $xlim, $yoff, $ylim, $nchunks );
552 while ( $yoff < $ylim ) {
553 $this->ychanged[$this->yind[$yoff++]] = 1;
555 while ( $xoff < $xlim ) {
556 $this->xchanged[$this->xind[$xoff++]] = 1;
562 while ( $pt2 = next( $seps ) ) {
563 $this->
compareSeq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] );
587 assert(
'count($lines) == count($changed)' );
589 $other_len = count( $other_changed );
603 while ( $j < $other_len && $other_changed[$j] ) {
607 while ( $i < $len && !
$changed[$i] ) {
608 assert(
'$j < $other_len && ! $other_changed[$j]' );
611 while ( $j < $other_len && $other_changed[$j] ) {
623 while ( ++$i < $len &&
$changed[$i] ) {
632 $runlength = $i - $start;
639 while ( $start > 0 &&
$lines[$start - 1] ==
$lines[$i - 1] ) {
642 while ( $start > 0 &&
$changed[$start - 1] ) {
646 while ( $other_changed[--$j] ) {
649 assert(
'$j >= 0 && !$other_changed[$j]' );
657 $corresponding = $j < $other_len ? $i : $len;
669 while ( $i < $len &&
$changed[$i] ) {
673 assert(
'$j < $other_len && ! $other_changed[$j]' );
675 if ( $j < $other_len && $other_changed[$j] ) {
677 while ( $j < $other_len && $other_changed[$j] ) {
682 }
while ( $runlength != $i - $start );
688 while ( $corresponding < $i ) {
692 while ( $other_changed[--$j] ) {
695 assert(
'$j >= 0 && !$other_changed[$j]' );
723 public function __construct( $from_lines, $to_lines ) {
725 $this->
edits = $eng->diff( $from_lines, $to_lines );
750 foreach ( $this->
edits as $edit ) {
751 $rev->edits[] = $edit->reverse();
763 foreach ( $this->
edits as $edit ) {
764 if ( $edit->type !=
'copy' ) {
779 public function lcs() {
781 foreach ( $this->
edits as $edit ) {
782 if ( $edit->type ==
'copy' ) {
783 $lcs += count( $edit->orig );
798 public function orig() {
801 foreach ( $this->
edits as $edit ) {
803 array_splice(
$lines, count(
$lines ), 0, $edit->orig );
821 foreach ( $this->
edits as $edit ) {
822 if ( $edit->closing ) {
823 array_splice(
$lines, count(
$lines ), 0, $edit->closing );
857 public function __construct( $from_lines, $to_lines,
858 $mapped_from_lines, $mapped_to_lines ) {
861 assert(
'count( $from_lines ) == count( $mapped_from_lines )' );
862 assert(
'count( $to_lines ) == count( $mapped_to_lines )' );
864 parent::__construct( $mapped_from_lines, $mapped_to_lines );
867 $editCount = count( $this->
edits );
868 for ( $i = 0; $i < $editCount; $i++ ) {
869 $orig = &$this->
edits[$i]->orig;
870 if ( is_array( $orig ) ) {
871 $orig = array_slice( $from_lines, $xi, count( $orig ) );
872 $xi += count( $orig );
875 $closing = &$this->
edits[$i]->closing;
876 if ( is_array( $closing ) ) {
877 $closing = array_slice( $to_lines, $yi, count( $closing ) );
878 $yi += count( $closing );
895 public $insClass =
' class="diffchange diffchange-inline"';
896 public $delClass =
' class="diffchange diffchange-inline"';
907 if ( $this->group !==
'' ) {
908 if ( $this->tag ==
'ins' ) {
909 $this->
line .=
"<ins{$this->insClass}>" .
910 htmlspecialchars( $this->group ) .
'</ins>';
911 } elseif ( $this->tag ==
'del' ) {
912 $this->
line .=
"<del{$this->delClass}>" .
913 htmlspecialchars( $this->group ) .
'</del>';
915 $this->
line .= htmlspecialchars( $this->group );
919 $this->tag = $new_tag;
927 if ( $this->
line !=
'' ) {
928 array_push( $this->lines, $this->
line );
930 # make empty lines visible by inserting an NBSP
931 array_push( $this->lines,
' ' );
941 if (
$tag != $this->tag ) {
945 foreach ( $words
as $word ) {
950 if ( $word[0] ==
"\n" ) {
952 $word = substr( $word, 1 );
954 assert(
'!strstr( $word, "\n" )' );
955 $this->group .= $word;
981 public function __construct( $orig_lines, $closing_lines ) {
984 list( $orig_words, $orig_stripped ) = $this->
split( $orig_lines );
985 list( $closing_words, $closing_stripped ) = $this->
split( $closing_lines );
987 parent::__construct( $orig_words, $closing_words,
988 $orig_stripped, $closing_stripped );
1001 $stripped =
array();
1004 # If the line is too long, just pretend the entire line is one big word
1005 # This prevents resource exhaustion problems
1012 if ( strlen(
$line ) > self::MAX_LINE_LENGTH ) {
1014 $stripped[] =
$line;
1017 if ( preg_match_all(
'/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1020 foreach ( $m[0]
as $word ) {
1023 foreach ( $m[1]
as $stripped_word ) {
1024 $stripped[] = $stripped_word;
1031 return array( $words, $stripped );
1037 public function orig() {
1041 foreach ( $this->
edits as $edit ) {
1042 if ( $edit->type ==
'copy' ) {
1044 } elseif ( $edit->orig ) {
1045 $orig->addWords( $edit->orig,
'del' );
1048 $lines = $orig->getLines();
1061 foreach ( $this->
edits as $edit ) {
1062 if ( $edit->type ==
'copy' ) {
1063 $closing->
addWords( $edit->closing );
1064 } elseif ( $edit->closing ) {
1065 $closing->addWords( $edit->closing,
'ins' );
1068 $lines = $closing->getLines();