19 define(
'USE_ASSERTS', function_exists(
'assert'));
    34         trigger_error(
'pure virtual', E_USER_ERROR);
    74         $this->closing = 
false;
    94         $this->closing = $lines;
   151     public const MAX_XREF_LENGTH = 10000;
   160     private array $seq = [];
   162     public function diff($from_lines, $to_lines)
   164         $fname = 
'_DiffEngine::diff';
   167         $n_from = 
sizeof($from_lines);
   168         $n_to = 
sizeof($to_lines);
   170         $this->xchanged = $this->ychanged = array();
   171         $this->xv = $this->yv = array();
   172         $this->xind = $this->yind = array();
   174         unset($this->in_seq);
   178         for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
   179             if ($from_lines[$skip] !== $to_lines[$skip]) {
   182             $this->xchanged[$skip] = $this->ychanged[$skip] = 
false;
   187         for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
   188             if ($from_lines[$xi] !== $to_lines[$yi]) {
   191             $this->xchanged[$xi] = $this->ychanged[$yi] = 
false;
   195         for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
   196             $xhash[$this->_line_hash($from_lines[$xi])] = 1;
   199         for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
   200             $line = $to_lines[$yi];
   201             if (($this->ychanged[$yi] = empty($xhash[$this->_line_hash($line)]))) {
   204             $yhash[$this->_line_hash($line)] = 1;
   208         for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
   209             $line = $from_lines[$xi];
   210             if (($this->xchanged[$xi] = empty($yhash[$this->_line_hash($line)]))) {
   218         $this->_compareseq(0, 
sizeof($this->xv), 0, 
sizeof($this->yv));
   221         $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
   222         $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
   227         while ($xi < $n_from || $yi < $n_to) {
   228             USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
   229             USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
   233             while ($xi < $n_from && $yi < $n_to
   234                     && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
   235                 $copy[] = $from_lines[$xi++];
   244             while ($xi < $n_from && $this->xchanged[$xi]) {
   245                 $delete[] = $from_lines[$xi++];
   249             while ($yi < $n_to && $this->ychanged[$yi]) {
   250                 $add[] = $to_lines[$yi++];
   253             if ($delete && $add) {
   270         if (strlen($line) > self::MAX_XREF_LENGTH) {
   294     public function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)
   296         $fname = 
'_DiffEngine::_diag';
   300         if ($xlim - $xoff > $ylim - $yoff) {
   304             list($xoff, $xlim, $yoff, $ylim)
   305             = array( $yoff, $ylim, $xoff, $xlim);
   309             for ($i = $ylim - 1; $i >= $yoff; $i--) {
   310                 $ymatches[$this->xv[$i]][] = $i;
   313             for ($i = $ylim - 1; $i >= $yoff; $i--) {
   314                 $ymatches[$this->yv[$i]][] = $i;
   319         $this->seq[0] = $yoff - 1;
   320         $this->in_seq = array();
   323         $numer = $xlim - $xoff + $nchunks - 1;
   325         for ($chunk = 0; $chunk < $nchunks; $chunk++) {
   328                 for ($i = 0; $i <= $this->lcs; $i++) {
   329                     $ymids[$i][$chunk - 1] = $this->seq[$i];
   333             $x1 = $xoff + (
int) (($numer + ($xlim - $xoff) * $chunk) / $nchunks);
   334             for (; $x < $x1; $x++) {
   335                 $line = $flip ? $this->yv[$x] : $this->xv[$x];
   336                 if (empty($ymatches[$line])) {
   339                 $matches = $ymatches[$line];
   341                 foreach ($matches as $junk => $y) {
   342                     if (empty($this->in_seq[$y])) {
   343                         $k = $this->_lcs_pos($y);
   345                         $ymids[$k] = $ymids[$k - 1];
   349                 foreach ($matches as $y) {
   350                     if ($y > $this->seq[$k - 1]) {
   354                         $this->in_seq[$this->seq[$k]] = 
false;
   356                         $this->in_seq[$y] = 1;
   357                     } elseif (empty($this->in_seq[$y])) {
   358                         $k = $this->_lcs_pos($y);
   360                         $ymids[$k] = $ymids[$k - 1];
   367         $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
   368         $ymid = $ymids[$this->lcs];
   369         for ($n = 0; $n < $nchunks - 1; $n++) {
   370             $x1 = $xoff + (
int) (($numer + ($xlim - $xoff) * $n) / $nchunks);
   372             $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
   374         $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
   377         return array($this->lcs, $seps);
   382         $fname = 
'_DiffEngine::_lcs_pos';
   386         if ($end == 0 || $ypos > $this->seq[$end]) {
   387             $this->seq[++$this->lcs] = $ypos;
   388             $this->in_seq[$ypos] = 1;
   394         while ($beg < $end) {
   395             $mid = (
int) (($beg + $end) / 2);
   396             if ($ypos > $this->seq[$mid]) {
   405         $this->in_seq[$this->seq[$end]] = 
false;
   406         $this->seq[$end] = $ypos;
   407         $this->in_seq[$ypos] = 1;
   425         $fname = 
'_DiffEngine::_compareseq';
   429         while ($xoff < $xlim && $yoff < $ylim
   430                && $this->xv[$xoff] == $this->yv[$yoff]) {
   436         while ($xlim > $xoff && $ylim > $yoff
   437                && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
   442         if ($xoff == $xlim || $yoff == $ylim) {
   448             $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
   450             = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
   456             while ($yoff < $ylim) {
   457                 $this->ychanged[$this->yind[$yoff++]] = 1;
   459             while ($xoff < $xlim) {
   460                 $this->xchanged[$this->xind[$xoff++]] = 1;
   466             while ($pt2 = next($seps)) {
   467                 $this->_compareseq($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
   488         $fname = 
'_DiffEngine::_shift_boundaries';
   493         USE_ASSERTS && assert(
sizeof($lines) == 
sizeof($changed));
   494         $len = 
sizeof($lines);
   495         $other_len = 
sizeof($other_changed);
   509             while ($j < $other_len && $other_changed[$j]) {
   513             while ($i < $len && !$changed[$i]) {
   514                 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
   517                 while ($j < $other_len && $other_changed[$j]) {
   529             while (++$i < $len && $changed[$i]) {
   537                 $runlength = $i - $start;
   544                 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
   545                     $changed[--$start] = 1;
   546                     $changed[--$i] = 
false;
   547                     while ($start > 0 && $changed[$start - 1]) {
   551                     while ($other_changed[--$j]) {
   553                     USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
   561                 $corresponding = $j < $other_len ? $i : $len;
   570                 while ($i < $len && $lines[$start] == $lines[$i]) {
   571                     $changed[$start++] = 
false;
   573                     while ($i < $len && $changed[$i]) {
   577                     USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
   579                     if ($j < $other_len && $other_changed[$j]) {
   581                         while ($j < $other_len && $other_changed[$j]) {
   586             } 
while ($runlength != $i - $start);
   592             while ($corresponding < $i) {
   593                 $changed[--$start] = 1;
   596                 while ($other_changed[--$j]) {
   598                 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
   626         $this->edits = $eng->diff($from_lines, $to_lines);
   643         $rev->edits = array();
   644         foreach ($this->edits as $edit) {
   645             $rev->edits[] = $edit->reverse();
   657         foreach ($this->edits as $edit) {
   658             if ($edit->type != 
'copy') {
   675         foreach ($this->edits as $edit) {
   676             if ($edit->type == 
'copy') {
   677                 $lcs += 
sizeof($edit->orig);
   695         foreach ($this->edits as $edit) {
   697                 array_splice($lines, 
sizeof($lines), 0, $edit->orig);
   715         foreach ($this->edits as $edit) {
   716             if ($edit->closing) {
   717                 array_splice($lines, 
sizeof($lines), 0, $edit->closing);
   728     public function _check($from_lines, $to_lines)
   730         $fname = 
'Diff::_check';
   732         if (serialize($from_lines) != serialize($this->orig())) {
   733             trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
   735         if (serialize($to_lines) != serialize($this->closing())) {
   736             trigger_error(
"Reconstructed closing doesn't match", E_USER_ERROR);
   740         if (serialize($to_lines) != serialize($rev->orig())) {
   741             trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
   743         if (serialize($from_lines) != serialize($rev->closing())) {
   744             trigger_error(
"Reversed closing doesn't match", E_USER_ERROR);
   749         foreach ($this->edits as $edit) {
   750             if ($prevtype == $edit->type) {
   751                 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
   753             $prevtype = $edit->type;
   757         trigger_error(
'Diff okay: LCS = ' . $lcs, E_USER_NOTICE);
   798         $fname = 
'MappedDiff::MappedDiff';
   801         assert(
sizeof($from_lines) == 
sizeof($mapped_from_lines));
   802         assert(
sizeof($to_lines) == 
sizeof($mapped_to_lines));
   807         for ($i = 0; $i < 
sizeof($this->edits); $i++) {
   808             $orig = &$this->edits[$i]->orig;
   809             if (is_array(
$orig)) {
   810                 $orig = array_slice($from_lines, $xi, 
sizeof(
$orig));
   811                 $xi += 
sizeof(
$orig);
   814             $closing = &$this->edits[$i]->closing;
   842     public $leading_context_lines = 0;
   850     public $trailing_context_lines = 0;
   860         $fname = 
'DiffFormatter::format';
   867         $nlead = $this->leading_context_lines;
   868         $ntrail = $this->trailing_context_lines;
   870         $this->_start_diff();
   872         foreach ($diff->edits as $edit) {
   873             if ($edit->type == 
'copy') {
   874                 if (is_array($block)) {
   875                     if (
sizeof($edit->orig) <= $nlead + $ntrail) {
   879                             $context = array_slice($edit->orig, 0, $ntrail);
   894                 if (!is_array($block)) {
   907                 $xi += 
sizeof($edit->orig);
   909             if ($edit->closing) {
   910                 $yi += 
sizeof($edit->closing);
   914         if (is_array($block)) {
   924         $end = $this->_end_diff();
   929     public function _block($xbeg, $xlen, $ybeg, $ylen, $edits)
   931         $fname = 
'DiffFormatter::_block';
   933         $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
   934         foreach ($edits as $edit) {
   935             if ($edit->type == 
'copy') {
   936                 $this->_context($edit->orig);
   937             } elseif ($edit->type == 
'add') {
   938                 $this->_added($edit->closing);
   939             } elseif ($edit->type == 
'delete') {
   940                 $this->_deleted($edit->orig);
   941             } elseif ($edit->type == 
'change') {
   942                 $this->_changed($edit->orig, $edit->closing);
   944                 trigger_error(
'Unknown edit type', E_USER_ERROR);
   958         $val = ob_get_contents();
   966             $xbeg .= 
"," . ($xbeg + $xlen - 1);
   969             $ybeg .= 
"," . ($ybeg + $ylen - 1);
   972         return $xbeg . ($xlen ? ($ylen ? 
'c' : 
'd') : 
'a') . $ybeg;
   984     public function _lines($lines, $prefix = 
' ')
   986         foreach ($lines as $line) {
   987             echo 
"$prefix $line\n";
   993         $this->_lines($lines);
   998         $this->_lines($lines, 
'>');
  1002         $this->_lines($lines, 
'<');
  1007         $this->_deleted(
$orig);
  1019 define(
'NBSP', 
' ');                       
  1035         $this->_lines = array();
  1043         if ($this->_group !== 
'') {
  1044             if ($this->_tag == 
'ins') {
  1045                 $this->_line .= 
'[ilDiffInsStart]' .
  1046                     htmlspecialchars($this->_group) . 
'[ilDiffInsEnd]';
  1047             } elseif ($this->_tag == 
'del') {
  1048                 $this->_line .= 
'[ilDiffDelStart]' .
  1049                     htmlspecialchars($this->_group) . 
'[ilDiffDelEnd]';
  1051                 $this->_line .= htmlspecialchars($this->_group);
  1055         $this->_tag = $new_tag;
  1060         $this->_flushGroup($new_tag);
  1061         if ($this->_line != 
'') {
  1062             array_push($this->_lines, $this->_line);
  1064             # make empty lines visible by inserting an NBSP  1065             array_push($this->_lines, 
NBSP);
  1072         if ($tag != $this->_tag) {
  1073             $this->_flushGroup($tag);
  1076         foreach ($words as $word) {
  1081             if ($word[0] == 
"\n") {
  1082                 $this->_flushLine($tag);
  1083                 $word = substr($word, 1);
  1085             assert(!strstr($word, 
"\n"));
  1086             $this->_group .= $word;
  1092         $this->_flushLine(
'~done');
  1093         return $this->_lines;
  1104     public const MAX_LINE_LENGTH = 10000;
  1108         $fname = 
'WordLevelDiff::WordLevelDiff';
  1111         list($orig_words, $orig_stripped) = $this->_split($orig_lines);
  1112         list($closing_words, $closing_stripped) = $this->_split($closing_lines);
  1125         $fname = 
'WordLevelDiff::_split';
  1129         $stripped = array();
  1131         foreach ($lines as $line) {
  1132             # If the line is too long, just pretend the entire line is one big word  1133             # This prevents resource exhaustion problems  1140             if (strlen($line) > self::MAX_LINE_LENGTH) {
  1142                 $stripped[] = $line;
  1146                     '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
  1150                     $words = array_merge($words, $m[0]);
  1151                     $stripped = array_merge($stripped, $m[1]);
  1156         return array($words, $stripped);
  1161         $fname = 
'WordLevelDiff::orig';
  1165         foreach ($this->edits as $edit) {
  1166             if ($edit->type == 
'copy') {
  1167                 $orig->addWords($edit->orig);
  1168             } elseif ($edit->orig) {
  1169                 $orig->addWords($edit->orig, 
'del');
  1172         $lines = 
$orig->getLines();
  1179         $fname = 
'WordLevelDiff::closing';
  1183         foreach ($this->edits as $edit) {
  1184             if ($edit->type == 
'copy') {
  1185                 $closing->addWords($edit->closing);
  1186             } elseif ($edit->closing) {
  1187                 $closing->addWords($edit->closing, 
'ins');
 
_line_hash($line)
Returns the whole line if it's small enough, or the MD5 hash otherwise. 
 
diff($from_lines, $to_lines)
 
closing()
Get the closing set of lines. 
 
__construct( $from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines)
Constructor. 
 
__construct($from_lines, $to_lines)
Constructor. 
 
_diag($xoff, $xlim, $yoff, $ylim, $nchunks)
 
reverse()
Compute reversed Diff. 
 
lcs()
Compute the length of the Longest Common Subsequence (LCS). 
 
orig()
Get the original set of lines. 
 
addWords($words, $tag='')
 
_compareseq($xoff, $xlim, $yoff, $ylim)
 
isEmpty()
Check for empty diff. 
 
_shift_boundaries($lines, &$changed, $other_changed)
 
__construct($orig_lines, $closing_lines)
 
_check($from_lines, $to_lines)
Check a Diff for validity. 
 
const NBSP
Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3. 
 
__construct($orig, $closing)
 
__construct($orig, $closing=false)