00001 <?php
00006
00007
00008
00009
00010
00011
00012 define('USE_ASSERTS', function_exists('assert'));
00013
00019 class _DiffOp {
00020 var $type;
00021 var $orig;
00022 var $closing;
00023
00024 function reverse() {
00025 trigger_error('pure virtual', E_USER_ERROR);
00026 }
00027
00028 function norig() {
00029 return $this->orig ? sizeof($this->orig) : 0;
00030 }
00031
00032 function nclosing() {
00033 return $this->closing ? sizeof($this->closing) : 0;
00034 }
00035 }
00036
00042 class _DiffOp_Copy extends _DiffOp {
00043 var $type = 'copy';
00044
00045 function _DiffOp_Copy ($orig, $closing = false) {
00046 if (!is_array($closing))
00047 $closing = $orig;
00048 $this->orig = $orig;
00049 $this->closing = $closing;
00050 }
00051
00052 function reverse() {
00053 return new _DiffOp_Copy($this->closing, $this->orig);
00054 }
00055 }
00056
00062 class _DiffOp_Delete extends _DiffOp {
00063 var $type = 'delete';
00064
00065 function _DiffOp_Delete ($lines) {
00066 $this->orig = $lines;
00067 $this->closing = false;
00068 }
00069
00070 function reverse() {
00071 return new _DiffOp_Add($this->orig);
00072 }
00073 }
00074
00080 class _DiffOp_Add extends _DiffOp {
00081 var $type = 'add';
00082
00083 function _DiffOp_Add ($lines) {
00084 $this->closing = $lines;
00085 $this->orig = false;
00086 }
00087
00088 function reverse() {
00089 return new _DiffOp_Delete($this->closing);
00090 }
00091 }
00092
00098 class _DiffOp_Change extends _DiffOp {
00099 var $type = 'change';
00100
00101 function _DiffOp_Change ($orig, $closing) {
00102 $this->orig = $orig;
00103 $this->closing = $closing;
00104 }
00105
00106 function reverse() {
00107 return new _DiffOp_Change($this->closing, $this->orig);
00108 }
00109 }
00110
00135 class _DiffEngine {
00136
00137 const MAX_XREF_LENGTH = 10000;
00138
00139 function diff ($from_lines, $to_lines){
00140 wfProfileIn( __METHOD__ );
00141
00142
00143 $this->diff_local($from_lines, $to_lines);
00144
00145
00146 $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
00147 $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
00148
00149
00150 $n_from = sizeof($from_lines);
00151 $n_to = sizeof($to_lines);
00152
00153 $edits = array();
00154 $xi = $yi = 0;
00155 while ($xi < $n_from || $yi < $n_to) {
00156 USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
00157 USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
00158
00159
00160 $copy = array();
00161 while ( $xi < $n_from && $yi < $n_to
00162 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
00163 $copy[] = $from_lines[$xi++];
00164 ++$yi;
00165 }
00166 if ($copy)
00167 $edits[] = new _DiffOp_Copy($copy);
00168
00169
00170 $delete = array();
00171 while ($xi < $n_from && $this->xchanged[$xi])
00172 $delete[] = $from_lines[$xi++];
00173
00174 $add = array();
00175 while ($yi < $n_to && $this->ychanged[$yi])
00176 $add[] = $to_lines[$yi++];
00177
00178 if ($delete && $add)
00179 $edits[] = new _DiffOp_Change($delete, $add);
00180 elseif ($delete)
00181 $edits[] = new _DiffOp_Delete($delete);
00182 elseif ($add)
00183 $edits[] = new _DiffOp_Add($add);
00184 }
00185 wfProfileOut( __METHOD__ );
00186 return $edits;
00187 }
00188
00189 function diff_local ($from_lines, $to_lines) {
00190 global $wgExternalDiffEngine;
00191 wfProfileIn( __METHOD__);
00192
00193 if($wgExternalDiffEngine == 'wikidiff3'){
00194
00195 $wikidiff3 = new WikiDiff3();
00196 $wikidiff3->diff($from_lines, $to_lines);
00197 $this->xchanged = $wikidiff3->removed;
00198 $this->ychanged = $wikidiff3->added;
00199 unset($wikidiff3);
00200 }else{
00201
00202 $n_from = sizeof($from_lines);
00203 $n_to = sizeof($to_lines);
00204 $this->xchanged = $this->ychanged = array();
00205 $this->xv = $this->yv = array();
00206 $this->xind = $this->yind = array();
00207 unset($this->seq);
00208 unset($this->in_seq);
00209 unset($this->lcs);
00210
00211
00212 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
00213 if ($from_lines[$skip] !== $to_lines[$skip])
00214 break;
00215 $this->xchanged[$skip] = $this->ychanged[$skip] = false;
00216 }
00217
00218 $xi = $n_from; $yi = $n_to;
00219 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
00220 if ($from_lines[$xi] !== $to_lines[$yi])
00221 break;
00222 $this->xchanged[$xi] = $this->ychanged[$yi] = false;
00223 }
00224
00225
00226 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
00227 $xhash[$this->_line_hash($from_lines[$xi])] = 1;
00228 }
00229
00230 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
00231 $line = $to_lines[$yi];
00232 if ( ($this->ychanged[$yi] = empty($xhash[$this->_line_hash($line)])) )
00233 continue;
00234 $yhash[$this->_line_hash($line)] = 1;
00235 $this->yv[] = $line;
00236 $this->yind[] = $yi;
00237 }
00238 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
00239 $line = $from_lines[$xi];
00240 if ( ($this->xchanged[$xi] = empty($yhash[$this->_line_hash($line)])) )
00241 continue;
00242 $this->xv[] = $line;
00243 $this->xind[] = $xi;
00244 }
00245
00246
00247 $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv));
00248 }
00249 wfProfileOut( __METHOD__ );
00250 }
00251
00255 function _line_hash( $line ) {
00256 if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
00257 return md5( $line );
00258 } else {
00259 return $line;
00260 }
00261 }
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279 function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
00280 $flip = false;
00281
00282 if ($xlim - $xoff > $ylim - $yoff) {
00283
00284
00285 $flip = true;
00286 list ($xoff, $xlim, $yoff, $ylim)
00287 = array( $yoff, $ylim, $xoff, $xlim);
00288 }
00289
00290 if ($flip)
00291 for ($i = $ylim - 1; $i >= $yoff; $i--)
00292 $ymatches[$this->xv[$i]][] = $i;
00293 else
00294 for ($i = $ylim - 1; $i >= $yoff; $i--)
00295 $ymatches[$this->yv[$i]][] = $i;
00296
00297 $this->lcs = 0;
00298 $this->seq[0]= $yoff - 1;
00299 $this->in_seq = array();
00300 $ymids[0] = array();
00301
00302 $numer = $xlim - $xoff + $nchunks - 1;
00303 $x = $xoff;
00304 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
00305 if ($chunk > 0)
00306 for ($i = 0; $i <= $this->lcs; $i++)
00307 $ymids[$i][$chunk-1] = $this->seq[$i];
00308
00309 $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
00310 for ( ; $x < $x1; $x++) {
00311 $line = $flip ? $this->yv[$x] : $this->xv[$x];
00312 if (empty($ymatches[$line]))
00313 continue;
00314 $matches = $ymatches[$line];
00315 reset($matches);
00316 while (list ($junk, $y) = each($matches))
00317 if (empty($this->in_seq[$y])) {
00318 $k = $this->_lcs_pos($y);
00319 USE_ASSERTS && assert($k > 0);
00320 $ymids[$k] = $ymids[$k-1];
00321 break;
00322 }
00323 while (list ( , $y) = each($matches)) {
00324 if ($y > $this->seq[$k-1]) {
00325 USE_ASSERTS && assert($y < $this->seq[$k]);
00326
00327
00328 $this->in_seq[$this->seq[$k]] = false;
00329 $this->seq[$k] = $y;
00330 $this->in_seq[$y] = 1;
00331 } else if (empty($this->in_seq[$y])) {
00332 $k = $this->_lcs_pos($y);
00333 USE_ASSERTS && assert($k > 0);
00334 $ymids[$k] = $ymids[$k-1];
00335 }
00336 }
00337 }
00338 }
00339
00340 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
00341 $ymid = $ymids[$this->lcs];
00342 for ($n = 0; $n < $nchunks - 1; $n++) {
00343 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
00344 $y1 = $ymid[$n] + 1;
00345 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
00346 }
00347 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
00348
00349 return array($this->lcs, $seps);
00350 }
00351
00352 function _lcs_pos ($ypos) {
00353 $end = $this->lcs;
00354 if ($end == 0 || $ypos > $this->seq[$end]) {
00355 $this->seq[++$this->lcs] = $ypos;
00356 $this->in_seq[$ypos] = 1;
00357 return $this->lcs;
00358 }
00359
00360 $beg = 1;
00361 while ($beg < $end) {
00362 $mid = (int)(($beg + $end) / 2);
00363 if ( $ypos > $this->seq[$mid] )
00364 $beg = $mid + 1;
00365 else
00366 $end = $mid;
00367 }
00368
00369 USE_ASSERTS && assert($ypos != $this->seq[$end]);
00370
00371 $this->in_seq[$this->seq[$end]] = false;
00372 $this->seq[$end] = $ypos;
00373 $this->in_seq[$ypos] = 1;
00374 return $end;
00375 }
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388 function _compareseq ($xoff, $xlim, $yoff, $ylim) {
00389
00390 while ($xoff < $xlim && $yoff < $ylim
00391 && $this->xv[$xoff] == $this->yv[$yoff]) {
00392 ++$xoff;
00393 ++$yoff;
00394 }
00395
00396
00397 while ($xlim > $xoff && $ylim > $yoff
00398 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
00399 --$xlim;
00400 --$ylim;
00401 }
00402
00403 if ($xoff == $xlim || $yoff == $ylim)
00404 $lcs = 0;
00405 else {
00406
00407
00408
00409 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
00410 list ($lcs, $seps)
00411 = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
00412 }
00413
00414 if ($lcs == 0) {
00415
00416
00417 while ($yoff < $ylim)
00418 $this->ychanged[$this->yind[$yoff++]] = 1;
00419 while ($xoff < $xlim)
00420 $this->xchanged[$this->xind[$xoff++]] = 1;
00421 } else {
00422
00423 reset($seps);
00424 $pt1 = $seps[0];
00425 while ($pt2 = next($seps)) {
00426 $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
00427 $pt1 = $pt2;
00428 }
00429 }
00430 }
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444 function _shift_boundaries ($lines, &$changed, $other_changed) {
00445 wfProfileIn( __METHOD__ );
00446 $i = 0;
00447 $j = 0;
00448
00449 USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)');
00450 $len = sizeof($lines);
00451 $other_len = sizeof($other_changed);
00452
00453 while (1) {
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465 while ($j < $other_len && $other_changed[$j])
00466 $j++;
00467
00468 while ($i < $len && ! $changed[$i]) {
00469 USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
00470 $i++; $j++;
00471 while ($j < $other_len && $other_changed[$j])
00472 $j++;
00473 }
00474
00475 if ($i == $len)
00476 break;
00477
00478 $start = $i;
00479
00480
00481 while (++$i < $len && $changed[$i])
00482 continue;
00483
00484 do {
00485
00486
00487
00488
00489 $runlength = $i - $start;
00490
00491
00492
00493
00494
00495
00496 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
00497 $changed[--$start] = 1;
00498 $changed[--$i] = false;
00499 while ($start > 0 && $changed[$start - 1])
00500 $start--;
00501 USE_ASSERTS && assert('$j > 0');
00502 while ($other_changed[--$j])
00503 continue;
00504 USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
00505 }
00506
00507
00508
00509
00510
00511
00512 $corresponding = $j < $other_len ? $i : $len;
00513
00514
00515
00516
00517
00518
00519
00520
00521 while ($i < $len && $lines[$start] == $lines[$i]) {
00522 $changed[$start++] = false;
00523 $changed[$i++] = 1;
00524 while ($i < $len && $changed[$i])
00525 $i++;
00526
00527 USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
00528 $j++;
00529 if ($j < $other_len && $other_changed[$j]) {
00530 $corresponding = $i;
00531 while ($j < $other_len && $other_changed[$j])
00532 $j++;
00533 }
00534 }
00535 } while ($runlength != $i - $start);
00536
00537
00538
00539
00540
00541 while ($corresponding < $i) {
00542 $changed[--$start] = 1;
00543 $changed[--$i] = 0;
00544 USE_ASSERTS && assert('$j > 0');
00545 while ($other_changed[--$j])
00546 continue;
00547 USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
00548 }
00549 }
00550 wfProfileOut( __METHOD__ );
00551 }
00552 }
00553
00560 class Diff
00561 {
00562 var $edits;
00563
00572 function Diff($from_lines, $to_lines) {
00573 $eng = new _DiffEngine;
00574 $this->edits = $eng->diff($from_lines, $to_lines);
00575
00576 }
00577
00588 function reverse () {
00589 $rev = $this;
00590 $rev->edits = array();
00591 foreach ($this->edits as $edit) {
00592 $rev->edits[] = $edit->reverse();
00593 }
00594 return $rev;
00595 }
00596
00602 function isEmpty () {
00603 foreach ($this->edits as $edit) {
00604 if ($edit->type != 'copy')
00605 return false;
00606 }
00607 return true;
00608 }
00609
00617 function lcs () {
00618 $lcs = 0;
00619 foreach ($this->edits as $edit) {
00620 if ($edit->type == 'copy')
00621 $lcs += sizeof($edit->orig);
00622 }
00623 return $lcs;
00624 }
00625
00634 function orig() {
00635 $lines = array();
00636
00637 foreach ($this->edits as $edit) {
00638 if ($edit->orig)
00639 array_splice($lines, sizeof($lines), 0, $edit->orig);
00640 }
00641 return $lines;
00642 }
00643
00652 function closing() {
00653 $lines = array();
00654
00655 foreach ($this->edits as $edit) {
00656 if ($edit->closing)
00657 array_splice($lines, sizeof($lines), 0, $edit->closing);
00658 }
00659 return $lines;
00660 }
00661
00667 function _check ($from_lines, $to_lines) {
00668 wfProfileIn( __METHOD__ );
00669 if (serialize($from_lines) != serialize($this->orig()))
00670 trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
00671 if (serialize($to_lines) != serialize($this->closing()))
00672 trigger_error("Reconstructed closing doesn't match", E_USER_ERROR);
00673
00674 $rev = $this->reverse();
00675 if (serialize($to_lines) != serialize($rev->orig()))
00676 trigger_error("Reversed original doesn't match", E_USER_ERROR);
00677 if (serialize($from_lines) != serialize($rev->closing()))
00678 trigger_error("Reversed closing doesn't match", E_USER_ERROR);
00679
00680
00681 $prevtype = 'none';
00682 foreach ($this->edits as $edit) {
00683 if ( $prevtype == $edit->type )
00684 trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
00685 $prevtype = $edit->type;
00686 }
00687
00688 $lcs = $this->lcs();
00689 trigger_error('Diff okay: LCS = '.$lcs, E_USER_NOTICE);
00690 wfProfileOut( __METHOD__ );
00691 }
00692 }
00693
00699 class MappedDiff extends Diff
00700 {
00724 function MappedDiff($from_lines, $to_lines,
00725 $mapped_from_lines, $mapped_to_lines) {
00726 wfProfileIn( __METHOD__ );
00727
00728 assert(sizeof($from_lines) == sizeof($mapped_from_lines));
00729 assert(sizeof($to_lines) == sizeof($mapped_to_lines));
00730
00731 $this->Diff($mapped_from_lines, $mapped_to_lines);
00732
00733 $xi = $yi = 0;
00734 for ($i = 0; $i < sizeof($this->edits); $i++) {
00735 $orig = &$this->edits[$i]->orig;
00736 if (is_array($orig)) {
00737 $orig = array_slice($from_lines, $xi, sizeof($orig));
00738 $xi += sizeof($orig);
00739 }
00740
00741 $closing = &$this->edits[$i]->closing;
00742 if (is_array($closing)) {
00743 $closing = array_slice($to_lines, $yi, sizeof($closing));
00744 $yi += sizeof($closing);
00745 }
00746 }
00747 wfProfileOut( __METHOD__ );
00748 }
00749 }
00750
00761 class DiffFormatter {
00768 var $leading_context_lines = 0;
00769
00776 var $trailing_context_lines = 0;
00777
00784 function format($diff) {
00785 wfProfileIn( __METHOD__ );
00786
00787 $xi = $yi = 1;
00788 $block = false;
00789 $context = array();
00790
00791 $nlead = $this->leading_context_lines;
00792 $ntrail = $this->trailing_context_lines;
00793
00794 $this->_start_diff();
00795
00796 foreach ($diff->edits as $edit) {
00797 if ($edit->type == 'copy') {
00798 if (is_array($block)) {
00799 if (sizeof($edit->orig) <= $nlead + $ntrail) {
00800 $block[] = $edit;
00801 }
00802 else{
00803 if ($ntrail) {
00804 $context = array_slice($edit->orig, 0, $ntrail);
00805 $block[] = new _DiffOp_Copy($context);
00806 }
00807 $this->_block($x0, $ntrail + $xi - $x0,
00808 $y0, $ntrail + $yi - $y0,
00809 $block);
00810 $block = false;
00811 }
00812 }
00813 $context = $edit->orig;
00814 }
00815 else {
00816 if (! is_array($block)) {
00817 $context = array_slice($context, sizeof($context) - $nlead);
00818 $x0 = $xi - sizeof($context);
00819 $y0 = $yi - sizeof($context);
00820 $block = array();
00821 if ($context)
00822 $block[] = new _DiffOp_Copy($context);
00823 }
00824 $block[] = $edit;
00825 }
00826
00827 if ($edit->orig)
00828 $xi += sizeof($edit->orig);
00829 if ($edit->closing)
00830 $yi += sizeof($edit->closing);
00831 }
00832
00833 if (is_array($block))
00834 $this->_block($x0, $xi - $x0,
00835 $y0, $yi - $y0,
00836 $block);
00837
00838 $end = $this->_end_diff();
00839 wfProfileOut( __METHOD__ );
00840 return $end;
00841 }
00842
00843 function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
00844 wfProfileIn( __METHOD__ );
00845 $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
00846 foreach ($edits as $edit) {
00847 if ($edit->type == 'copy')
00848 $this->_context($edit->orig);
00849 elseif ($edit->type == 'add')
00850 $this->_added($edit->closing);
00851 elseif ($edit->type == 'delete')
00852 $this->_deleted($edit->orig);
00853 elseif ($edit->type == 'change')
00854 $this->_changed($edit->orig, $edit->closing);
00855 else
00856 trigger_error('Unknown edit type', E_USER_ERROR);
00857 }
00858 $this->_end_block();
00859 wfProfileOut( __METHOD__ );
00860 }
00861
00862 function _start_diff() {
00863 ob_start();
00864 }
00865
00866 function _end_diff() {
00867 $val = ob_get_contents();
00868 ob_end_clean();
00869 return $val;
00870 }
00871
00872 function _block_header($xbeg, $xlen, $ybeg, $ylen) {
00873 if ($xlen > 1)
00874 $xbeg .= "," . ($xbeg + $xlen - 1);
00875 if ($ylen > 1)
00876 $ybeg .= "," . ($ybeg + $ylen - 1);
00877
00878 return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
00879 }
00880
00881 function _start_block($header) {
00882 echo $header . "\n";
00883 }
00884
00885 function _end_block() {
00886 }
00887
00888 function _lines($lines, $prefix = ' ') {
00889 foreach ($lines as $line)
00890 echo "$prefix $line\n";
00891 }
00892
00893 function _context($lines) {
00894 $this->_lines($lines);
00895 }
00896
00897 function _added($lines) {
00898 $this->_lines($lines, '>');
00899 }
00900 function _deleted($lines) {
00901 $this->_lines($lines, '<');
00902 }
00903
00904 function _changed($orig, $closing) {
00905 $this->_deleted($orig);
00906 echo "---\n";
00907 $this->_added($closing);
00908 }
00909 }
00910
00916 class UnifiedDiffFormatter extends DiffFormatter {
00917 var $leading_context_lines = 2;
00918 var $trailing_context_lines = 2;
00919
00920 function _added($lines) {
00921 $this->_lines($lines, '+');
00922 }
00923 function _deleted($lines) {
00924 $this->_lines($lines, '-');
00925 }
00926 function _changed($orig, $closing) {
00927 $this->_deleted($orig);
00928 $this->_added($closing);
00929 }
00930 function _block_header($xbeg, $xlen, $ybeg, $ylen) {
00931 return "@@ -$xbeg,$xlen +$ybeg,$ylen @@";
00932 }
00933 }
00934
00939 class ArrayDiffFormatter extends DiffFormatter {
00940 function format($diff) {
00941 $oldline = 1;
00942 $newline = 1;
00943 $retval = array();
00944 foreach($diff->edits as $edit)
00945 switch($edit->type) {
00946 case 'add':
00947 foreach($edit->closing as $l) {
00948 $retval[] = array(
00949 'action' => 'add',
00950 'new'=> $l,
00951 'newline' => $newline++
00952 );
00953 }
00954 break;
00955 case 'delete':
00956 foreach($edit->orig as $l) {
00957 $retval[] = array(
00958 'action' => 'delete',
00959 'old' => $l,
00960 'oldline' => $oldline++,
00961 );
00962 }
00963 break;
00964 case 'change':
00965 foreach($edit->orig as $i => $l) {
00966 $retval[] = array(
00967 'action' => 'change',
00968 'old' => $l,
00969 'new' => @$edit->closing[$i],
00970 'oldline' => $oldline++,
00971 'newline' => $newline++,
00972 );
00973 }
00974 break;
00975 case 'copy':
00976 $oldline += count($edit->orig);
00977 $newline += count($edit->orig);
00978 }
00979 return $retval;
00980 }
00981 }
00982
00988 define('NBSP', ' ');
00989
00995 class _HWLDF_WordAccumulator {
00996 function _HWLDF_WordAccumulator () {
00997 $this->_lines = array();
00998 $this->_line = '';
00999 $this->_group = '';
01000 $this->_tag = '';
01001 }
01002
01003 function _flushGroup ($new_tag) {
01004 if ($this->_group !== '') {
01005 if ($this->_tag == 'ins')
01006 $this->_line .= '<ins class="diffchange diffchange-inline">' .
01007 htmlspecialchars ( $this->_group ) . '</ins>';
01008 elseif ($this->_tag == 'del')
01009 $this->_line .= '<del class="diffchange diffchange-inline">' .
01010 htmlspecialchars ( $this->_group ) . '</del>';
01011 else
01012 $this->_line .= htmlspecialchars ( $this->_group );
01013 }
01014 $this->_group = '';
01015 $this->_tag = $new_tag;
01016 }
01017
01018 function _flushLine ($new_tag) {
01019 $this->_flushGroup($new_tag);
01020 if ($this->_line != '')
01021 array_push ( $this->_lines, $this->_line );
01022 else
01023 # make empty lines visible by inserting an NBSP
01024 array_push ( $this->_lines, NBSP );
01025 $this->_line = '';
01026 }
01027
01028 function addWords ($words, $tag = '') {
01029 if ($tag != $this->_tag)
01030 $this->_flushGroup($tag);
01031
01032 foreach ($words as $word) {
01033
01034 if ($word == '')
01035 continue;
01036 if ($word[0] == "\n") {
01037 $this->_flushLine($tag);
01038 $word = substr($word, 1);
01039 }
01040 assert(!strstr($word, "\n"));
01041 $this->_group .= $word;
01042 }
01043 }
01044
01045 function getLines() {
01046 $this->_flushLine('~done');
01047 return $this->_lines;
01048 }
01049 }
01050
01056 class WordLevelDiff extends MappedDiff {
01057 const MAX_LINE_LENGTH = 10000;
01058
01059 function WordLevelDiff ($orig_lines, $closing_lines) {
01060 wfProfileIn( __METHOD__ );
01061
01062 list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
01063 list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
01064
01065 $this->MappedDiff($orig_words, $closing_words,
01066 $orig_stripped, $closing_stripped);
01067 wfProfileOut( __METHOD__ );
01068 }
01069
01070 function _split($lines) {
01071 wfProfileIn( __METHOD__ );
01072
01073 $words = array();
01074 $stripped = array();
01075 $first = true;
01076 foreach ( $lines as $line ) {
01077 # If the line is too long, just pretend the entire line is one big word
01078 # This prevents resource exhaustion problems
01079 if ( $first ) {
01080 $first = false;
01081 } else {
01082 $words[] = "\n";
01083 $stripped[] = "\n";
01084 }
01085 if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
01086 $words[] = $line;
01087 $stripped[] = $line;
01088 } else {
01089 $m = array();
01090 if (preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
01091 $line, $m))
01092 {
01093 $words = array_merge( $words, $m[0] );
01094 $stripped = array_merge( $stripped, $m[1] );
01095 }
01096 }
01097 }
01098 wfProfileOut( __METHOD__ );
01099 return array($words, $stripped);
01100 }
01101
01102 function orig () {
01103 wfProfileIn( __METHOD__ );
01104 $orig = new _HWLDF_WordAccumulator;
01105
01106 foreach ($this->edits as $edit) {
01107 if ($edit->type == 'copy')
01108 $orig->addWords($edit->orig);
01109 elseif ($edit->orig)
01110 $orig->addWords($edit->orig, 'del');
01111 }
01112 $lines = $orig->getLines();
01113 wfProfileOut( __METHOD__ );
01114 return $lines;
01115 }
01116
01117 function closing () {
01118 wfProfileIn( __METHOD__ );
01119 $closing = new _HWLDF_WordAccumulator;
01120
01121 foreach ($this->edits as $edit) {
01122 if ($edit->type == 'copy')
01123 $closing->addWords($edit->closing);
01124 elseif ($edit->closing)
01125 $closing->addWords($edit->closing, 'ins');
01126 }
01127 $lines = $closing->getLines();
01128 wfProfileOut( __METHOD__ );
01129 return $lines;
01130 }
01131 }
01132
01139 class TableDiffFormatter extends DiffFormatter {
01140 function TableDiffFormatter() {
01141 $this->leading_context_lines = 2;
01142 $this->trailing_context_lines = 2;
01143 }
01144
01145 public static function escapeWhiteSpace( $msg ) {
01146 $msg = preg_replace( '/^ /m', ' ', $msg );
01147 $msg = preg_replace( '/ $/m', ' ', $msg );
01148 $msg = preg_replace( '/ /', ' ', $msg );
01149 return $msg;
01150 }
01151
01152 function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
01153 $r = '<tr><td colspan="2" class="diff-lineno"><!--LINE '.$xbeg."--></td>\n" .
01154 '<td colspan="2" class="diff-lineno"><!--LINE '.$ybeg."--></td></tr>\n";
01155 return $r;
01156 }
01157
01158 function _start_block( $header ) {
01159 echo $header;
01160 }
01161
01162 function _end_block() {
01163 }
01164
01165 function _lines( $lines, $prefix=' ', $color='white' ) {
01166 }
01167
01168 # HTML-escape parameter before calling this
01169 function addedLine( $line ) {
01170 return $this->wrapLine( '+', 'diff-addedline', $line );
01171 }
01172
01173 # HTML-escape parameter before calling this
01174 function deletedLine( $line ) {
01175 return $this->wrapLine( '-', 'diff-deletedline', $line );
01176 }
01177
01178 # HTML-escape parameter before calling this
01179 function contextLine( $line ) {
01180 return $this->wrapLine( ' ', 'diff-context', $line );
01181 }
01182
01183 private function wrapLine( $marker, $class, $line ) {
01184 if( $line !== '' ) {
01185
01186 $line = Xml::tags( 'div', null, $this->escapeWhiteSpace( $line ) );
01187 }
01188 return "<td class='diff-marker'>$marker</td><td class='$class'>$line</td>";
01189 }
01190
01191 function emptyLine() {
01192 return '<td colspan="2"> </td>';
01193 }
01194
01195 function _added( $lines ) {
01196 foreach ($lines as $line) {
01197 echo '<tr>' . $this->emptyLine() .
01198 $this->addedLine( '<ins class="diffchange">' .
01199 htmlspecialchars ( $line ) . '</ins>' ) . "</tr>\n";
01200 }
01201 }
01202
01203 function _deleted($lines) {
01204 foreach ($lines as $line) {
01205 echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
01206 htmlspecialchars ( $line ) . '</del>' ) .
01207 $this->emptyLine() . "</tr>\n";
01208 }
01209 }
01210
01211 function _context( $lines ) {
01212 foreach ($lines as $line) {
01213 echo '<tr>' .
01214 $this->contextLine( htmlspecialchars ( $line ) ) .
01215 $this->contextLine( htmlspecialchars ( $line ) ) . "</tr>\n";
01216 }
01217 }
01218
01219 function _changed( $orig, $closing ) {
01220 wfProfileIn( __METHOD__ );
01221
01222 $diff = new WordLevelDiff( $orig, $closing );
01223 $del = $diff->orig();
01224 $add = $diff->closing();
01225
01226 # Notice that WordLevelDiff returns HTML-escaped output.
01227 # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
01228
01229 while ( $line = array_shift( $del ) ) {
01230 $aline = array_shift( $add );
01231 echo '<tr>' . $this->deletedLine( $line ) .
01232 $this->addedLine( $aline ) . "</tr>\n";
01233 }
01234 foreach ($add as $line) { # If any leftovers
01235 echo '<tr>' . $this->emptyLine() .
01236 $this->addedLine( $line ) . "</tr>\n";
01237 }
01238 wfProfileOut( __METHOD__ );
01239 }
01240 }