1f3f0262cSandi<?php 215fae107Sandi/** 315fae107Sandi * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3) 415fae107Sandi * 515fae107Sandi * Additions by Axel Boldt for MediaWiki 615fae107Sandi * 715fae107Sandi * @copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org> 815fae107Sandi * @license You may copy this code freely under the conditions of the GPL. 915fae107Sandi */ 10f3f0262cSandidefine('USE_ASSERTS', function_exists('assert')); 11f3f0262cSandi 12f3f0262cSandiclass _DiffOp { 13f3f0262cSandi var $type; 14f3f0262cSandi var $orig; 15f3f0262cSandi var $closing; 16f3f0262cSandi 1742ea7f44SGerrit Uitslag /** 1842ea7f44SGerrit Uitslag * @return _DiffOp 1942ea7f44SGerrit Uitslag */ 20f3f0262cSandi function reverse() { 21f3f0262cSandi trigger_error("pure virtual", E_USER_ERROR); 22f3f0262cSandi } 23f3f0262cSandi 24f3f0262cSandi function norig() { 25f5b78785SAndreas Gohr return $this->orig ? count($this->orig) : 0; 26f3f0262cSandi } 27f3f0262cSandi 28f3f0262cSandi function nclosing() { 29f5b78785SAndreas Gohr return $this->closing ? count($this->closing) : 0; 30f3f0262cSandi } 31f3f0262cSandi} 32f3f0262cSandi 33f3f0262cSandiclass _DiffOp_Copy extends _DiffOp { 34f3f0262cSandi var $type = 'copy'; 35988c1340SPiyush Mishra 36988c1340SPiyush Mishra function __construct($orig, $closing = false) { 37f3f0262cSandi if (!is_array($closing)) 38f3f0262cSandi $closing = $orig; 39f3f0262cSandi $this->orig = $orig; 40f3f0262cSandi $this->closing = $closing; 41f3f0262cSandi } 42f3f0262cSandi 43f3f0262cSandi function reverse() { 44f3f0262cSandi return new _DiffOp_Copy($this->closing, $this->orig); 45f3f0262cSandi } 46f3f0262cSandi} 47f3f0262cSandi 48f3f0262cSandiclass _DiffOp_Delete extends _DiffOp { 49f3f0262cSandi var $type = 'delete'; 50988c1340SPiyush Mishra 51988c1340SPiyush Mishra function __construct($lines) { 52f3f0262cSandi $this->orig = $lines; 53f3f0262cSandi $this->closing = false; 54f3f0262cSandi } 55f3f0262cSandi 56f3f0262cSandi function reverse() { 57f3f0262cSandi return new _DiffOp_Add($this->orig); 58f3f0262cSandi } 59f3f0262cSandi} 60f3f0262cSandi 61f3f0262cSandiclass _DiffOp_Add extends _DiffOp { 62f3f0262cSandi var $type = 'add'; 63988c1340SPiyush Mishra 64988c1340SPiyush Mishra function __construct($lines) { 65f3f0262cSandi $this->closing = $lines; 66f3f0262cSandi $this->orig = false; 67f3f0262cSandi } 68f3f0262cSandi 69f3f0262cSandi function reverse() { 70f3f0262cSandi return new _DiffOp_Delete($this->closing); 71f3f0262cSandi } 72f3f0262cSandi} 73f3f0262cSandi 74f3f0262cSandiclass _DiffOp_Change extends _DiffOp { 75f3f0262cSandi var $type = 'change'; 76988c1340SPiyush Mishra 77988c1340SPiyush Mishra function __construct($orig, $closing) { 78f3f0262cSandi $this->orig = $orig; 79f3f0262cSandi $this->closing = $closing; 80f3f0262cSandi } 81f3f0262cSandi 82f3f0262cSandi function reverse() { 83f3f0262cSandi return new _DiffOp_Change($this->closing, $this->orig); 84f3f0262cSandi } 85f3f0262cSandi} 86f3f0262cSandi 87f3f0262cSandi 88f3f0262cSandi/** 89f3f0262cSandi * Class used internally by Diff to actually compute the diffs. 90f3f0262cSandi * 91f3f0262cSandi * The algorithm used here is mostly lifted from the perl module 92f3f0262cSandi * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: 93f3f0262cSandi * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip 94f3f0262cSandi * 95f3f0262cSandi * More ideas are taken from: 96f3f0262cSandi * http://www.ics.uci.edu/~eppstein/161/960229.html 97f3f0262cSandi * 98f3f0262cSandi * Some ideas are (and a bit of code) are from from analyze.c, from GNU 99f3f0262cSandi * diffutils-2.7, which can be found at: 100f3f0262cSandi * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz 101f3f0262cSandi * 102f3f0262cSandi * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations) 103f3f0262cSandi * are my own. 104f3f0262cSandi * 105f3f0262cSandi * @author Geoffrey T. Dairiki 106f3f0262cSandi * @access private 107f3f0262cSandi */ 108f5b78785SAndreas Gohrclass _DiffEngine { 109f5b78785SAndreas Gohr 11042ea7f44SGerrit Uitslag var $xchanged = array(); 11142ea7f44SGerrit Uitslag var $ychanged = array(); 11242ea7f44SGerrit Uitslag var $xv = array(); 11342ea7f44SGerrit Uitslag var $yv = array(); 11442ea7f44SGerrit Uitslag var $xind = array(); 11542ea7f44SGerrit Uitslag var $yind = array(); 11642ea7f44SGerrit Uitslag var $seq; 11742ea7f44SGerrit Uitslag var $in_seq; 11842ea7f44SGerrit Uitslag var $lcs; 11942ea7f44SGerrit Uitslag 12042ea7f44SGerrit Uitslag /** 12142ea7f44SGerrit Uitslag * @param array $from_lines 12242ea7f44SGerrit Uitslag * @param array $to_lines 12342ea7f44SGerrit Uitslag * @return _DiffOp[] 12442ea7f44SGerrit Uitslag */ 125f3f0262cSandi function diff($from_lines, $to_lines) { 126f5b78785SAndreas Gohr $n_from = count($from_lines); 127f5b78785SAndreas Gohr $n_to = count($to_lines); 128f3f0262cSandi 129f3f0262cSandi $this->xchanged = $this->ychanged = array(); 130f3f0262cSandi $this->xv = $this->yv = array(); 131f3f0262cSandi $this->xind = $this->yind = array(); 132f3f0262cSandi unset($this->seq); 133f3f0262cSandi unset($this->in_seq); 134f3f0262cSandi unset($this->lcs); 135f3f0262cSandi 136f3f0262cSandi // Skip leading common lines. 137f3f0262cSandi for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { 138f3f0262cSandi if ($from_lines[$skip] != $to_lines[$skip]) 139f3f0262cSandi break; 140f3f0262cSandi $this->xchanged[$skip] = $this->ychanged[$skip] = false; 141f3f0262cSandi } 142f3f0262cSandi // Skip trailing common lines. 143f5b78785SAndreas Gohr $xi = $n_from; 144f5b78785SAndreas Gohr $yi = $n_to; 145f3f0262cSandi for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { 146f3f0262cSandi if ($from_lines[$xi] != $to_lines[$yi]) 147f3f0262cSandi break; 148f3f0262cSandi $this->xchanged[$xi] = $this->ychanged[$yi] = false; 149f3f0262cSandi } 150f3f0262cSandi 151f3f0262cSandi // Ignore lines which do not exist in both files. 152f3f0262cSandi for ($xi = $skip; $xi < $n_from - $endskip; $xi++) 153f3f0262cSandi $xhash[$from_lines[$xi]] = 1; 154f3f0262cSandi for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { 155f3f0262cSandi $line = $to_lines[$yi]; 156f3f0262cSandi if (($this->ychanged[$yi] = empty($xhash[$line]))) 157f3f0262cSandi continue; 158f3f0262cSandi $yhash[$line] = 1; 159f3f0262cSandi $this->yv[] = $line; 160f3f0262cSandi $this->yind[] = $yi; 161f3f0262cSandi } 162f3f0262cSandi for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { 163f3f0262cSandi $line = $from_lines[$xi]; 164f3f0262cSandi if (($this->xchanged[$xi] = empty($yhash[$line]))) 165f3f0262cSandi continue; 166f3f0262cSandi $this->xv[] = $line; 167f3f0262cSandi $this->xind[] = $xi; 168f3f0262cSandi } 169f3f0262cSandi 170f3f0262cSandi // Find the LCS. 171f5b78785SAndreas Gohr $this->_compareseq(0, count($this->xv), 0, count($this->yv)); 172f3f0262cSandi 173f3f0262cSandi // Merge edits when possible 174f3f0262cSandi $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); 175f3f0262cSandi $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); 176f3f0262cSandi 177f3f0262cSandi // Compute the edit operations. 178f3f0262cSandi $edits = array(); 179f3f0262cSandi $xi = $yi = 0; 180f3f0262cSandi while ($xi < $n_from || $yi < $n_to) { 181f3f0262cSandi USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); 182f3f0262cSandi USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]); 183f3f0262cSandi 184f3f0262cSandi // Skip matching "snake". 185f3f0262cSandi $copy = array(); 1867deca91bSRobin Getz while ($xi < $n_from && $yi < $n_to && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { 187f3f0262cSandi $copy[] = $from_lines[$xi++]; 188f3f0262cSandi ++$yi; 189f3f0262cSandi } 190f3f0262cSandi if ($copy) 191f3f0262cSandi $edits[] = new _DiffOp_Copy($copy); 192f3f0262cSandi 193f3f0262cSandi // Find deletes & adds. 194f3f0262cSandi $delete = array(); 195f3f0262cSandi while ($xi < $n_from && $this->xchanged[$xi]) 196f3f0262cSandi $delete[] = $from_lines[$xi++]; 197f3f0262cSandi 198f3f0262cSandi $add = array(); 199f3f0262cSandi while ($yi < $n_to && $this->ychanged[$yi]) 200f3f0262cSandi $add[] = $to_lines[$yi++]; 201f3f0262cSandi 202f3f0262cSandi if ($delete && $add) 203f3f0262cSandi $edits[] = new _DiffOp_Change($delete, $add); 204f3f0262cSandi elseif ($delete) 205f3f0262cSandi $edits[] = new _DiffOp_Delete($delete); 206f3f0262cSandi elseif ($add) 207f3f0262cSandi $edits[] = new _DiffOp_Add($add); 208f3f0262cSandi } 209f3f0262cSandi return $edits; 210f3f0262cSandi } 211f3f0262cSandi 212f3f0262cSandi 21315fae107Sandi /** 21415fae107Sandi * Divide the Largest Common Subsequence (LCS) of the sequences 215f3f0262cSandi * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally 216f3f0262cSandi * sized segments. 217f3f0262cSandi * 218f3f0262cSandi * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an 219f3f0262cSandi * array of NCHUNKS+1 (X, Y) indexes giving the diving points between 220f3f0262cSandi * sub sequences. The first sub-sequence is contained in [X0, X1), 221f3f0262cSandi * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note 222f3f0262cSandi * that (X0, Y0) == (XOFF, YOFF) and 223f3f0262cSandi * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). 224f3f0262cSandi * 225f3f0262cSandi * This function assumes that the first lines of the specified portions 226f3f0262cSandi * of the two files do not match, and likewise that the last lines do not 227f3f0262cSandi * match. The caller must trim matching lines from the beginning and end 228f3f0262cSandi * of the portions it is going to specify. 229f50a239bSTakamura * 230f50a239bSTakamura * @param integer $xoff 231f50a239bSTakamura * @param integer $xlim 232f50a239bSTakamura * @param integer $yoff 233f50a239bSTakamura * @param integer $ylim 234f50a239bSTakamura * @param integer $nchunks 235f50a239bSTakamura * 236f50a239bSTakamura * @return array 237f3f0262cSandi */ 238f3f0262cSandi function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) { 239f3f0262cSandi $flip = false; 240f3f0262cSandi 241f3f0262cSandi if ($xlim - $xoff > $ylim - $yoff) { 242f3f0262cSandi // Things seems faster (I'm not sure I understand why) 243f3f0262cSandi // when the shortest sequence in X. 244f3f0262cSandi $flip = true; 2457deca91bSRobin Getz list ($xoff, $xlim, $yoff, $ylim) = array($yoff, $ylim, $xoff, $xlim); 246f3f0262cSandi } 247f3f0262cSandi 248f3f0262cSandi if ($flip) 249f3f0262cSandi for ($i = $ylim - 1; $i >= $yoff; $i--) 250f3f0262cSandi $ymatches[$this->xv[$i]][] = $i; 251f3f0262cSandi else 252f3f0262cSandi for ($i = $ylim - 1; $i >= $yoff; $i--) 253f3f0262cSandi $ymatches[$this->yv[$i]][] = $i; 254f3f0262cSandi 255f3f0262cSandi $this->lcs = 0; 256f3f0262cSandi $this->seq[0]= $yoff - 1; 257f3f0262cSandi $this->in_seq = array(); 258f3f0262cSandi $ymids[0] = array(); 259f3f0262cSandi 260f3f0262cSandi $numer = $xlim - $xoff + $nchunks - 1; 261f3f0262cSandi $x = $xoff; 262f3f0262cSandi for ($chunk = 0; $chunk < $nchunks; $chunk++) { 263f3f0262cSandi if ($chunk > 0) 264f3f0262cSandi for ($i = 0; $i <= $this->lcs; $i++) 265f3f0262cSandi $ymids[$i][$chunk-1] = $this->seq[$i]; 266f3f0262cSandi 267f3f0262cSandi $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); 268f3f0262cSandi for ( ; $x < $x1; $x++) { 269f3f0262cSandi $line = $flip ? $this->yv[$x] : $this->xv[$x]; 270f3f0262cSandi if (empty($ymatches[$line])) 271f3f0262cSandi continue; 272f3f0262cSandi $matches = $ymatches[$line]; 273*dd5c3e2bSPhy $switch = false; 274*dd5c3e2bSPhy foreach ($matches as $y) { 275*dd5c3e2bSPhy if(!$switch) { 276f3f0262cSandi if (empty($this->in_seq[$y])) { 277f3f0262cSandi $k = $this->_lcs_pos($y); 278f3f0262cSandi USE_ASSERTS && assert($k > 0); 279f3f0262cSandi $ymids[$k] = $ymids[$k-1]; 280*dd5c3e2bSPhy $switch = true; 281f3f0262cSandi } 282*dd5c3e2bSPhy }else{ 283f3f0262cSandi if ($y > $this->seq[$k-1]) { 284f3f0262cSandi USE_ASSERTS && assert($y < $this->seq[$k]); 285f3f0262cSandi // Optimization: this is a common case: 286f3f0262cSandi // next match is just replacing previous match. 287f3f0262cSandi $this->in_seq[$this->seq[$k]] = false; 288f3f0262cSandi $this->seq[$k] = $y; 289f3f0262cSandi $this->in_seq[$y] = 1; 290f3f0262cSandi } 291f3f0262cSandi else if (empty($this->in_seq[$y])) { 292f3f0262cSandi $k = $this->_lcs_pos($y); 293f3f0262cSandi USE_ASSERTS && assert($k > 0); 294f3f0262cSandi $ymids[$k] = $ymids[$k-1]; 295f3f0262cSandi } 296f3f0262cSandi } 297f3f0262cSandi } 298f3f0262cSandi } 299*dd5c3e2bSPhy } 300f3f0262cSandi 301f3f0262cSandi $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); 302f3f0262cSandi $ymid = $ymids[$this->lcs]; 303f3f0262cSandi for ($n = 0; $n < $nchunks - 1; $n++) { 304f3f0262cSandi $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); 305f3f0262cSandi $y1 = $ymid[$n] + 1; 306f3f0262cSandi $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); 307f3f0262cSandi } 308f3f0262cSandi $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); 309f3f0262cSandi 310f3f0262cSandi return array($this->lcs, $seps); 311f3f0262cSandi } 312f3f0262cSandi 313f3f0262cSandi function _lcs_pos($ypos) { 314f3f0262cSandi $end = $this->lcs; 315f3f0262cSandi if ($end == 0 || $ypos > $this->seq[$end]) { 316f3f0262cSandi $this->seq[++$this->lcs] = $ypos; 317f3f0262cSandi $this->in_seq[$ypos] = 1; 318f3f0262cSandi return $this->lcs; 319f3f0262cSandi } 320f3f0262cSandi 321f3f0262cSandi $beg = 1; 322f3f0262cSandi while ($beg < $end) { 323f3f0262cSandi $mid = (int)(($beg + $end) / 2); 324f3f0262cSandi if ($ypos > $this->seq[$mid]) 325f3f0262cSandi $beg = $mid + 1; 326f3f0262cSandi else 327f3f0262cSandi $end = $mid; 328f3f0262cSandi } 329f3f0262cSandi 330f3f0262cSandi USE_ASSERTS && assert($ypos != $this->seq[$end]); 331f3f0262cSandi 332f3f0262cSandi $this->in_seq[$this->seq[$end]] = false; 333f3f0262cSandi $this->seq[$end] = $ypos; 334f3f0262cSandi $this->in_seq[$ypos] = 1; 335f3f0262cSandi return $end; 336f3f0262cSandi } 337f3f0262cSandi 33815fae107Sandi /** 33915fae107Sandi * Find LCS of two sequences. 340f3f0262cSandi * 341f3f0262cSandi * The results are recorded in the vectors $this->{x,y}changed[], by 342f3f0262cSandi * storing a 1 in the element for each line that is an insertion 343f3f0262cSandi * or deletion (ie. is not in the LCS). 344f3f0262cSandi * 345f3f0262cSandi * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. 346f3f0262cSandi * 347f3f0262cSandi * Note that XLIM, YLIM are exclusive bounds. 348f3f0262cSandi * All line numbers are origin-0 and discarded lines are not counted. 349f50a239bSTakamura * 350f50a239bSTakamura * @param integer $xoff 351f50a239bSTakamura * @param integer $xlim 352f50a239bSTakamura * @param integer $yoff 353f50a239bSTakamura * @param integer $ylim 354f3f0262cSandi */ 355f3f0262cSandi function _compareseq($xoff, $xlim, $yoff, $ylim) { 356f3f0262cSandi // Slide down the bottom initial diagonal. 3577deca91bSRobin Getz while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) { 358f3f0262cSandi ++$xoff; 359f3f0262cSandi ++$yoff; 360f3f0262cSandi } 361f3f0262cSandi 362f3f0262cSandi // Slide up the top initial diagonal. 3637deca91bSRobin Getz while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { 364f3f0262cSandi --$xlim; 365f3f0262cSandi --$ylim; 366f3f0262cSandi } 367f3f0262cSandi 368f3f0262cSandi if ($xoff == $xlim || $yoff == $ylim) 369f3f0262cSandi $lcs = 0; 370f3f0262cSandi else { 371f3f0262cSandi // This is ad hoc but seems to work well. 372f3f0262cSandi //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); 373f3f0262cSandi //$nchunks = max(2,min(8,(int)$nchunks)); 374f3f0262cSandi $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; 375f3f0262cSandi list ($lcs, $seps) 376f3f0262cSandi = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); 377f3f0262cSandi } 378f3f0262cSandi 379f3f0262cSandi if ($lcs == 0) { 380f3f0262cSandi // X and Y sequences have no common subsequence: 381f3f0262cSandi // mark all changed. 382f3f0262cSandi while ($yoff < $ylim) 383f3f0262cSandi $this->ychanged[$this->yind[$yoff++]] = 1; 384f3f0262cSandi while ($xoff < $xlim) 385f3f0262cSandi $this->xchanged[$this->xind[$xoff++]] = 1; 386f3f0262cSandi } 387f3f0262cSandi else { 388f3f0262cSandi // Use the partitions to split this problem into subproblems. 389f3f0262cSandi reset($seps); 390f3f0262cSandi $pt1 = $seps[0]; 391f3f0262cSandi while ($pt2 = next($seps)) { 392f3f0262cSandi $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); 393f3f0262cSandi $pt1 = $pt2; 394f3f0262cSandi } 395f3f0262cSandi } 396f3f0262cSandi } 397f3f0262cSandi 39815fae107Sandi /** 39915fae107Sandi * Adjust inserts/deletes of identical lines to join changes 400f3f0262cSandi * as much as possible. 401f3f0262cSandi * 402f3f0262cSandi * We do something when a run of changed lines include a 403f3f0262cSandi * line at one end and has an excluded, identical line at the other. 404f3f0262cSandi * We are free to choose which identical line is included. 405f3f0262cSandi * `compareseq' usually chooses the one at the beginning, 406f3f0262cSandi * but usually it is cleaner to consider the following identical line 407f3f0262cSandi * to be the "change". 408f3f0262cSandi * 409f3f0262cSandi * This is extracted verbatim from analyze.c (GNU diffutils-2.7). 410f50a239bSTakamura * 411f50a239bSTakamura * @param array $lines 412f50a239bSTakamura * @param array $changed 413f50a239bSTakamura * @param array $other_changed 414f3f0262cSandi */ 415f3f0262cSandi function _shift_boundaries($lines, &$changed, $other_changed) { 416f3f0262cSandi $i = 0; 417f3f0262cSandi $j = 0; 418f3f0262cSandi 419f5b78785SAndreas Gohr USE_ASSERTS && assert('count($lines) == count($changed)'); 420f5b78785SAndreas Gohr $len = count($lines); 421f5b78785SAndreas Gohr $other_len = count($other_changed); 422f3f0262cSandi 423f3f0262cSandi while (1) { 424f3f0262cSandi /* 425f3f0262cSandi * Scan forwards to find beginning of another run of changes. 426f3f0262cSandi * Also keep track of the corresponding point in the other file. 427f3f0262cSandi * 428f3f0262cSandi * Throughout this code, $i and $j are adjusted together so that 429f3f0262cSandi * the first $i elements of $changed and the first $j elements 430f3f0262cSandi * of $other_changed both contain the same number of zeros 431f3f0262cSandi * (unchanged lines). 432f3f0262cSandi * Furthermore, $j is always kept so that $j == $other_len or 433f3f0262cSandi * $other_changed[$j] == false. 434f3f0262cSandi */ 435f3f0262cSandi while ($j < $other_len && $other_changed[$j]) 436f3f0262cSandi $j++; 437f3f0262cSandi 438f3f0262cSandi while ($i < $len && ! $changed[$i]) { 439f3f0262cSandi USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); 440f5b78785SAndreas Gohr $i++; 441f5b78785SAndreas Gohr $j++; 442f3f0262cSandi while ($j < $other_len && $other_changed[$j]) 443f3f0262cSandi $j++; 444f3f0262cSandi } 445f3f0262cSandi 446f3f0262cSandi if ($i == $len) 447f3f0262cSandi break; 448f3f0262cSandi 449f3f0262cSandi $start = $i; 450f3f0262cSandi 451f3f0262cSandi // Find the end of this run of changes. 452f3f0262cSandi while (++$i < $len && $changed[$i]) 453f3f0262cSandi continue; 454f3f0262cSandi 455f3f0262cSandi do { 456f3f0262cSandi /* 457f3f0262cSandi * Record the length of this run of changes, so that 458f3f0262cSandi * we can later determine whether the run has grown. 459f3f0262cSandi */ 460f3f0262cSandi $runlength = $i - $start; 461f3f0262cSandi 462f3f0262cSandi /* 463f3f0262cSandi * Move the changed region back, so long as the 464f3f0262cSandi * previous unchanged line matches the last changed one. 465f3f0262cSandi * This merges with previous changed regions. 466f3f0262cSandi */ 467f3f0262cSandi while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { 468f3f0262cSandi $changed[--$start] = 1; 469f3f0262cSandi $changed[--$i] = false; 470f3f0262cSandi while ($start > 0 && $changed[$start - 1]) 471f3f0262cSandi $start--; 472f3f0262cSandi USE_ASSERTS && assert('$j > 0'); 473f3f0262cSandi while ($other_changed[--$j]) 474f3f0262cSandi continue; 475f3f0262cSandi USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); 476f3f0262cSandi } 477f3f0262cSandi 478f3f0262cSandi /* 479f3f0262cSandi * Set CORRESPONDING to the end of the changed run, at the last 480f3f0262cSandi * point where it corresponds to a changed run in the other file. 481f3f0262cSandi * CORRESPONDING == LEN means no such point has been found. 482f3f0262cSandi */ 483f3f0262cSandi $corresponding = $j < $other_len ? $i : $len; 484f3f0262cSandi 485f3f0262cSandi /* 486f3f0262cSandi * Move the changed region forward, so long as the 487f3f0262cSandi * first changed line matches the following unchanged one. 488f3f0262cSandi * This merges with following changed regions. 489f3f0262cSandi * Do this second, so that if there are no merges, 490f3f0262cSandi * the changed region is moved forward as far as possible. 491f3f0262cSandi */ 492f3f0262cSandi while ($i < $len && $lines[$start] == $lines[$i]) { 493f3f0262cSandi $changed[$start++] = false; 494f3f0262cSandi $changed[$i++] = 1; 495f3f0262cSandi while ($i < $len && $changed[$i]) 496f3f0262cSandi $i++; 497f3f0262cSandi 498f3f0262cSandi USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); 499f3f0262cSandi $j++; 500f3f0262cSandi if ($j < $other_len && $other_changed[$j]) { 501f3f0262cSandi $corresponding = $i; 502f3f0262cSandi while ($j < $other_len && $other_changed[$j]) 503f3f0262cSandi $j++; 504f3f0262cSandi } 505f3f0262cSandi } 506f3f0262cSandi } while ($runlength != $i - $start); 507f3f0262cSandi 508f3f0262cSandi /* 509f3f0262cSandi * If possible, move the fully-merged run of changes 510f3f0262cSandi * back to a corresponding run in the other file. 511f3f0262cSandi */ 512f3f0262cSandi while ($corresponding < $i) { 513f3f0262cSandi $changed[--$start] = 1; 514f3f0262cSandi $changed[--$i] = 0; 515f3f0262cSandi USE_ASSERTS && assert('$j > 0'); 516f3f0262cSandi while ($other_changed[--$j]) 517f3f0262cSandi continue; 518f3f0262cSandi USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); 519f3f0262cSandi } 520f3f0262cSandi } 521f3f0262cSandi } 522f3f0262cSandi} 523f3f0262cSandi 524f3f0262cSandi/** 525f3f0262cSandi * Class representing a 'diff' between two sequences of strings. 526f3f0262cSandi */ 527f5b78785SAndreas Gohrclass Diff { 528f5b78785SAndreas Gohr 529f3f0262cSandi var $edits; 530f3f0262cSandi 531f3f0262cSandi /** 532f3f0262cSandi * Constructor. 533f3f0262cSandi * Computes diff between sequences of strings. 534f3f0262cSandi * 53542ea7f44SGerrit Uitslag * @param array $from_lines An array of strings. 536f3f0262cSandi * (Typically these are lines from a file.) 53742ea7f44SGerrit Uitslag * @param array $to_lines An array of strings. 538f3f0262cSandi */ 539988c1340SPiyush Mishra function __construct($from_lines, $to_lines) { 540f3f0262cSandi $eng = new _DiffEngine; 541f3f0262cSandi $this->edits = $eng->diff($from_lines, $to_lines); 542f3f0262cSandi //$this->_check($from_lines, $to_lines); 543f3f0262cSandi } 544f3f0262cSandi 545f3f0262cSandi /** 546f3f0262cSandi * Compute reversed Diff. 547f3f0262cSandi * 548f3f0262cSandi * SYNOPSIS: 549f3f0262cSandi * 550f3f0262cSandi * $diff = new Diff($lines1, $lines2); 551f3f0262cSandi * $rev = $diff->reverse(); 55242ea7f44SGerrit Uitslag * 55342ea7f44SGerrit Uitslag * @return Diff A Diff object representing the inverse of the 554f3f0262cSandi * original diff. 555f3f0262cSandi */ 556f3f0262cSandi function reverse() { 557f3f0262cSandi $rev = $this; 558f3f0262cSandi $rev->edits = array(); 559f3f0262cSandi foreach ($this->edits as $edit) { 560f3f0262cSandi $rev->edits[] = $edit->reverse(); 561f3f0262cSandi } 562f3f0262cSandi return $rev; 563f3f0262cSandi } 564f3f0262cSandi 565f3f0262cSandi /** 566f3f0262cSandi * Check for empty diff. 567f3f0262cSandi * 568f3f0262cSandi * @return bool True iff two sequences were identical. 569f3f0262cSandi */ 570f3f0262cSandi function isEmpty() { 571f3f0262cSandi foreach ($this->edits as $edit) { 572f3f0262cSandi if ($edit->type != 'copy') 573f3f0262cSandi return false; 574f3f0262cSandi } 575f3f0262cSandi return true; 576f3f0262cSandi } 577f3f0262cSandi 578f3f0262cSandi /** 579f3f0262cSandi * Compute the length of the Longest Common Subsequence (LCS). 580f3f0262cSandi * 581f3f0262cSandi * This is mostly for diagnostic purposed. 582f3f0262cSandi * 583f3f0262cSandi * @return int The length of the LCS. 584f3f0262cSandi */ 585f3f0262cSandi function lcs() { 586f3f0262cSandi $lcs = 0; 587f3f0262cSandi foreach ($this->edits as $edit) { 588f3f0262cSandi if ($edit->type == 'copy') 589f5b78785SAndreas Gohr $lcs += count($edit->orig); 590f3f0262cSandi } 591f3f0262cSandi return $lcs; 592f3f0262cSandi } 593f3f0262cSandi 594f3f0262cSandi /** 595f3f0262cSandi * Get the original set of lines. 596f3f0262cSandi * 597f3f0262cSandi * This reconstructs the $from_lines parameter passed to the 598f3f0262cSandi * constructor. 599f3f0262cSandi * 600f3f0262cSandi * @return array The original sequence of strings. 601f3f0262cSandi */ 602f3f0262cSandi function orig() { 603f3f0262cSandi $lines = array(); 604f3f0262cSandi 605f3f0262cSandi foreach ($this->edits as $edit) { 606f3f0262cSandi if ($edit->orig) 607f5b78785SAndreas Gohr array_splice($lines, count($lines), 0, $edit->orig); 608f3f0262cSandi } 609f3f0262cSandi return $lines; 610f3f0262cSandi } 611f3f0262cSandi 612f3f0262cSandi /** 613f3f0262cSandi * Get the closing set of lines. 614f3f0262cSandi * 615f3f0262cSandi * This reconstructs the $to_lines parameter passed to the 616f3f0262cSandi * constructor. 617f3f0262cSandi * 618f3f0262cSandi * @return array The sequence of strings. 619f3f0262cSandi */ 620f3f0262cSandi function closing() { 621f3f0262cSandi $lines = array(); 622f3f0262cSandi 623f3f0262cSandi foreach ($this->edits as $edit) { 624f3f0262cSandi if ($edit->closing) 625f5b78785SAndreas Gohr array_splice($lines, count($lines), 0, $edit->closing); 626f3f0262cSandi } 627f3f0262cSandi return $lines; 628f3f0262cSandi } 629f3f0262cSandi 630f3f0262cSandi /** 631f3f0262cSandi * Check a Diff for validity. 632f3f0262cSandi * 633f3f0262cSandi * This is here only for debugging purposes. 634f50a239bSTakamura * 635f50a239bSTakamura * @param mixed $from_lines 636f50a239bSTakamura * @param mixed $to_lines 637f3f0262cSandi */ 638f3f0262cSandi function _check($from_lines, $to_lines) { 639f3f0262cSandi if (serialize($from_lines) != serialize($this->orig())) 640f3f0262cSandi trigger_error("Reconstructed original doesn't match", E_USER_ERROR); 641f3f0262cSandi if (serialize($to_lines) != serialize($this->closing())) 642f3f0262cSandi trigger_error("Reconstructed closing doesn't match", E_USER_ERROR); 643f3f0262cSandi 644f3f0262cSandi $rev = $this->reverse(); 645f3f0262cSandi if (serialize($to_lines) != serialize($rev->orig())) 646f3f0262cSandi trigger_error("Reversed original doesn't match", E_USER_ERROR); 647f3f0262cSandi if (serialize($from_lines) != serialize($rev->closing())) 648f3f0262cSandi trigger_error("Reversed closing doesn't match", E_USER_ERROR); 649f3f0262cSandi 650f3f0262cSandi $prevtype = 'none'; 651f3f0262cSandi foreach ($this->edits as $edit) { 652f3f0262cSandi if ($prevtype == $edit->type) 653f3f0262cSandi trigger_error("Edit sequence is non-optimal", E_USER_ERROR); 654f3f0262cSandi $prevtype = $edit->type; 655f3f0262cSandi } 656f3f0262cSandi 657f3f0262cSandi $lcs = $this->lcs(); 658f3f0262cSandi trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); 659f3f0262cSandi } 660f3f0262cSandi} 661f3f0262cSandi 662f3f0262cSandi/** 663f3f0262cSandi * FIXME: bad name. 664f3f0262cSandi */ 665f5b78785SAndreas Gohrclass MappedDiff extends Diff { 666f3f0262cSandi /** 667f3f0262cSandi * Constructor. 668f3f0262cSandi * 669f3f0262cSandi * Computes diff between sequences of strings. 670f3f0262cSandi * 671f3f0262cSandi * This can be used to compute things like 672f3f0262cSandi * case-insensitve diffs, or diffs which ignore 673f3f0262cSandi * changes in white-space. 674f3f0262cSandi * 67542ea7f44SGerrit Uitslag * @param string[] $from_lines An array of strings. 676f3f0262cSandi * (Typically these are lines from a file.) 677f3f0262cSandi * 67842ea7f44SGerrit Uitslag * @param string[] $to_lines An array of strings. 679f3f0262cSandi * 68042ea7f44SGerrit Uitslag * @param string[] $mapped_from_lines This array should 681f3f0262cSandi * have the same size number of elements as $from_lines. 682f3f0262cSandi * The elements in $mapped_from_lines and 683f3f0262cSandi * $mapped_to_lines are what is actually compared 684f3f0262cSandi * when computing the diff. 685f3f0262cSandi * 68642ea7f44SGerrit Uitslag * @param string[] $mapped_to_lines This array should 687f3f0262cSandi * have the same number of elements as $to_lines. 688f3f0262cSandi */ 689988c1340SPiyush Mishra function __construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) { 690f3f0262cSandi 691f5b78785SAndreas Gohr assert(count($from_lines) == count($mapped_from_lines)); 692f5b78785SAndreas Gohr assert(count($to_lines) == count($mapped_to_lines)); 693f3f0262cSandi 694988c1340SPiyush Mishra parent::__construct($mapped_from_lines, $mapped_to_lines); 695f3f0262cSandi 696f3f0262cSandi $xi = $yi = 0; 697f5b78785SAndreas Gohr $ecnt = count($this->edits); 698f5b78785SAndreas Gohr for ($i = 0; $i < $ecnt; $i++) { 699f3f0262cSandi $orig = &$this->edits[$i]->orig; 700f3f0262cSandi if (is_array($orig)) { 701f5b78785SAndreas Gohr $orig = array_slice($from_lines, $xi, count($orig)); 702f5b78785SAndreas Gohr $xi += count($orig); 703f3f0262cSandi } 704f3f0262cSandi 705f3f0262cSandi $closing = &$this->edits[$i]->closing; 706f3f0262cSandi if (is_array($closing)) { 707f5b78785SAndreas Gohr $closing = array_slice($to_lines, $yi, count($closing)); 708f5b78785SAndreas Gohr $yi += count($closing); 709f3f0262cSandi } 710f3f0262cSandi } 711f3f0262cSandi } 712f3f0262cSandi} 713f3f0262cSandi 714f3f0262cSandi/** 715f3f0262cSandi * A class to format Diffs 716f3f0262cSandi * 717f3f0262cSandi * This class formats the diff in classic diff format. 718f3f0262cSandi * It is intended that this class be customized via inheritance, 719f3f0262cSandi * to obtain fancier outputs. 720f3f0262cSandi */ 721f5b78785SAndreas Gohrclass DiffFormatter { 722f3f0262cSandi /** 723f3f0262cSandi * Number of leading context "lines" to preserve. 724f3f0262cSandi * 725f3f0262cSandi * This should be left at zero for this class, but subclasses 726f3f0262cSandi * may want to set this to other values. 727f3f0262cSandi */ 728f3f0262cSandi var $leading_context_lines = 0; 729f3f0262cSandi 730f3f0262cSandi /** 731f3f0262cSandi * Number of trailing context "lines" to preserve. 732f3f0262cSandi * 733f3f0262cSandi * This should be left at zero for this class, but subclasses 734f3f0262cSandi * may want to set this to other values. 735f3f0262cSandi */ 736f3f0262cSandi var $trailing_context_lines = 0; 737f3f0262cSandi 738f3f0262cSandi /** 739f3f0262cSandi * Format a diff. 740f3f0262cSandi * 74142ea7f44SGerrit Uitslag * @param Diff $diff A Diff object. 742f3f0262cSandi * @return string The formatted output. 743f3f0262cSandi */ 744f3f0262cSandi function format($diff) { 745f3f0262cSandi 746f3f0262cSandi $xi = $yi = 1; 74742ea7f44SGerrit Uitslag $x0 = $y0 = 0; 748f3f0262cSandi $block = false; 749f3f0262cSandi $context = array(); 750f3f0262cSandi 751f3f0262cSandi $nlead = $this->leading_context_lines; 752f3f0262cSandi $ntrail = $this->trailing_context_lines; 753f3f0262cSandi 754f3f0262cSandi $this->_start_diff(); 755f3f0262cSandi 756f3f0262cSandi foreach ($diff->edits as $edit) { 757f3f0262cSandi if ($edit->type == 'copy') { 758f3f0262cSandi if (is_array($block)) { 759f5b78785SAndreas Gohr if (count($edit->orig) <= $nlead + $ntrail) { 760f3f0262cSandi $block[] = $edit; 761f3f0262cSandi } 762f3f0262cSandi else{ 763f3f0262cSandi if ($ntrail) { 764f3f0262cSandi $context = array_slice($edit->orig, 0, $ntrail); 765f3f0262cSandi $block[] = new _DiffOp_Copy($context); 766f3f0262cSandi } 7677deca91bSRobin Getz $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block); 768f3f0262cSandi $block = false; 769f3f0262cSandi } 770f3f0262cSandi } 771f3f0262cSandi $context = $edit->orig; 772f3f0262cSandi } 773f3f0262cSandi else { 774f3f0262cSandi if (! is_array($block)) { 775f5b78785SAndreas Gohr $context = array_slice($context, count($context) - $nlead); 776f5b78785SAndreas Gohr $x0 = $xi - count($context); 777f5b78785SAndreas Gohr $y0 = $yi - count($context); 778f3f0262cSandi $block = array(); 779f3f0262cSandi if ($context) 780f3f0262cSandi $block[] = new _DiffOp_Copy($context); 781f3f0262cSandi } 782f3f0262cSandi $block[] = $edit; 783f3f0262cSandi } 784f3f0262cSandi 785f3f0262cSandi if ($edit->orig) 786f5b78785SAndreas Gohr $xi += count($edit->orig); 787f3f0262cSandi if ($edit->closing) 788f5b78785SAndreas Gohr $yi += count($edit->closing); 789f3f0262cSandi } 790f3f0262cSandi 791f3f0262cSandi if (is_array($block)) 7927deca91bSRobin Getz $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block); 793f3f0262cSandi 794f3f0262cSandi return $this->_end_diff(); 795f3f0262cSandi } 796f3f0262cSandi 79742ea7f44SGerrit Uitslag /** 79842ea7f44SGerrit Uitslag * @param int $xbeg 79942ea7f44SGerrit Uitslag * @param int $xlen 80042ea7f44SGerrit Uitslag * @param int $ybeg 80142ea7f44SGerrit Uitslag * @param int $ylen 80242ea7f44SGerrit Uitslag * @param array $edits 80342ea7f44SGerrit Uitslag */ 804f3f0262cSandi function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { 805f3f0262cSandi $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); 806f3f0262cSandi foreach ($edits as $edit) { 807f3f0262cSandi if ($edit->type == 'copy') 808f3f0262cSandi $this->_context($edit->orig); 809f3f0262cSandi elseif ($edit->type == 'add') 810f3f0262cSandi $this->_added($edit->closing); 811f3f0262cSandi elseif ($edit->type == 'delete') 812f3f0262cSandi $this->_deleted($edit->orig); 813f3f0262cSandi elseif ($edit->type == 'change') 814f3f0262cSandi $this->_changed($edit->orig, $edit->closing); 815f3f0262cSandi else 816f3f0262cSandi trigger_error("Unknown edit type", E_USER_ERROR); 817f3f0262cSandi } 818f3f0262cSandi $this->_end_block(); 819f3f0262cSandi } 820f3f0262cSandi 821f3f0262cSandi function _start_diff() { 822f3f0262cSandi ob_start(); 823f3f0262cSandi } 824f3f0262cSandi 825f3f0262cSandi function _end_diff() { 826f3f0262cSandi $val = ob_get_contents(); 827f3f0262cSandi ob_end_clean(); 828f3f0262cSandi return $val; 829f3f0262cSandi } 830f3f0262cSandi 83142ea7f44SGerrit Uitslag /** 83242ea7f44SGerrit Uitslag * @param int $xbeg 83342ea7f44SGerrit Uitslag * @param int $xlen 83442ea7f44SGerrit Uitslag * @param int $ybeg 83542ea7f44SGerrit Uitslag * @param int $ylen 83642ea7f44SGerrit Uitslag * @return string 83742ea7f44SGerrit Uitslag */ 838f3f0262cSandi function _block_header($xbeg, $xlen, $ybeg, $ylen) { 839f3f0262cSandi if ($xlen > 1) 840f3f0262cSandi $xbeg .= "," . ($xbeg + $xlen - 1); 841f3f0262cSandi if ($ylen > 1) 842f3f0262cSandi $ybeg .= "," . ($ybeg + $ylen - 1); 843f3f0262cSandi 844f3f0262cSandi return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; 845f3f0262cSandi } 846f3f0262cSandi 84742ea7f44SGerrit Uitslag /** 84842ea7f44SGerrit Uitslag * @param string $header 84942ea7f44SGerrit Uitslag */ 850f3f0262cSandi function _start_block($header) { 851f3f0262cSandi echo $header; 852f3f0262cSandi } 853f3f0262cSandi 854f3f0262cSandi function _end_block() { 855f3f0262cSandi } 856f3f0262cSandi 857f3f0262cSandi function _lines($lines, $prefix = ' ') { 858f3f0262cSandi foreach ($lines as $line) 85960056e69SChristopher Smith echo "$prefix ".$this->_escape($line)."\n"; 860f3f0262cSandi } 861f3f0262cSandi 862f3f0262cSandi function _context($lines) { 863f3f0262cSandi $this->_lines($lines); 864f3f0262cSandi } 865f3f0262cSandi 866f3f0262cSandi function _added($lines) { 867f3f0262cSandi $this->_lines($lines, ">"); 868f3f0262cSandi } 869f3f0262cSandi function _deleted($lines) { 870f3f0262cSandi $this->_lines($lines, "<"); 871f3f0262cSandi } 872f3f0262cSandi 873f3f0262cSandi function _changed($orig, $closing) { 874f3f0262cSandi $this->_deleted($orig); 875f3f0262cSandi echo "---\n"; 876f3f0262cSandi $this->_added($closing); 877f3f0262cSandi } 87860056e69SChristopher Smith 879bfd197d2ShArpanet /** 880bfd197d2ShArpanet * Escape string 881bfd197d2ShArpanet * 882bfd197d2ShArpanet * Override this method within other formatters if escaping required. 883bfd197d2ShArpanet * Base class requires $str to be returned WITHOUT escaping. 884bfd197d2ShArpanet * 885bfd197d2ShArpanet * @param $str string Text string to escape 886bfd197d2ShArpanet * @return string The escaped string. 887bfd197d2ShArpanet */ 88860056e69SChristopher Smith function _escape($str){ 88960056e69SChristopher Smith return $str; 89060056e69SChristopher Smith } 891f3f0262cSandi} 892f3f0262cSandi 89347a906eaSAndreas Gohr/** 89447a906eaSAndreas Gohr * Utilityclass for styling HTML formatted diffs 89547a906eaSAndreas Gohr * 89647a906eaSAndreas Gohr * Depends on global var $DIFF_INLINESTYLES, if true some minimal predefined 89747a906eaSAndreas Gohr * inline styles are used. Useful for HTML mails and RSS feeds 89847a906eaSAndreas Gohr * 89947a906eaSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 90047a906eaSAndreas Gohr */ 90147a906eaSAndreas Gohrclass HTMLDiff { 90247a906eaSAndreas Gohr /** 90347a906eaSAndreas Gohr * Holds the style names and basic CSS 90447a906eaSAndreas Gohr */ 90547a906eaSAndreas Gohr static public $styles = array( 90647a906eaSAndreas Gohr 'diff-addedline' => 'background-color: #ddffdd;', 90747a906eaSAndreas Gohr 'diff-deletedline' => 'background-color: #ffdddd;', 90847a906eaSAndreas Gohr 'diff-context' => 'background-color: #f5f5f5;', 90947a906eaSAndreas Gohr 'diff-mark' => 'color: #ff0000;', 91047a906eaSAndreas Gohr ); 91147a906eaSAndreas Gohr 91247a906eaSAndreas Gohr /** 91347a906eaSAndreas Gohr * Return a class or style parameter 914f50a239bSTakamura * 915f50a239bSTakamura * @param string $classname 916f50a239bSTakamura * 917f50a239bSTakamura * @return string 91847a906eaSAndreas Gohr */ 91947a906eaSAndreas Gohr static function css($classname){ 92047a906eaSAndreas Gohr global $DIFF_INLINESTYLES; 92147a906eaSAndreas Gohr 92247a906eaSAndreas Gohr if($DIFF_INLINESTYLES){ 92347a906eaSAndreas Gohr if(!isset(self::$styles[$classname])) return ''; 92447a906eaSAndreas Gohr return 'style="'.self::$styles[$classname].'"'; 92547a906eaSAndreas Gohr }else{ 92647a906eaSAndreas Gohr return 'class="'.$classname.'"'; 92747a906eaSAndreas Gohr } 92847a906eaSAndreas Gohr } 92947a906eaSAndreas Gohr} 930f3f0262cSandi 931f3f0262cSandi/** 932f3f0262cSandi * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 933f3f0262cSandi * 934f3f0262cSandi */ 935f3f0262cSandi 9369b3a5b24Schrisdefine('NBSP', "\xC2\xA0"); // utf-8 non-breaking space. 937f3f0262cSandi 938f3f0262cSandiclass _HWLDF_WordAccumulator { 939988c1340SPiyush Mishra 940988c1340SPiyush Mishra function __construct() { 941f3f0262cSandi $this->_lines = array(); 942f3f0262cSandi $this->_line = ''; 943f3f0262cSandi $this->_group = ''; 944f3f0262cSandi $this->_tag = ''; 945f3f0262cSandi } 946f3f0262cSandi 947f3f0262cSandi function _flushGroup($new_tag) { 948f3f0262cSandi if ($this->_group !== '') { 949f3f0262cSandi if ($this->_tag == 'mark') 95060056e69SChristopher Smith $this->_line .= '<strong '.HTMLDiff::css('diff-mark').'>'.$this->_escape($this->_group).'</strong>'; 951812bb04eSRobin Getz elseif ($this->_tag == 'add') 95260056e69SChristopher Smith $this->_line .= '<span '.HTMLDiff::css('diff-addedline').'>'.$this->_escape($this->_group).'</span>'; 953812bb04eSRobin Getz elseif ($this->_tag == 'del') 95460056e69SChristopher Smith $this->_line .= '<span '.HTMLDiff::css('diff-deletedline').'><del>'.$this->_escape($this->_group).'</del></span>'; 955f3f0262cSandi else 95660056e69SChristopher Smith $this->_line .= $this->_escape($this->_group); 957f3f0262cSandi } 958f3f0262cSandi $this->_group = ''; 959f3f0262cSandi $this->_tag = $new_tag; 960f3f0262cSandi } 961f3f0262cSandi 96242ea7f44SGerrit Uitslag /** 96342ea7f44SGerrit Uitslag * @param string $new_tag 96442ea7f44SGerrit Uitslag */ 965f3f0262cSandi function _flushLine($new_tag) { 966f3f0262cSandi $this->_flushGroup($new_tag); 967f3f0262cSandi if ($this->_line != '') 968f3f0262cSandi $this->_lines[] = $this->_line; 969f3f0262cSandi $this->_line = ''; 970f3f0262cSandi } 971f3f0262cSandi 972f3f0262cSandi function addWords($words, $tag = '') { 973f3f0262cSandi if ($tag != $this->_tag) 974f3f0262cSandi $this->_flushGroup($tag); 975f3f0262cSandi 976f3f0262cSandi foreach ($words as $word) { 977f3f0262cSandi // new-line should only come as first char of word. 978f3f0262cSandi if ($word == '') 979f3f0262cSandi continue; 980f3f0262cSandi if ($word[0] == "\n") { 981f3f0262cSandi $this->_group .= NBSP; 982f3f0262cSandi $this->_flushLine($tag); 983f3f0262cSandi $word = substr($word, 1); 984f3f0262cSandi } 985f3f0262cSandi assert(!strstr($word, "\n")); 986f3f0262cSandi $this->_group .= $word; 987f3f0262cSandi } 988f3f0262cSandi } 989f3f0262cSandi 990f3f0262cSandi function getLines() { 991f3f0262cSandi $this->_flushLine('~done'); 992f3f0262cSandi return $this->_lines; 993f3f0262cSandi } 99460056e69SChristopher Smith 99560056e69SChristopher Smith function _escape($str){ 99660056e69SChristopher Smith return hsc($str); 99760056e69SChristopher Smith } 998f3f0262cSandi} 999f3f0262cSandi 1000f5b78785SAndreas Gohrclass WordLevelDiff extends MappedDiff { 1001f5b78785SAndreas Gohr 1002988c1340SPiyush Mishra function __construct($orig_lines, $closing_lines) { 1003f3f0262cSandi list ($orig_words, $orig_stripped) = $this->_split($orig_lines); 1004f3f0262cSandi list ($closing_words, $closing_stripped) = $this->_split($closing_lines); 1005f3f0262cSandi 1006988c1340SPiyush Mishra parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped); 1007f3f0262cSandi } 1008f3f0262cSandi 1009f3f0262cSandi function _split($lines) { 10105e1ee188SXin LI if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu', 10117deca91bSRobin Getz implode("\n", $lines), $m)) { 1012f3f0262cSandi return array(array(''), array('')); 1013f3f0262cSandi } 1014f3f0262cSandi return array($m[0], $m[1]); 1015f3f0262cSandi } 1016f3f0262cSandi 1017f3f0262cSandi function orig() { 1018f3f0262cSandi $orig = new _HWLDF_WordAccumulator; 1019f3f0262cSandi 1020f3f0262cSandi foreach ($this->edits as $edit) { 1021f3f0262cSandi if ($edit->type == 'copy') 1022f3f0262cSandi $orig->addWords($edit->orig); 1023f3f0262cSandi elseif ($edit->orig) 1024f3f0262cSandi $orig->addWords($edit->orig, 'mark'); 1025f3f0262cSandi } 1026f3f0262cSandi return $orig->getLines(); 1027f3f0262cSandi } 1028f3f0262cSandi 1029f3f0262cSandi function closing() { 1030f3f0262cSandi $closing = new _HWLDF_WordAccumulator; 1031f3f0262cSandi 1032f3f0262cSandi foreach ($this->edits as $edit) { 1033f3f0262cSandi if ($edit->type == 'copy') 1034f3f0262cSandi $closing->addWords($edit->closing); 1035f3f0262cSandi elseif ($edit->closing) 1036f3f0262cSandi $closing->addWords($edit->closing, 'mark'); 1037f3f0262cSandi } 1038f3f0262cSandi return $closing->getLines(); 1039f3f0262cSandi } 1040f3f0262cSandi} 1041f3f0262cSandi 1042812bb04eSRobin Getzclass InlineWordLevelDiff extends MappedDiff { 1043812bb04eSRobin Getz 1044988c1340SPiyush Mishra function __construct($orig_lines, $closing_lines) { 1045812bb04eSRobin Getz list ($orig_words, $orig_stripped) = $this->_split($orig_lines); 1046812bb04eSRobin Getz list ($closing_words, $closing_stripped) = $this->_split($closing_lines); 1047812bb04eSRobin Getz 1048988c1340SPiyush Mishra parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped); 1049812bb04eSRobin Getz } 1050812bb04eSRobin Getz 1051812bb04eSRobin Getz function _split($lines) { 1052c5982caaSDanny Lin if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu', 1053812bb04eSRobin Getz implode("\n", $lines), $m)) { 1054812bb04eSRobin Getz return array(array(''), array('')); 1055812bb04eSRobin Getz } 1056812bb04eSRobin Getz return array($m[0], $m[1]); 1057812bb04eSRobin Getz } 1058812bb04eSRobin Getz 1059812bb04eSRobin Getz function inline() { 1060812bb04eSRobin Getz $orig = new _HWLDF_WordAccumulator; 1061812bb04eSRobin Getz foreach ($this->edits as $edit) { 1062812bb04eSRobin Getz if ($edit->type == 'copy') 10634f2305cbSAdrian Lang $orig->addWords($edit->closing); 1064812bb04eSRobin Getz elseif ($edit->type == 'change'){ 1065812bb04eSRobin Getz $orig->addWords($edit->orig, 'del'); 1066812bb04eSRobin Getz $orig->addWords($edit->closing, 'add'); 1067812bb04eSRobin Getz } elseif ($edit->type == 'delete') 1068812bb04eSRobin Getz $orig->addWords($edit->orig, 'del'); 1069812bb04eSRobin Getz elseif ($edit->type == 'add') 1070812bb04eSRobin Getz $orig->addWords($edit->closing, 'add'); 1071812bb04eSRobin Getz elseif ($edit->orig) 1072812bb04eSRobin Getz $orig->addWords($edit->orig, 'del'); 1073812bb04eSRobin Getz } 1074812bb04eSRobin Getz return $orig->getLines(); 1075812bb04eSRobin Getz } 1076812bb04eSRobin Getz} 1077812bb04eSRobin Getz 1078f3f0262cSandi/** 1079f3f0262cSandi * "Unified" diff formatter. 1080f3f0262cSandi * 1081f3f0262cSandi * This class formats the diff in classic "unified diff" format. 1082df9752e9SChristopher Smith * 1083df9752e9SChristopher Smith * NOTE: output is plain text and unsafe for use in HTML without escaping. 1084f3f0262cSandi */ 1085f5b78785SAndreas Gohrclass UnifiedDiffFormatter extends DiffFormatter { 1086f5b78785SAndreas Gohr 1087988c1340SPiyush Mishra function __construct($context_lines = 4) { 1088f3f0262cSandi $this->leading_context_lines = $context_lines; 1089f3f0262cSandi $this->trailing_context_lines = $context_lines; 1090f3f0262cSandi } 1091f3f0262cSandi 1092f3f0262cSandi function _block_header($xbeg, $xlen, $ybeg, $ylen) { 1093f3f0262cSandi if ($xlen != 1) 1094f3f0262cSandi $xbeg .= "," . $xlen; 1095f3f0262cSandi if ($ylen != 1) 1096f3f0262cSandi $ybeg .= "," . $ylen; 1097f3f0262cSandi return "@@ -$xbeg +$ybeg @@\n"; 1098f3f0262cSandi } 1099f3f0262cSandi 1100f3f0262cSandi function _added($lines) { 1101f3f0262cSandi $this->_lines($lines, "+"); 1102f3f0262cSandi } 1103f3f0262cSandi function _deleted($lines) { 1104f3f0262cSandi $this->_lines($lines, "-"); 1105f3f0262cSandi } 1106f3f0262cSandi function _changed($orig, $final) { 1107f3f0262cSandi $this->_deleted($orig); 1108f3f0262cSandi $this->_added($final); 1109f3f0262cSandi } 1110f3f0262cSandi} 1111f3f0262cSandi 1112f3f0262cSandi/** 1113f3f0262cSandi * Wikipedia Table style diff formatter. 1114f3f0262cSandi * 1115f3f0262cSandi */ 1116f5b78785SAndreas Gohrclass TableDiffFormatter extends DiffFormatter { 11173a4ea35cSChristopher Smith var $colspan = 2; 1118f5b78785SAndreas Gohr 1119988c1340SPiyush Mishra function __construct() { 1120f3f0262cSandi $this->leading_context_lines = 2; 1121f3f0262cSandi $this->trailing_context_lines = 2; 1122f3f0262cSandi } 1123f3f0262cSandi 112442ea7f44SGerrit Uitslag /** 112542ea7f44SGerrit Uitslag * @param Diff $diff 112642ea7f44SGerrit Uitslag * @return string 112742ea7f44SGerrit Uitslag */ 11282d880650SAdrian Lang function format($diff) { 11292d880650SAdrian Lang // Preserve whitespaces by converting some to non-breaking spaces. 11302d880650SAdrian Lang // Do not convert all of them to allow word-wrap. 11312d880650SAdrian Lang $val = parent::format($diff); 1132e260f93bSAnika Henke $val = str_replace(' ','  ', $val); 1133e260f93bSAnika Henke $val = preg_replace('/ (?=<)|(?<=[ >]) /', ' ', $val); 11342d880650SAdrian Lang return $val; 11352d880650SAdrian Lang } 11362d880650SAdrian Lang 1137f3f0262cSandi function _pre($text){ 1138f3f0262cSandi $text = htmlspecialchars($text); 1139f3f0262cSandi return $text; 1140f3f0262cSandi } 1141f3f0262cSandi 1142f3f0262cSandi function _block_header($xbeg, $xlen, $ybeg, $ylen) { 1143f3f0262cSandi global $lang; 1144f3f0262cSandi $l1 = $lang['line'].' '.$xbeg; 1145f3f0262cSandi $l2 = $lang['line'].' '.$ybeg; 11463a4ea35cSChristopher Smith $r = '<tr><td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l1.":</td>\n". 11473a4ea35cSChristopher Smith '<td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l2.":</td>\n". 11485048c277SAnika Henke "</tr>\n"; 1149f3f0262cSandi return $r; 1150f3f0262cSandi } 1151f3f0262cSandi 1152f3f0262cSandi function _start_block($header) { 1153f3f0262cSandi print($header); 1154f3f0262cSandi } 1155f3f0262cSandi 1156f3f0262cSandi function _end_block() { 1157f3f0262cSandi } 1158f3f0262cSandi 1159f3f0262cSandi function _lines($lines, $prefix=' ', $color="white") { 1160f3f0262cSandi } 1161f3f0262cSandi 116260056e69SChristopher Smith function addedLine($line,$escaped=false) { 116360056e69SChristopher Smith if (!$escaped){ 116460056e69SChristopher Smith $line = $this->_escape($line); 116560056e69SChristopher Smith } 1166f76724a4STom N Harris return '<td '.HTMLDiff::css('diff-lineheader').'>+</td>'. 1167f76724a4STom N Harris '<td '.HTMLDiff::css('diff-addedline').'>' . $line.'</td>'; 1168f3f0262cSandi } 1169f3f0262cSandi 117060056e69SChristopher Smith function deletedLine($line,$escaped=false) { 117160056e69SChristopher Smith if (!$escaped){ 117260056e69SChristopher Smith $line = $this->_escape($line); 117360056e69SChristopher Smith } 1174f76724a4STom N Harris return '<td '.HTMLDiff::css('diff-lineheader').'>-</td>'. 1175f76724a4STom N Harris '<td '.HTMLDiff::css('diff-deletedline').'>' . $line.'</td>'; 1176f3f0262cSandi } 1177f3f0262cSandi 1178f3f0262cSandi function emptyLine() { 11793a4ea35cSChristopher Smith return '<td colspan="'.$this->colspan.'"> </td>'; 1180f3f0262cSandi } 1181f3f0262cSandi 1182f3f0262cSandi function contextLine($line) { 1183f76724a4STom N Harris return '<td '.HTMLDiff::css('diff-lineheader').'> </td>'. 1184333ef4f3SChristopher Smith '<td '.HTMLDiff::css('diff-context').'>'.$this->_escape($line).'</td>'; 1185f3f0262cSandi } 1186f3f0262cSandi 1187f3f0262cSandi function _added($lines) { 118860056e69SChristopher Smith $this->_addedLines($lines,false); 118960056e69SChristopher Smith } 119060056e69SChristopher Smith 119160056e69SChristopher Smith function _addedLines($lines,$escaped=false){ 1192f3f0262cSandi foreach ($lines as $line) { 119360056e69SChristopher Smith print('<tr>' . $this->emptyLine() . $this->addedLine($line,$escaped) . "</tr>\n"); 1194f3f0262cSandi } 1195f3f0262cSandi } 1196f3f0262cSandi 1197f3f0262cSandi function _deleted($lines) { 1198f3f0262cSandi foreach ($lines as $line) { 11997deca91bSRobin Getz print('<tr>' . $this->deletedLine($line) . $this->emptyLine() . "</tr>\n"); 1200f3f0262cSandi } 1201f3f0262cSandi } 1202f3f0262cSandi 1203f3f0262cSandi function _context($lines) { 1204f3f0262cSandi foreach ($lines as $line) { 12057deca91bSRobin Getz print('<tr>' . $this->contextLine($line) . $this->contextLine($line) . "</tr>\n"); 1206f3f0262cSandi } 1207f3f0262cSandi } 1208f3f0262cSandi 1209f3f0262cSandi function _changed($orig, $closing) { 121060056e69SChristopher Smith $diff = new WordLevelDiff($orig, $closing); // this escapes the diff data 1211f3f0262cSandi $del = $diff->orig(); 1212f3f0262cSandi $add = $diff->closing(); 1213f3f0262cSandi 1214f3f0262cSandi while ($line = array_shift($del)) { 1215f3f0262cSandi $aline = array_shift($add); 121660056e69SChristopher Smith print('<tr>' . $this->deletedLine($line,true) . $this->addedLine($aline,true) . "</tr>\n"); 1217f3f0262cSandi } 121860056e69SChristopher Smith $this->_addedLines($add,true); # If any leftovers 121960056e69SChristopher Smith } 122060056e69SChristopher Smith 122160056e69SChristopher Smith function _escape($str) { 122260056e69SChristopher Smith return hsc($str); 1223f3f0262cSandi } 1224f3f0262cSandi} 1225f3f0262cSandi 1226812bb04eSRobin Getz/** 1227812bb04eSRobin Getz * Inline style diff formatter. 1228812bb04eSRobin Getz * 1229812bb04eSRobin Getz */ 1230812bb04eSRobin Getzclass InlineDiffFormatter extends DiffFormatter { 1231f76724a4STom N Harris var $colspan = 2; 1232988c1340SPiyush Mishra 1233988c1340SPiyush Mishra function __construct() { 1234812bb04eSRobin Getz $this->leading_context_lines = 2; 1235812bb04eSRobin Getz $this->trailing_context_lines = 2; 1236812bb04eSRobin Getz } 1237812bb04eSRobin Getz 123842ea7f44SGerrit Uitslag /** 123942ea7f44SGerrit Uitslag * @param Diff $diff 124042ea7f44SGerrit Uitslag * @return string 124142ea7f44SGerrit Uitslag */ 1242812bb04eSRobin Getz function format($diff) { 1243812bb04eSRobin Getz // Preserve whitespaces by converting some to non-breaking spaces. 1244812bb04eSRobin Getz // Do not convert all of them to allow word-wrap. 1245812bb04eSRobin Getz $val = parent::format($diff); 1246e260f93bSAnika Henke $val = str_replace(' ','  ', $val); 1247e260f93bSAnika Henke $val = preg_replace('/ (?=<)|(?<=[ >]) /', ' ', $val); 1248812bb04eSRobin Getz return $val; 1249812bb04eSRobin Getz } 1250812bb04eSRobin Getz 1251812bb04eSRobin Getz function _pre($text){ 1252812bb04eSRobin Getz $text = htmlspecialchars($text); 1253812bb04eSRobin Getz return $text; 1254812bb04eSRobin Getz } 1255812bb04eSRobin Getz 1256812bb04eSRobin Getz function _block_header($xbeg, $xlen, $ybeg, $ylen) { 1257812bb04eSRobin Getz global $lang; 1258812bb04eSRobin Getz if ($xlen != 1) 1259812bb04eSRobin Getz $xbeg .= "," . $xlen; 1260812bb04eSRobin Getz if ($ylen != 1) 1261812bb04eSRobin Getz $ybeg .= "," . $ylen; 12623a4ea35cSChristopher Smith $r = '<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-blockheader').'>@@ '.$lang['line']." -$xbeg +$ybeg @@"; 126347a906eaSAndreas Gohr $r .= ' <span '.HTMLDiff::css('diff-deletedline').'><del>'.$lang['deleted'].'</del></span>'; 126447a906eaSAndreas Gohr $r .= ' <span '.HTMLDiff::css('diff-addedline').'>'.$lang['created'].'</span>'; 1265812bb04eSRobin Getz $r .= "</td></tr>\n"; 1266812bb04eSRobin Getz return $r; 1267812bb04eSRobin Getz } 1268812bb04eSRobin Getz 1269812bb04eSRobin Getz function _start_block($header) { 1270812bb04eSRobin Getz print($header."\n"); 1271812bb04eSRobin Getz } 1272812bb04eSRobin Getz 1273812bb04eSRobin Getz function _end_block() { 1274812bb04eSRobin Getz } 1275812bb04eSRobin Getz 1276812bb04eSRobin Getz function _lines($lines, $prefix=' ', $color="white") { 1277812bb04eSRobin Getz } 1278812bb04eSRobin Getz 1279812bb04eSRobin Getz function _added($lines) { 1280812bb04eSRobin Getz foreach ($lines as $line) { 1281333ef4f3SChristopher Smith print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td '.HTMLDiff::css('diff-addedline').'>'. $this->_escape($line) . "</td></tr>\n"); 1282812bb04eSRobin Getz } 1283812bb04eSRobin Getz } 1284812bb04eSRobin Getz 1285812bb04eSRobin Getz function _deleted($lines) { 1286812bb04eSRobin Getz foreach ($lines as $line) { 1287333ef4f3SChristopher Smith print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td '.HTMLDiff::css('diff-deletedline').'><del>' . $this->_escape($line) . "</del></td></tr>\n"); 1288812bb04eSRobin Getz } 1289812bb04eSRobin Getz } 1290812bb04eSRobin Getz 1291812bb04eSRobin Getz function _context($lines) { 1292812bb04eSRobin Getz foreach ($lines as $line) { 1293333ef4f3SChristopher Smith print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td '.HTMLDiff::css('diff-context').'>'. $this->_escape($line) ."</td></tr>\n"); 1294812bb04eSRobin Getz } 1295812bb04eSRobin Getz } 1296812bb04eSRobin Getz 1297812bb04eSRobin Getz function _changed($orig, $closing) { 129860056e69SChristopher Smith $diff = new InlineWordLevelDiff($orig, $closing); // this escapes the diff data 1299812bb04eSRobin Getz $add = $diff->inline(); 1300812bb04eSRobin Getz 1301812bb04eSRobin Getz foreach ($add as $line) 1302a69506c5STom N Harris print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td>'.$line."</td></tr>\n"); 1303812bb04eSRobin Getz } 130460056e69SChristopher Smith 130560056e69SChristopher Smith function _escape($str) { 130660056e69SChristopher Smith return hsc($str); 130760056e69SChristopher Smith } 1308812bb04eSRobin Getz} 1309812bb04eSRobin Getz 1310a297e675SAndreas Gohr/** 1311a297e675SAndreas Gohr * A class for computing three way diffs. 1312a297e675SAndreas Gohr * 1313a297e675SAndreas Gohr * @author Geoffrey T. Dairiki <dairiki@dairiki.org> 1314a297e675SAndreas Gohr */ 1315a297e675SAndreas Gohrclass Diff3 extends Diff { 1316a297e675SAndreas Gohr 1317a297e675SAndreas Gohr /** 1318a297e675SAndreas Gohr * Conflict counter. 1319a297e675SAndreas Gohr * 1320a297e675SAndreas Gohr * @var integer 1321a297e675SAndreas Gohr */ 1322a297e675SAndreas Gohr var $_conflictingBlocks = 0; 1323a297e675SAndreas Gohr 1324a297e675SAndreas Gohr /** 1325a297e675SAndreas Gohr * Computes diff between 3 sequences of strings. 1326a297e675SAndreas Gohr * 1327a297e675SAndreas Gohr * @param array $orig The original lines to use. 1328a297e675SAndreas Gohr * @param array $final1 The first version to compare to. 1329a297e675SAndreas Gohr * @param array $final2 The second version to compare to. 1330a297e675SAndreas Gohr */ 1331a297e675SAndreas Gohr function __construct($orig, $final1, $final2) { 1332a297e675SAndreas Gohr $engine = new _DiffEngine(); 1333a297e675SAndreas Gohr 1334a297e675SAndreas Gohr $this->_edits = $this->_diff3($engine->diff($orig, $final1), 1335a297e675SAndreas Gohr $engine->diff($orig, $final2)); 1336a297e675SAndreas Gohr } 1337a297e675SAndreas Gohr 1338a297e675SAndreas Gohr /** 1339a297e675SAndreas Gohr * Returns the merged lines 1340a297e675SAndreas Gohr * 1341a297e675SAndreas Gohr * @param string $label1 label for first version 1342a297e675SAndreas Gohr * @param string $label2 label for second version 134334df7cb0SAndreas Gohr * @param string $label3 separator between versions 1344a297e675SAndreas Gohr * @return array lines of the merged text 1345a297e675SAndreas Gohr */ 134634df7cb0SAndreas Gohr function mergedOutput($label1='<<<<<<<',$label2='>>>>>>>',$label3='=======') { 1347a297e675SAndreas Gohr $lines = array(); 1348a297e675SAndreas Gohr foreach ($this->_edits as $edit) { 1349a297e675SAndreas Gohr if ($edit->isConflict()) { 1350a297e675SAndreas Gohr /* FIXME: this should probably be moved somewhere else. */ 1351a297e675SAndreas Gohr $lines = array_merge($lines, 135234df7cb0SAndreas Gohr array($label1), 1353a297e675SAndreas Gohr $edit->final1, 135434df7cb0SAndreas Gohr array($label3), 1355a297e675SAndreas Gohr $edit->final2, 135634df7cb0SAndreas Gohr array($label2)); 1357a297e675SAndreas Gohr $this->_conflictingBlocks++; 1358a297e675SAndreas Gohr } else { 1359a297e675SAndreas Gohr $lines = array_merge($lines, $edit->merged()); 1360a297e675SAndreas Gohr } 1361a297e675SAndreas Gohr } 1362a297e675SAndreas Gohr 1363a297e675SAndreas Gohr return $lines; 1364a297e675SAndreas Gohr } 1365a297e675SAndreas Gohr 1366a297e675SAndreas Gohr /** 1367a297e675SAndreas Gohr * @access private 1368f50a239bSTakamura * 1369f50a239bSTakamura * @param array $edits1 1370f50a239bSTakamura * @param array $edits2 1371f50a239bSTakamura * 1372f50a239bSTakamura * @return array 1373a297e675SAndreas Gohr */ 1374a297e675SAndreas Gohr function _diff3($edits1, $edits2) { 1375a297e675SAndreas Gohr $edits = array(); 1376a297e675SAndreas Gohr $bb = new _Diff3_BlockBuilder(); 1377a297e675SAndreas Gohr 1378a297e675SAndreas Gohr $e1 = current($edits1); 1379a297e675SAndreas Gohr $e2 = current($edits2); 1380a297e675SAndreas Gohr while ($e1 || $e2) { 1381a297e675SAndreas Gohr if ($e1 && $e2 && is_a($e1, '_DiffOp_copy') && is_a($e2, '_DiffOp_copy')) { 1382a297e675SAndreas Gohr /* We have copy blocks from both diffs. This is the (only) 1383a297e675SAndreas Gohr * time we want to emit a diff3 copy block. Flush current 1384a297e675SAndreas Gohr * diff3 diff block, if any. */ 1385a297e675SAndreas Gohr if ($edit = $bb->finish()) { 1386a297e675SAndreas Gohr $edits[] = $edit; 1387a297e675SAndreas Gohr } 1388a297e675SAndreas Gohr 1389a297e675SAndreas Gohr $ncopy = min($e1->norig(), $e2->norig()); 1390a297e675SAndreas Gohr assert($ncopy > 0); 1391a297e675SAndreas Gohr $edits[] = new _Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy)); 1392a297e675SAndreas Gohr 1393a297e675SAndreas Gohr if ($e1->norig() > $ncopy) { 1394a297e675SAndreas Gohr array_splice($e1->orig, 0, $ncopy); 1395a297e675SAndreas Gohr array_splice($e1->closing, 0, $ncopy); 1396a297e675SAndreas Gohr } else { 1397a297e675SAndreas Gohr $e1 = next($edits1); 1398a297e675SAndreas Gohr } 1399a297e675SAndreas Gohr 1400a297e675SAndreas Gohr if ($e2->norig() > $ncopy) { 1401a297e675SAndreas Gohr array_splice($e2->orig, 0, $ncopy); 1402a297e675SAndreas Gohr array_splice($e2->closing, 0, $ncopy); 1403a297e675SAndreas Gohr } else { 1404a297e675SAndreas Gohr $e2 = next($edits2); 1405a297e675SAndreas Gohr } 1406a297e675SAndreas Gohr } else { 1407a297e675SAndreas Gohr if ($e1 && $e2) { 1408a297e675SAndreas Gohr if ($e1->orig && $e2->orig) { 1409a297e675SAndreas Gohr $norig = min($e1->norig(), $e2->norig()); 1410a297e675SAndreas Gohr $orig = array_splice($e1->orig, 0, $norig); 1411a297e675SAndreas Gohr array_splice($e2->orig, 0, $norig); 1412a297e675SAndreas Gohr $bb->input($orig); 1413a297e675SAndreas Gohr } 1414a297e675SAndreas Gohr 1415a297e675SAndreas Gohr if (is_a($e1, '_DiffOp_copy')) { 1416a297e675SAndreas Gohr $bb->out1(array_splice($e1->closing, 0, $norig)); 1417a297e675SAndreas Gohr } 1418a297e675SAndreas Gohr 1419a297e675SAndreas Gohr if (is_a($e2, '_DiffOp_copy')) { 1420a297e675SAndreas Gohr $bb->out2(array_splice($e2->closing, 0, $norig)); 1421a297e675SAndreas Gohr } 1422a297e675SAndreas Gohr } 1423a297e675SAndreas Gohr 1424a297e675SAndreas Gohr if ($e1 && ! $e1->orig) { 1425a297e675SAndreas Gohr $bb->out1($e1->closing); 1426a297e675SAndreas Gohr $e1 = next($edits1); 1427a297e675SAndreas Gohr } 1428a297e675SAndreas Gohr if ($e2 && ! $e2->orig) { 1429a297e675SAndreas Gohr $bb->out2($e2->closing); 1430a297e675SAndreas Gohr $e2 = next($edits2); 1431a297e675SAndreas Gohr } 1432a297e675SAndreas Gohr } 1433a297e675SAndreas Gohr } 1434a297e675SAndreas Gohr 1435a297e675SAndreas Gohr if ($edit = $bb->finish()) { 1436a297e675SAndreas Gohr $edits[] = $edit; 1437a297e675SAndreas Gohr } 1438a297e675SAndreas Gohr 1439a297e675SAndreas Gohr return $edits; 1440a297e675SAndreas Gohr } 1441a297e675SAndreas Gohr} 1442a297e675SAndreas Gohr 1443a297e675SAndreas Gohr/** 1444a297e675SAndreas Gohr * @author Geoffrey T. Dairiki <dairiki@dairiki.org> 1445a297e675SAndreas Gohr * 1446a297e675SAndreas Gohr * @access private 1447a297e675SAndreas Gohr */ 1448a297e675SAndreas Gohrclass _Diff3_Op { 1449a297e675SAndreas Gohr 1450a297e675SAndreas Gohr function __construct($orig = false, $final1 = false, $final2 = false) { 1451a297e675SAndreas Gohr $this->orig = $orig ? $orig : array(); 1452a297e675SAndreas Gohr $this->final1 = $final1 ? $final1 : array(); 1453a297e675SAndreas Gohr $this->final2 = $final2 ? $final2 : array(); 1454a297e675SAndreas Gohr } 1455a297e675SAndreas Gohr 1456a297e675SAndreas Gohr function merged() { 1457a297e675SAndreas Gohr if (!isset($this->_merged)) { 1458a297e675SAndreas Gohr if ($this->final1 === $this->final2) { 1459a297e675SAndreas Gohr $this->_merged = &$this->final1; 1460a297e675SAndreas Gohr } elseif ($this->final1 === $this->orig) { 1461a297e675SAndreas Gohr $this->_merged = &$this->final2; 1462a297e675SAndreas Gohr } elseif ($this->final2 === $this->orig) { 1463a297e675SAndreas Gohr $this->_merged = &$this->final1; 1464a297e675SAndreas Gohr } else { 1465a297e675SAndreas Gohr $this->_merged = false; 1466a297e675SAndreas Gohr } 1467a297e675SAndreas Gohr } 1468a297e675SAndreas Gohr 1469a297e675SAndreas Gohr return $this->_merged; 1470a297e675SAndreas Gohr } 1471a297e675SAndreas Gohr 1472a297e675SAndreas Gohr function isConflict() { 1473a297e675SAndreas Gohr return $this->merged() === false; 1474a297e675SAndreas Gohr } 1475a297e675SAndreas Gohr 1476a297e675SAndreas Gohr} 1477a297e675SAndreas Gohr 1478a297e675SAndreas Gohr/** 1479a297e675SAndreas Gohr * @author Geoffrey T. Dairiki <dairiki@dairiki.org> 1480a297e675SAndreas Gohr * 1481a297e675SAndreas Gohr * @access private 1482a297e675SAndreas Gohr */ 1483a297e675SAndreas Gohrclass _Diff3_Op_copy extends _Diff3_Op { 1484a297e675SAndreas Gohr 1485a297e675SAndreas Gohr function __construct($lines = false) { 1486a297e675SAndreas Gohr $this->orig = $lines ? $lines : array(); 1487a297e675SAndreas Gohr $this->final1 = &$this->orig; 1488a297e675SAndreas Gohr $this->final2 = &$this->orig; 1489a297e675SAndreas Gohr } 1490a297e675SAndreas Gohr 1491a297e675SAndreas Gohr function merged() { 1492a297e675SAndreas Gohr return $this->orig; 1493a297e675SAndreas Gohr } 1494a297e675SAndreas Gohr 1495a297e675SAndreas Gohr function isConflict() { 1496a297e675SAndreas Gohr return false; 1497a297e675SAndreas Gohr } 1498a297e675SAndreas Gohr} 1499a297e675SAndreas Gohr 1500a297e675SAndreas Gohr/** 1501a297e675SAndreas Gohr * @author Geoffrey T. Dairiki <dairiki@dairiki.org> 1502a297e675SAndreas Gohr * 1503a297e675SAndreas Gohr * @access private 1504a297e675SAndreas Gohr */ 1505a297e675SAndreas Gohrclass _Diff3_BlockBuilder { 1506a297e675SAndreas Gohr 1507a297e675SAndreas Gohr function __construct() { 1508a297e675SAndreas Gohr $this->_init(); 1509a297e675SAndreas Gohr } 1510a297e675SAndreas Gohr 1511a297e675SAndreas Gohr function input($lines) { 1512a297e675SAndreas Gohr if ($lines) { 1513a297e675SAndreas Gohr $this->_append($this->orig, $lines); 1514a297e675SAndreas Gohr } 1515a297e675SAndreas Gohr } 1516a297e675SAndreas Gohr 1517a297e675SAndreas Gohr function out1($lines) { 1518a297e675SAndreas Gohr if ($lines) { 1519a297e675SAndreas Gohr $this->_append($this->final1, $lines); 1520a297e675SAndreas Gohr } 1521a297e675SAndreas Gohr } 1522a297e675SAndreas Gohr 1523a297e675SAndreas Gohr function out2($lines) { 1524a297e675SAndreas Gohr if ($lines) { 1525a297e675SAndreas Gohr $this->_append($this->final2, $lines); 1526a297e675SAndreas Gohr } 1527a297e675SAndreas Gohr } 1528a297e675SAndreas Gohr 1529a297e675SAndreas Gohr function isEmpty() { 1530a297e675SAndreas Gohr return !$this->orig && !$this->final1 && !$this->final2; 1531a297e675SAndreas Gohr } 1532a297e675SAndreas Gohr 1533a297e675SAndreas Gohr function finish() { 1534a297e675SAndreas Gohr if ($this->isEmpty()) { 1535a297e675SAndreas Gohr return false; 1536a297e675SAndreas Gohr } else { 1537a297e675SAndreas Gohr $edit = new _Diff3_Op($this->orig, $this->final1, $this->final2); 1538a297e675SAndreas Gohr $this->_init(); 1539a297e675SAndreas Gohr return $edit; 1540a297e675SAndreas Gohr } 1541a297e675SAndreas Gohr } 1542a297e675SAndreas Gohr 1543a297e675SAndreas Gohr function _init() { 1544a297e675SAndreas Gohr $this->orig = $this->final1 = $this->final2 = array(); 1545a297e675SAndreas Gohr } 1546a297e675SAndreas Gohr 1547a297e675SAndreas Gohr function _append(&$array, $lines) { 1548a297e675SAndreas Gohr array_splice($array, sizeof($array), 0, $lines); 1549a297e675SAndreas Gohr } 1550a297e675SAndreas Gohr} 1551340756e4Sandi 1552e3776c06SMichael Hamann//Setup VIM: ex: et ts=4 : 1553