|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 00004 # See diff.doc 00005 00006 // A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3) 00007 // 00008 // Copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org> 00009 // You may copy this code freely under the conditions of the GPL. 00010 // 00011 00012 define('USE_ASSERTS_IN_WIKI', function_exists('assert')); 00013 00014 class _WikiDiffOp { 00015 var $type; 00016 var $orig; 00017 var $closing; 00018 00019 function reverse() { 00020 trigger_error("pure virtual", E_USER_ERROR); 00021 } 00022 00023 function norig() { 00024 return $this->orig ? sizeof($this->orig) : 0; 00025 } 00026 00027 function nclosing() { 00028 return $this->closing ? sizeof($this->closing) : 0; 00029 } 00030 } 00031 00032 class _WikiDiffOp_Copy extends _WikiDiffOp { 00033 var $type = 'copy'; 00034 00035 function _WikiDiffOp_Copy ($orig, $closing = false) { 00036 if (!is_array($closing)) 00037 $closing = $orig; 00038 $this->orig = $orig; 00039 $this->closing = $closing; 00040 } 00041 00042 function reverse() { 00043 return new _WikiDiffOp_Copy($this->closing, $this->orig); 00044 } 00045 } 00046 00047 class _WikiDiffOp_Delete extends _WikiDiffOp { 00048 var $type = 'delete'; 00049 00050 function _WikiDiffOp_Delete ($lines) { 00051 $this->orig = $lines; 00052 $this->closing = false; 00053 } 00054 00055 function reverse() { 00056 return new _WikiDiffOp_Add($this->orig); 00057 } 00058 } 00059 00060 class _WikiDiffOp_Add extends _WikiDiffOp { 00061 var $type = 'add'; 00062 00063 function _WikiDiffOp_Add ($lines) { 00064 $this->closing = $lines; 00065 $this->orig = false; 00066 } 00067 00068 function reverse() { 00069 return new _WikiDiffOp_Delete($this->closing); 00070 } 00071 } 00072 00073 class _WikiDiffOp_Change extends _WikiDiffOp { 00074 var $type = 'change'; 00075 00076 function _WikiDiffOp_Change ($orig, $closing) { 00077 $this->orig = $orig; 00078 $this->closing = $closing; 00079 } 00080 00081 function reverse() { 00082 return new _WikiDiffOp_Change($this->closing, $this->orig); 00083 } 00084 } 00085 00086 00107 class _WikiDiffEngine 00108 { 00109 function diff ($from_lines, $to_lines) { 00110 $n_from = sizeof($from_lines); 00111 $n_to = sizeof($to_lines); 00112 00113 $this->xchanged = $this->ychanged = array(); 00114 $this->xv = $this->yv = array(); 00115 $this->xind = $this->yind = array(); 00116 unset($this->seq); 00117 unset($this->in_seq); 00118 unset($this->lcs); 00119 00120 // Skip leading common lines. 00121 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { 00122 if ($from_lines[$skip] != $to_lines[$skip]) 00123 break; 00124 $this->xchanged[$skip] = $this->ychanged[$skip] = false; 00125 } 00126 // Skip trailing common lines. 00127 $xi = $n_from; $yi = $n_to; 00128 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { 00129 if ($from_lines[$xi] != $to_lines[$yi]) 00130 break; 00131 $this->xchanged[$xi] = $this->ychanged[$yi] = false; 00132 } 00133 00134 // Ignore lines which do not exist in both files. 00135 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) 00136 $xhash[$from_lines[$xi]] = 1; 00137 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { 00138 $line = $to_lines[$yi]; 00139 if ( ($this->ychanged[$yi] = empty($xhash[$line])) ) 00140 continue; 00141 $yhash[$line] = 1; 00142 $this->yv[] = $line; 00143 $this->yind[] = $yi; 00144 } 00145 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { 00146 $line = $from_lines[$xi]; 00147 if ( ($this->xchanged[$xi] = empty($yhash[$line])) ) 00148 continue; 00149 $this->xv[] = $line; 00150 $this->xind[] = $xi; 00151 } 00152 00153 // Find the LCS. 00154 $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); 00155 00156 // Merge edits when possible 00157 $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); 00158 $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); 00159 00160 // Compute the edit operations. 00161 $edits = array(); 00162 $xi = $yi = 0; 00163 while ($xi < $n_from || $yi < $n_to) { 00164 USE_ASSERTS_IN_WIKI && assert($yi < $n_to || $this->xchanged[$xi]); 00165 USE_ASSERTS_IN_WIKI && assert($xi < $n_from || $this->ychanged[$yi]); 00166 00167 // Skip matching "snake". 00168 $copy = array(); 00169 while ( $xi < $n_from && $yi < $n_to 00170 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { 00171 $copy[] = $from_lines[$xi++]; 00172 ++$yi; 00173 } 00174 if ($copy) 00175 $edits[] = new _WikiDiffOp_Copy($copy); 00176 00177 // Find deletes & adds. 00178 $delete = array(); 00179 while ($xi < $n_from && $this->xchanged[$xi]) 00180 $delete[] = $from_lines[$xi++]; 00181 00182 $add = array(); 00183 while ($yi < $n_to && $this->ychanged[$yi]) 00184 $add[] = $to_lines[$yi++]; 00185 00186 if ($delete && $add) 00187 $edits[] = new _WikiDiffOp_Change($delete, $add); 00188 elseif ($delete) 00189 $edits[] = new _WikiDiffOp_Delete($delete); 00190 elseif ($add) 00191 $edits[] = new _WikiDiffOp_Add($add); 00192 } 00193 return $edits; 00194 } 00195 00196 00197 /* Divide the Largest Common Subsequence (LCS) of the sequences 00198 * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally 00199 * sized segments. 00200 * 00201 * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an 00202 * array of NCHUNKS+1 (X, Y) indexes giving the diving points between 00203 * sub sequences. The first sub-sequence is contained in [X0, X1), 00204 * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note 00205 * that (X0, Y0) == (XOFF, YOFF) and 00206 * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). 00207 * 00208 * This function assumes that the first lines of the specified portions 00209 * of the two files do not match, and likewise that the last lines do not 00210 * match. The caller must trim matching lines from the beginning and end 00211 * of the portions it is going to specify. 00212 */ 00213 function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { 00214 $flip = false; 00215 00216 if ($xlim - $xoff > $ylim - $yoff) { 00217 // Things seems faster (I'm not sure I understand why) 00218 // when the shortest sequence in X. 00219 $flip = true; 00220 list ($xoff, $xlim, $yoff, $ylim) 00221 = array( $yoff, $ylim, $xoff, $xlim); 00222 } 00223 00224 if ($flip) 00225 for ($i = $ylim - 1; $i >= $yoff; $i--) 00226 $ymatches[$this->xv[$i]][] = $i; 00227 else 00228 for ($i = $ylim - 1; $i >= $yoff; $i--) 00229 $ymatches[$this->yv[$i]][] = $i; 00230 00231 $this->lcs = 0; 00232 $this->seq[0]= $yoff - 1; 00233 $this->in_seq = array(); 00234 $ymids[0] = array(); 00235 00236 $numer = $xlim - $xoff + $nchunks - 1; 00237 $x = $xoff; 00238 for ($chunk = 0; $chunk < $nchunks; $chunk++) { 00239 if ($chunk > 0) 00240 for ($i = 0; $i <= $this->lcs; $i++) 00241 $ymids[$i][$chunk-1] = $this->seq[$i]; 00242 00243 $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); 00244 for ( ; $x < $x1; $x++) { 00245 $line = $flip ? $this->yv[$x] : $this->xv[$x]; 00246 if (empty($ymatches[$line])) 00247 continue; 00248 $matches = $ymatches[$line]; 00249 reset($matches); 00250 while (list ($junk, $y) = each($matches)) 00251 if (empty($this->in_seq[$y])) { 00252 $k = $this->_lcs_pos($y); 00253 USE_ASSERTS_IN_WIKI && assert($k > 0); 00254 $ymids[$k] = $ymids[$k-1]; 00255 break; 00256 } 00257 while (list ($junk, $y) = each($matches)) { 00258 if ($y > $this->seq[$k-1]) { 00259 USE_ASSERTS_IN_WIKI && assert($y < $this->seq[$k]); 00260 // Optimization: this is a common case: 00261 // next match is just replacing previous match. 00262 $this->in_seq[$this->seq[$k]] = false; 00263 $this->seq[$k] = $y; 00264 $this->in_seq[$y] = 1; 00265 } 00266 else if (empty($this->in_seq[$y])) { 00267 $k = $this->_lcs_pos($y); 00268 USE_ASSERTS_IN_WIKI && assert($k > 0); 00269 $ymids[$k] = $ymids[$k-1]; 00270 } 00271 } 00272 } 00273 } 00274 00275 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); 00276 $ymid = $ymids[$this->lcs]; 00277 for ($n = 0; $n < $nchunks - 1; $n++) { 00278 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); 00279 $y1 = $ymid[$n] + 1; 00280 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); 00281 } 00282 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); 00283 00284 return array($this->lcs, $seps); 00285 } 00286 00287 function _lcs_pos ($ypos) { 00288 $end = $this->lcs; 00289 if ($end == 0 || $ypos > $this->seq[$end]) { 00290 $this->seq[++$this->lcs] = $ypos; 00291 $this->in_seq[$ypos] = 1; 00292 return $this->lcs; 00293 } 00294 00295 $beg = 1; 00296 while ($beg < $end) { 00297 $mid = (int)(($beg + $end) / 2); 00298 if ( $ypos > $this->seq[$mid] ) 00299 $beg = $mid + 1; 00300 else 00301 $end = $mid; 00302 } 00303 00304 USE_ASSERTS_IN_WIKI && assert($ypos != $this->seq[$end]); 00305 00306 $this->in_seq[$this->seq[$end]] = false; 00307 $this->seq[$end] = $ypos; 00308 $this->in_seq[$ypos] = 1; 00309 return $end; 00310 } 00311 00312 /* Find LCS of two sequences. 00313 * 00314 * The results are recorded in the vectors $this->{x,y}changed[], by 00315 * storing a 1 in the element for each line that is an insertion 00316 * or deletion (ie. is not in the LCS). 00317 * 00318 * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. 00319 * 00320 * Note that XLIM, YLIM are exclusive bounds. 00321 * All line numbers are origin-0 and discarded lines are not counted. 00322 */ 00323 function _compareseq ($xoff, $xlim, $yoff, $ylim) { 00324 // Slide down the bottom initial diagonal. 00325 while ($xoff < $xlim && $yoff < $ylim 00326 && $this->xv[$xoff] == $this->yv[$yoff]) { 00327 ++$xoff; 00328 ++$yoff; 00329 } 00330 00331 // Slide up the top initial diagonal. 00332 while ($xlim > $xoff && $ylim > $yoff 00333 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { 00334 --$xlim; 00335 --$ylim; 00336 } 00337 00338 if ($xoff == $xlim || $yoff == $ylim) 00339 $lcs = 0; 00340 else { 00341 // This is ad hoc but seems to work well. 00342 //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); 00343 //$nchunks = max(2,min(8,(int)$nchunks)); 00344 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; 00345 list ($lcs, $seps) 00346 = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); 00347 } 00348 00349 if ($lcs == 0) { 00350 // X and Y sequences have no common subsequence: 00351 // mark all changed. 00352 while ($yoff < $ylim) 00353 $this->ychanged[$this->yind[$yoff++]] = 1; 00354 while ($xoff < $xlim) 00355 $this->xchanged[$this->xind[$xoff++]] = 1; 00356 } 00357 else { 00358 // Use the partitions to split this problem into subproblems. 00359 reset($seps); 00360 $pt1 = $seps[0]; 00361 while ($pt2 = next($seps)) { 00362 $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); 00363 $pt1 = $pt2; 00364 } 00365 } 00366 } 00367 00368 /* Adjust inserts/deletes of identical lines to join changes 00369 * as much as possible. 00370 * 00371 * We do something when a run of changed lines include a 00372 * line at one end and has an excluded, identical line at the other. 00373 * We are free to choose which identical line is included. 00374 * `compareseq' usually chooses the one at the beginning, 00375 * but usually it is cleaner to consider the following identical line 00376 * to be the "change". 00377 * 00378 * This is extracted verbatim from analyze.c (GNU diffutils-2.7). 00379 */ 00380 function _shift_boundaries ($lines, &$changed, $other_changed) { 00381 $i = 0; 00382 $j = 0; 00383 00384 USE_ASSERTS_IN_WIKI && assert('sizeof($lines) == sizeof($changed)'); 00385 $len = sizeof($lines); 00386 $other_len = sizeof($other_changed); 00387 00388 while (1) { 00389 /* 00390 * Scan forwards to find beginning of another run of changes. 00391 * Also keep track of the corresponding point in the other file. 00392 * 00393 * Throughout this code, $i and $j are adjusted together so that 00394 * the first $i elements of $changed and the first $j elements 00395 * of $other_changed both contain the same number of zeros 00396 * (unchanged lines). 00397 * Furthermore, $j is always kept so that $j == $other_len or 00398 * $other_changed[$j] == false. 00399 */ 00400 while ($j < $other_len && $other_changed[$j]) 00401 $j++; 00402 00403 while ($i < $len && ! $changed[$i]) { 00404 USE_ASSERTS_IN_WIKI && assert('$j < $other_len && ! $other_changed[$j]'); 00405 $i++; $j++; 00406 while ($j < $other_len && $other_changed[$j]) 00407 $j++; 00408 } 00409 00410 if ($i == $len) 00411 break; 00412 00413 $start = $i; 00414 00415 // Find the end of this run of changes. 00416 while (++$i < $len && $changed[$i]) 00417 continue; 00418 00419 do { 00420 /* 00421 * Record the length of this run of changes, so that 00422 * we can later determine whether the run has grown. 00423 */ 00424 $runlength = $i - $start; 00425 00426 /* 00427 * Move the changed region back, so long as the 00428 * previous unchanged line matches the last changed one. 00429 * This merges with previous changed regions. 00430 */ 00431 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { 00432 $changed[--$start] = 1; 00433 $changed[--$i] = false; 00434 while ($start > 0 && $changed[$start - 1]) 00435 $start--; 00436 USE_ASSERTS_IN_WIKI && assert('$j > 0'); 00437 while ($other_changed[--$j]) 00438 continue; 00439 USE_ASSERTS_IN_WIKI && assert('$j >= 0 && !$other_changed[$j]'); 00440 } 00441 00442 /* 00443 * Set CORRESPONDING to the end of the changed run, at the last 00444 * point where it corresponds to a changed run in the other file. 00445 * CORRESPONDING == LEN means no such point has been found. 00446 */ 00447 $corresponding = $j < $other_len ? $i : $len; 00448 00449 /* 00450 * Move the changed region forward, so long as the 00451 * first changed line matches the following unchanged one. 00452 * This merges with following changed regions. 00453 * Do this second, so that if there are no merges, 00454 * the changed region is moved forward as far as possible. 00455 */ 00456 while ($i < $len && $lines[$start] == $lines[$i]) { 00457 $changed[$start++] = false; 00458 $changed[$i++] = 1; 00459 while ($i < $len && $changed[$i]) 00460 $i++; 00461 00462 USE_ASSERTS_IN_WIKI && assert('$j < $other_len && ! $other_changed[$j]'); 00463 $j++; 00464 if ($j < $other_len && $other_changed[$j]) { 00465 $corresponding = $i; 00466 while ($j < $other_len && $other_changed[$j]) 00467 $j++; 00468 } 00469 } 00470 } while ($runlength != $i - $start); 00471 00472 /* 00473 * If possible, move the fully-merged run of changes 00474 * back to a corresponding run in the other file. 00475 */ 00476 while ($corresponding < $i) { 00477 $changed[--$start] = 1; 00478 $changed[--$i] = 0; 00479 USE_ASSERTS_IN_WIKI && assert('$j > 0'); 00480 while ($other_changed[--$j]) 00481 continue; 00482 USE_ASSERTS_IN_WIKI && assert('$j >= 0 && !$other_changed[$j]'); 00483 } 00484 } 00485 } 00486 } 00487 00491 class WikiDiff 00492 { 00493 var $edits; 00494 00503 function WikiDiff($from_lines, $to_lines) { 00504 $eng = new _WikiDiffEngine; 00505 $this->edits = $eng->diff($from_lines, $to_lines); 00506 //$this->_check($from_lines, $to_lines); 00507 } 00508 00519 function reverse () { 00520 $rev = $this; 00521 $rev->edits = array(); 00522 foreach ($this->edits as $edit) { 00523 $rev->edits[] = $edit->reverse(); 00524 } 00525 return $rev; 00526 } 00527 00533 function isEmpty () { 00534 foreach ($this->edits as $edit) { 00535 if ($edit->type != 'copy') 00536 return false; 00537 } 00538 return true; 00539 } 00540 00548 function lcs () { 00549 $lcs = 0; 00550 foreach ($this->edits as $edit) { 00551 if ($edit->type == 'copy') 00552 $lcs += sizeof($edit->orig); 00553 } 00554 return $lcs; 00555 } 00556 00565 function orig() { 00566 $lines = array(); 00567 00568 foreach ($this->edits as $edit) { 00569 if ($edit->orig) 00570 array_splice($lines, sizeof($lines), 0, $edit->orig); 00571 } 00572 return $lines; 00573 } 00574 00583 function closing() { 00584 $lines = array(); 00585 00586 foreach ($this->edits as $edit) { 00587 if ($edit->closing) 00588 array_splice($lines, sizeof($lines), 0, $edit->closing); 00589 } 00590 return $lines; 00591 } 00592 00598 function _check ($from_lines, $to_lines) { 00599 if (serialize($from_lines) != serialize($this->orig())) 00600 trigger_error("Reconstructed original doesn't match", E_USER_ERROR); 00601 if (serialize($to_lines) != serialize($this->closing())) 00602 trigger_error("Reconstructed closing doesn't match", E_USER_ERROR); 00603 00604 $rev = $this->reverse(); 00605 if (serialize($to_lines) != serialize($rev->orig())) 00606 trigger_error("Reversed original doesn't match", E_USER_ERROR); 00607 if (serialize($from_lines) != serialize($rev->closing())) 00608 trigger_error("Reversed closing doesn't match", E_USER_ERROR); 00609 00610 00611 $prevtype = 'none'; 00612 foreach ($this->edits as $edit) { 00613 if ( $prevtype == $edit->type ) 00614 trigger_error("Edit sequence is non-optimal", E_USER_ERROR); 00615 $prevtype = $edit->type; 00616 } 00617 00618 $lcs = $this->lcs(); 00619 trigger_error("WikiDiff okay: LCS = $lcs", E_USER_NOTICE); 00620 } 00621 } 00622 00627 class MappedWikiDiff 00628 extends WikiDiff 00629 { 00653 function MappedWikiDiff($from_lines, $to_lines, 00654 $mapped_from_lines, $mapped_to_lines) { 00655 00656 assert(sizeof($from_lines) == sizeof($mapped_from_lines)); 00657 assert(sizeof($to_lines) == sizeof($mapped_to_lines)); 00658 00659 $this->WikiDiff($mapped_from_lines, $mapped_to_lines); 00660 00661 $xi = $yi = 0; 00662 for ($i = 0; $i < sizeof($this->edits); $i++) { 00663 $orig = &$this->edits[$i]->orig; 00664 if (is_array($orig)) { 00665 $orig = array_slice($from_lines, $xi, sizeof($orig)); 00666 $xi += sizeof($orig); 00667 } 00668 00669 $closing = &$this->edits[$i]->closing; 00670 if (is_array($closing)) { 00671 $closing = array_slice($to_lines, $yi, sizeof($closing)); 00672 $yi += sizeof($closing); 00673 } 00674 } 00675 } 00676 } 00677 00685 class WikiDiffFormatter 00686 { 00693 var $leading_context_lines = 0; 00694 00701 var $trailing_context_lines = 0; 00702 00709 function format($diff) { 00710 00711 $xi = $yi = 1; 00712 $block = false; 00713 $context = array(); 00714 00715 $nlead = $this->leading_context_lines; 00716 $ntrail = $this->trailing_context_lines; 00717 00718 $this->_start_diff(); 00719 00720 foreach ($diff->edits as $edit) { 00721 if ($edit->type == 'copy') { 00722 if (is_array($block)) { 00723 if (sizeof($edit->orig) <= $nlead + $ntrail) { 00724 $block[] = $edit; 00725 } 00726 else{ 00727 if ($ntrail) { 00728 $context = array_slice($edit->orig, 0, $ntrail); 00729 $block[] = new _WikiWikiDiffOp_Copy($context); 00730 } 00731 $this->_block($x0, $ntrail + $xi - $x0, 00732 $y0, $ntrail + $yi - $y0, 00733 $block); 00734 $block = false; 00735 } 00736 } 00737 $context = $edit->orig; 00738 } 00739 else { 00740 if (! is_array($block)) { 00741 $context = array_slice($context, sizeof($context) - $nlead); 00742 $x0 = $xi - sizeof($context); 00743 $y0 = $yi - sizeof($context); 00744 $block = array(); 00745 if ($context) 00746 $block[] = new _WikiWikiDiffOp_Copy($context); 00747 } 00748 $block[] = $edit; 00749 } 00750 00751 if ($edit->orig) 00752 $xi += sizeof($edit->orig); 00753 if ($edit->closing) 00754 $yi += sizeof($edit->closing); 00755 } 00756 00757 if (is_array($block)) 00758 $this->_block($x0, $xi - $x0, 00759 $y0, $yi - $y0, 00760 $block); 00761 00762 return $this->_end_diff(); 00763 } 00764 00765 function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { 00766 $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); 00767 foreach ($edits as $edit) { 00768 if ($edit->type == 'copy') 00769 $this->_context($edit->orig); 00770 elseif ($edit->type == 'add') 00771 $this->_added($edit->closing); 00772 elseif ($edit->type == 'delete') 00773 $this->_deleted($edit->orig); 00774 elseif ($edit->type == 'change') 00775 $this->_changed($edit->orig, $edit->closing); 00776 else 00777 trigger_error("Unknown edit type", E_USER_ERROR); 00778 } 00779 $this->_end_block(); 00780 } 00781 00782 function _start_diff() { 00783 ob_start(); 00784 } 00785 00786 function _end_diff() { 00787 $val = ob_get_contents(); 00788 ob_end_clean(); 00789 return $val; 00790 } 00791 00792 function _block_header($xbeg, $xlen, $ybeg, $ylen) { 00793 if ($xlen > 1) 00794 $xbeg .= "," . ($xbeg + $xlen - 1); 00795 if ($ylen > 1) 00796 $ybeg .= "," . ($ybeg + $ylen - 1); 00797 00798 return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; 00799 } 00800 00801 function _start_block($header) { 00802 echo $header; 00803 } 00804 00805 function _end_block() { 00806 } 00807 00808 function _lines($lines, $prefix = ' ') { 00809 foreach ($lines as $line) 00810 echo "$prefix $line\n"; 00811 } 00812 00813 function _context($lines) { 00814 $this->_lines($lines); 00815 } 00816 00817 function _added($lines) { 00818 $this->_lines($lines, ">"); 00819 } 00820 function _deleted($lines) { 00821 $this->_lines($lines, "<"); 00822 } 00823 00824 function _changed($orig, $closing) { 00825 $this->_deleted($orig); 00826 echo "---\n"; 00827 $this->_added($closing); 00828 } 00829 } 00830 00831 00837 define('NBSP', ' '); // iso-8859-x non-breaking space. 00838 00839 class _WikiHWLDF_WordAccumulator { 00840 function _WikiHWLDF_WordAccumulator () { 00841 $this->_lines = array(); 00842 $this->_line = ''; 00843 $this->_group = ''; 00844 $this->_tag = ''; 00845 } 00846 00847 function _flushGroup ($new_tag) { 00848 if ($this->_group !== '') { 00849 if ($this->_tag == 'mark') 00850 $this->_line .= '<span class="diffchange">'.$this->_group.'</span>'; 00851 else 00852 $this->_line .= $this->_group; 00853 } 00854 $this->_group = ''; 00855 $this->_tag = $new_tag; 00856 } 00857 00858 function _flushLine ($new_tag) { 00859 $this->_flushGroup($new_tag); 00860 if ($this->_line != '') 00861 $this->_lines[] = $this->_line; 00862 $this->_line = ''; 00863 } 00864 00865 function addWords ($words, $tag = '') { 00866 if ($tag != $this->_tag) 00867 $this->_flushGroup($tag); 00868 00869 foreach ($words as $word) { 00870 // new-line should only come as first char of word. 00871 if ($word == '') 00872 continue; 00873 if ($word[0] == "\n") { 00874 $this->_group .= NBSP; 00875 $this->_flushLine($tag); 00876 $word = substr($word, 1); 00877 } 00878 assert(!strstr($word, "\n")); 00879 $this->_group .= $word; 00880 } 00881 } 00882 00883 function getLines() { 00884 $this->_flushLine('~done'); 00885 return $this->_lines; 00886 } 00887 } 00888 00889 class WordLevelWikiDiff extends MappedWikiDiff 00890 { 00891 function WordLevelWikiDiff ($orig_lines, $closing_lines) { 00892 list ($orig_words, $orig_stripped) = $this->_split($orig_lines); 00893 list ($closing_words, $closing_stripped) = $this->_split($closing_lines); 00894 00895 00896 $this->MappedWikiDiff($orig_words, $closing_words, 00897 $orig_stripped, $closing_stripped); 00898 } 00899 00900 function _split($lines) { 00901 // FIXME: fix POSIX char class. 00902 # if (!preg_match_all('/ ( [^\S\n]+ | [[:alnum:]]+ | . ) (?: (?!< \n) [^\S\n])? /xs', 00903 if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs', 00904 implode("\n", $lines), 00905 $m)) { 00906 return array(array(''), array('')); 00907 } 00908 return array($m[0], $m[1]); 00909 } 00910 00911 function orig () { 00912 $orig = new _WikiHWLDF_WordAccumulator; 00913 00914 foreach ($this->edits as $edit) { 00915 if ($edit->type == 'copy') 00916 $orig->addWords($edit->orig); 00917 elseif ($edit->orig) 00918 $orig->addWords($edit->orig, 'mark'); 00919 } 00920 return $orig->getLines(); 00921 } 00922 00923 function closing () { 00924 $closing = new _WikiHWLDF_WordAccumulator; 00925 00926 foreach ($this->edits as $edit) { 00927 if ($edit->type == 'copy') 00928 $closing->addWords($edit->closing); 00929 elseif ($edit->closing) 00930 $closing->addWords($edit->closing, 'mark'); 00931 } 00932 return $closing->getLines(); 00933 } 00934 } 00935 00939 class TableWikiDiffFormatter extends WikiDiffFormatter 00940 { 00941 var $htmltable = array(); 00942 00943 function TableWikiDiffFormatter() { 00944 $this->leading_context_lines = 2; 00945 $this->trailing_context_lines = 2; 00946 } 00947 00948 function _block_header( $xbeg, $xlen, $ybeg, $ylen) { 00949 00950 } 00951 00952 function _start_block ($header) { 00953 00954 } 00955 00956 function _end_block() { 00957 00958 } 00959 00960 function _lines($lines, $prefix=' ', $color="white") { 00961 00962 } 00963 00964 function _added($lines) { 00965 global $htmltable; 00966 foreach ($lines as $line) { 00967 $htmltable[] = array('','+','<div class="wiki_diffadd">'.$line.'</div>'); 00968 } 00969 } 00970 00971 function _deleted($lines) { 00972 global $htmltable; 00973 foreach ($lines as $line) { 00974 $htmltable[] = array('<div class="wiki_diffdel">'.$line.'</div>','-',''); 00975 } 00976 } 00977 00978 function _context($lines) { 00979 global $htmltable; 00980 foreach ($lines as $line) { 00981 $htmltable[] = array($line,'',$line); 00982 } 00983 } 00984 00985 function _changed( $orig, $closing ) { 00986 global $htmltable; 00987 $diff = new WordLevelWikiDiff( $orig, $closing ); 00988 $del = $diff->orig(); 00989 $add = $diff->closing(); 00990 00991 while ( $line = array_shift( $del ) ) { 00992 $aline = array_shift( $add ); 00993 $htmltable[] = array('<div class="wiki_diffdel">'.$line.'</div>','-','<div class="wiki_diffadd">'.$aline.'</div>'); 00994 } 00995 $this->_added( $add ); # If any leftovers 00996 } 00997 00998 function get_result() { 00999 global $htmltable; 01000 return $htmltable; 01001 } 01002 01003 } 01004 01005 01010 class TableWikiDiffFormatterOld extends WikiDiffFormatter 01011 { 01012 function TableWikiDiffFormatter() { 01013 $this->leading_context_lines = 2; 01014 $this->trailing_context_lines = 2; 01015 } 01016 01017 function _block_header( $xbeg, $xlen, $ybeg, $ylen ) { 01018 $l1 = wfMsg( "lineno", $xbeg ); 01019 $l2 = wfMsg( "lineno", $ybeg ); 01020 01021 $r = '<tr><td colspan="2" align="left"><strong>'.$l1."</strong></td>\n" . 01022 '<td colspan="2" align="left"><strong>'.$l2."</strong></td></tr>\n"; 01023 return $r; 01024 } 01025 01026 function _start_block( $header ) { 01027 global $wgOut; 01028 $wgOut->addHTML( $header ); 01029 } 01030 01031 function _end_block() { 01032 } 01033 01034 function _lines( $lines, $prefix=' ', $color="white" ) { 01035 } 01036 01037 function addedLine( $line ) { 01038 return '<td>+</td><td class="diff-addedline">' . 01039 $line.'</td>'; 01040 } 01041 01042 function deletedLine( $line ) { 01043 return '<td>-</td><td class="diff-deletedline">' . 01044 $line.'</td>'; 01045 } 01046 01047 function emptyLine() { 01048 return '<td colspan="2"> </td>'; 01049 } 01050 01051 function contextLine( $line ) { 01052 return '<td> </td><td class="diff-context">'.$line.'</td>'; 01053 } 01054 01055 function _added($lines) { 01056 global $wgOut; 01057 foreach ($lines as $line) { 01058 $wgOut->addHTML( '<tr>' . $this->emptyLine() . 01059 $this->addedLine( $line ) . "</tr>\n" ); 01060 } 01061 } 01062 01063 function _deleted($lines) { 01064 global $wgOut; 01065 foreach ($lines as $line) { 01066 $wgOut->addHTML( '<tr>' . $this->deletedLine( $line ) . 01067 $this->emptyLine() . "</tr>\n" ); 01068 } 01069 } 01070 01071 function _context( $lines ) { 01072 global $wgOut; 01073 foreach ($lines as $line) { 01074 $wgOut->addHTML( '<tr>' . $this->contextLine( $line ) . 01075 $this->contextLine( $line ) . "</tr>\n" ); 01076 } 01077 } 01078 01079 function _changed( $orig, $closing ) { 01080 global $wgOut; 01081 $diff = new WordLevelWikiDiff( $orig, $closing ); 01082 $del = $diff->orig(); 01083 $add = $diff->closing(); 01084 01085 while ( $line = array_shift( $del ) ) { 01086 $aline = array_shift( $add ); 01087 $wgOut->addHTML( '<tr>' . $this->deletedLine( $line ) . 01088 $this->addedLine( $aline ) . "</tr>\n" ); 01089 } 01090 $this->_added( $add ); # If any leftovers 01091 } 01092 } 01093