19define(
'USE_ASSERTS', function_exists(
'assert'));
71 $this->closing =
false;
91 $this->closing = $lines;
159 public function diff($from_lines, $to_lines)
161 $fname =
'_DiffEngine::diff';
164 $n_from =
sizeof($from_lines);
165 $n_to =
sizeof($to_lines);
167 $this->xchanged = $this->ychanged = array();
168 $this->xv = $this->yv = array();
169 $this->xind = $this->yind = array();
171 unset($this->in_seq);
175 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
176 if ($from_lines[$skip] !== $to_lines[$skip]) {
179 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
184 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
185 if ($from_lines[$xi] !== $to_lines[$yi]) {
188 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
192 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
193 $xhash[$this->
_line_hash($from_lines[$xi])] = 1;
196 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
197 $line = $to_lines[$yi];
198 if (($this->ychanged[$yi] = empty($xhash[$this->
_line_hash($line)]))) {
205 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
206 $line = $from_lines[$xi];
207 if (($this->xchanged[$xi] = empty($yhash[$this->
_line_hash($line)]))) {
215 $this->
_compareseq(0,
sizeof($this->xv), 0,
sizeof($this->yv));
224 while ($xi < $n_from || $yi < $n_to) {
225 USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
226 USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
230 while ($xi < $n_from && $yi < $n_to
231 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
232 $copy[] = $from_lines[$xi++];
241 while ($xi < $n_from && $this->xchanged[$xi]) {
242 $delete[] = $from_lines[$xi++];
246 while ($yi < $n_to && $this->ychanged[$yi]) {
247 $add[] = $to_lines[$yi++];
250 if ($delete && $add) {
267 if (strlen($line) > self::MAX_XREF_LENGTH) {
291 public function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)
293 $fname =
'_DiffEngine::_diag';
297 if ($xlim - $xoff > $ylim - $yoff) {
301 list($xoff, $xlim, $yoff, $ylim)
302 = array( $yoff, $ylim, $xoff, $xlim);
306 for ($i = $ylim - 1; $i >= $yoff; $i--) {
307 $ymatches[$this->xv[$i]][] = $i;
310 for ($i = $ylim - 1; $i >= $yoff; $i--) {
311 $ymatches[$this->yv[$i]][] = $i;
316 $this->seq[0] = $yoff - 1;
317 $this->in_seq = array();
320 $numer = $xlim - $xoff + $nchunks - 1;
322 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
326 $ymids[$i][$chunk - 1] = $this->seq[$i];
330 $x1 = $xoff + (
int) (($numer + ($xlim - $xoff) * $chunk) / $nchunks);
331 for (; $x < $x1; $x++) {
332 $line = $flip ? $this->yv[$x] : $this->xv[$x];
333 if (empty($ymatches[$line])) {
336 $matches = $ymatches[$line];
338 foreach ($matches as $junk => $y) {
339 if (empty($this->in_seq[$y])) {
342 $ymids[$k] = $ymids[$k - 1];
346 foreach ($matches as $y) {
347 if ($y > $this->seq[$k - 1]) {
351 $this->in_seq[$this->seq[$k]] =
false;
353 $this->in_seq[$y] = 1;
354 } elseif (empty($this->in_seq[$y])) {
357 $ymids[$k] = $ymids[$k - 1];
364 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
366 for ($n = 0; $n < $nchunks - 1; $n++) {
367 $x1 = $xoff + (
int) (($numer + ($xlim - $xoff) * $n) / $nchunks);
369 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
371 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
374 return array($this->lcs, $seps);
379 $fname =
'_DiffEngine::_lcs_pos';
383 if ($end == 0 || $ypos > $this->seq[$end]) {
385 $this->in_seq[$ypos] = 1;
391 while ($beg < $end) {
392 $mid = (
int) (($beg + $end) / 2);
393 if ($ypos > $this->seq[$mid]) {
402 $this->in_seq[$this->seq[$end]] =
false;
403 $this->seq[$end] = $ypos;
404 $this->in_seq[$ypos] = 1;
422 $fname =
'_DiffEngine::_compareseq';
426 while ($xoff < $xlim && $yoff < $ylim
427 && $this->xv[$xoff] == $this->yv[$yoff]) {
433 while ($xlim > $xoff && $ylim > $yoff
434 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
439 if ($xoff == $xlim || $yoff == $ylim) {
445 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
447 = $this->
_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
453 while ($yoff < $ylim) {
454 $this->ychanged[$this->yind[$yoff++]] = 1;
456 while ($xoff < $xlim) {
457 $this->xchanged[$this->xind[$xoff++]] = 1;
463 while ($pt2 =
next($seps)) {
464 $this->
_compareseq($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
485 $fname =
'_DiffEngine::_shift_boundaries';
490 USE_ASSERTS && assert(
sizeof($lines) ==
sizeof($changed));
491 $len =
sizeof($lines);
492 $other_len =
sizeof($other_changed);
506 while ($j < $other_len && $other_changed[$j]) {
510 while ($i < $len && !$changed[$i]) {
511 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
514 while ($j < $other_len && $other_changed[$j]) {
526 while (++$i < $len && $changed[$i]) {
534 $runlength = $i - $start;
541 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
542 $changed[--$start] = 1;
543 $changed[--$i] =
false;
544 while ($start > 0 && $changed[$start - 1]) {
548 while ($other_changed[--$j]) {
550 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
558 $corresponding = $j < $other_len ? $i : $len;
567 while ($i < $len && $lines[$start] == $lines[$i]) {
568 $changed[$start++] =
false;
570 while ($i < $len && $changed[$i]) {
574 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
576 if ($j < $other_len && $other_changed[$j]) {
578 while ($j < $other_len && $other_changed[$j]) {
583 }
while ($runlength != $i - $start);
589 while ($corresponding < $i) {
590 $changed[--$start] = 1;
593 while ($other_changed[--$j]) {
595 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
623 $this->edits = $eng->diff($from_lines, $to_lines);
640 $rev->edits = array();
641 foreach ($this->edits as $edit) {
642 $rev->edits[] = $edit->reverse();
654 foreach ($this->edits as $edit) {
655 if ($edit->type !=
'copy') {
672 foreach ($this->edits as $edit) {
673 if ($edit->type ==
'copy') {
674 $lcs +=
sizeof($edit->orig);
692 foreach ($this->edits as $edit) {
694 array_splice($lines,
sizeof($lines), 0, $edit->orig);
712 foreach ($this->edits as $edit) {
713 if ($edit->closing) {
714 array_splice($lines,
sizeof($lines), 0, $edit->closing);
725 public function _check($from_lines, $to_lines)
727 $fname =
'Diff::_check';
729 if (serialize($from_lines) != serialize($this->
orig())) {
730 throw new \LogicException(
"Reconstructed original doesn't match");
732 if (serialize($to_lines) != serialize($this->
closing())) {
733 throw new \LogicException(
"Reconstructed closing doesn't match");
737 if (serialize($to_lines) != serialize($rev->orig())) {
738 throw new \LogicException(
"Reversed original doesn't match");
740 if (serialize($from_lines) != serialize($rev->closing())) {
741 throw new \LogicException(
"Reversed closing doesn't match");
746 foreach ($this->edits as $edit) {
747 if ($prevtype == $edit->type) {
748 throw new \RuntimeException(
"Edit sequence is non-optimal");
750 $prevtype = $edit->type;
754 trigger_error(
'Diff okay: LCS = ' . $lcs, E_USER_NOTICE);
795 $fname =
'MappedDiff::MappedDiff';
798 assert(
sizeof($from_lines) ==
sizeof($mapped_from_lines));
799 assert(
sizeof($to_lines) ==
sizeof($mapped_to_lines));
805 $orig = &$this->edits[$i]->orig;
806 if (is_array($orig)) {
807 $orig = array_slice($from_lines, $xi,
sizeof($orig));
808 $xi +=
sizeof($orig);
811 $closing = &$this->edits[$i]->closing;
812 if (is_array($closing)) {
813 $closing = array_slice($to_lines, $yi,
sizeof($closing));
814 $yi +=
sizeof($closing);
857 $fname =
'DiffFormatter::format';
869 foreach ($diff->edits as $edit) {
870 if ($edit->type ==
'copy') {
871 if (is_array($block)) {
872 if (
sizeof($edit->orig) <= $nlead + $ntrail) {
876 $context = array_slice($edit->orig, 0, $ntrail);
891 if (!is_array($block)) {
904 $xi +=
sizeof($edit->orig);
906 if ($edit->closing) {
907 $yi +=
sizeof($edit->closing);
911 if (is_array($block)) {
926 public function _block($xbeg, $xlen, $ybeg, $ylen, $edits)
928 $fname =
'DiffFormatter::_block';
931 foreach ($edits as $edit) {
932 if ($edit->type ==
'copy') {
934 } elseif ($edit->type ==
'add') {
935 $this->
_added($edit->closing);
936 } elseif ($edit->type ==
'delete') {
938 } elseif ($edit->type ==
'change') {
939 $this->
_changed($edit->orig, $edit->closing);
941 throw new \RuntimeException(
'Unknown edit type');
955 $val = ob_get_contents();
963 $xbeg .=
"," . ($xbeg + $xlen - 1);
966 $ybeg .=
"," . ($ybeg + $ylen - 1);
969 return $xbeg . ($xlen ? ($ylen ?
'c' :
'd') :
'a') . $ybeg;
981 public function _lines($lines, $prefix =
' ')
983 foreach ($lines as $line) {
984 echo
"$prefix $line\n";
995 $this->
_lines($lines,
'>');
999 $this->
_lines($lines,
'<');
1016define(
'NBSP',
' ');
1032 $this->_lines = array();
1040 if ($this->_group !==
'') {
1041 if ($this->_tag ==
'ins') {
1042 $this->_line .=
'[ilDiffInsStart]' .
1043 htmlspecialchars($this->_group) .
'[ilDiffInsEnd]';
1044 } elseif ($this->_tag ==
'del') {
1045 $this->_line .=
'[ilDiffDelStart]' .
1046 htmlspecialchars($this->_group) .
'[ilDiffDelEnd]';
1048 $this->_line .= htmlspecialchars($this->_group);
1052 $this->_tag = $new_tag;
1058 if ($this->_line !=
'') {
1059 array_push($this->_lines, $this->_line);
1061 # make empty lines visible by inserting an NBSP
1062 array_push($this->_lines,
NBSP);
1069 if ($tag != $this->_tag) {
1073 foreach ($words as $word) {
1078 if ($word[0] ==
"\n") {
1080 $word = substr($word, 1);
1082 assert(!strstr($word,
"\n"));
1083 $this->_group .= $word;
1105 $fname =
'WordLevelDiff::WordLevelDiff';
1108 list($orig_words, $orig_stripped) = $this->
_split($orig_lines);
1109 list($closing_words, $closing_stripped) = $this->
_split($closing_lines);
1122 $fname =
'WordLevelDiff::_split';
1126 $stripped = array();
1128 foreach ($lines as $line) {
1129 # If the line is too long, just pretend the entire line is one big word
1130 # This prevents resource exhaustion problems
1137 if (strlen($line) > self::MAX_LINE_LENGTH) {
1139 $stripped[] = $line;
1143 '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1147 $words = array_merge($words, $m[0]);
1148 $stripped = array_merge($stripped, $m[1]);
1153 return array($words, $stripped);
1158 $fname =
'WordLevelDiff::orig';
1162 foreach ($this->edits as $edit) {
1163 if ($edit->type ==
'copy') {
1164 $orig->addWords($edit->orig);
1165 } elseif ($edit->orig) {
1166 $orig->addWords($edit->orig,
'del');
1169 $lines = $orig->getLines();
1176 $fname =
'WordLevelDiff::closing';
1180 foreach ($this->edits as $edit) {
1181 if ($edit->type ==
'copy') {
1182 $closing->addWords($edit->closing);
1183 } elseif ($edit->closing) {
1184 $closing->addWords($edit->closing,
'ins');
1187 $lines = $closing->getLines();
reverse()
Compute reversed Diff.
orig()
Get the original set of lines.
__construct($from_lines, $to_lines)
Constructor.
isEmpty()
Check for empty diff.
lcs()
Compute the length of the Longest Common Subsequence (LCS).
closing()
Get the closing set of lines.
_check($from_lines, $to_lines)
Check a Diff for validity.
__construct( $from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines)
Constructor.
__construct($orig_lines, $closing_lines)
Constructor.
closing()
Get the closing set of lines.
orig()
Get the original set of lines.
const NBSP
Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3.
_diag($xoff, $xlim, $yoff, $ylim, $nchunks)
diff($from_lines, $to_lines)
_shift_boundaries($lines, &$changed, $other_changed)
_line_hash($line)
Returns the whole line if it's small enough, or the MD5 hash otherwise.
_compareseq($xoff, $xlim, $yoff, $ylim)
__construct($orig, $closing)
__construct($orig, $closing=false)
addWords($words, $tag='')
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc