MediaWiki
REL1_23
|
00001 <?php 00033 abstract class DiffOp { 00034 00038 public $type; 00039 00043 public $orig; 00044 00048 public $closing; 00049 00053 public function getType() { 00054 return $this->type; 00055 } 00056 00060 public function getOrig() { 00061 return $this->orig; 00062 } 00063 00068 public function getClosing( $i = null ) { 00069 if ( $i === null ) { 00070 return $this->closing; 00071 } 00072 if ( array_key_exists( $i, $this->closing ) ) { 00073 return $this->closing[$i]; 00074 } 00075 return null; 00076 } 00077 00078 abstract public function reverse(); 00079 00083 public function norig() { 00084 return $this->orig ? count( $this->orig ) : 0; 00085 } 00086 00090 public function nclosing() { 00091 return $this->closing ? count( $this->closing ) : 0; 00092 } 00093 } 00094 00100 class DiffOpCopy extends DiffOp { 00101 public $type = 'copy'; 00102 00103 public function __construct( $orig, $closing = false ) { 00104 if ( !is_array( $closing ) ) { 00105 $closing = $orig; 00106 } 00107 $this->orig = $orig; 00108 $this->closing = $closing; 00109 } 00110 00114 public function reverse() { 00115 return new DiffOpCopy( $this->closing, $this->orig ); 00116 } 00117 } 00118 00124 class DiffOpDelete extends DiffOp { 00125 public $type = 'delete'; 00126 00127 public function __construct( $lines ) { 00128 $this->orig = $lines; 00129 $this->closing = false; 00130 } 00131 00135 public function reverse() { 00136 return new DiffOpAdd( $this->orig ); 00137 } 00138 } 00139 00145 class DiffOpAdd extends DiffOp { 00146 public $type = 'add'; 00147 00148 public function __construct( $lines ) { 00149 $this->closing = $lines; 00150 $this->orig = false; 00151 } 00152 00156 public function reverse() { 00157 return new DiffOpDelete( $this->closing ); 00158 } 00159 } 00160 00166 class DiffOpChange extends DiffOp { 00167 public $type = 'change'; 00168 00169 public function __construct( $orig, $closing ) { 00170 $this->orig = $orig; 00171 $this->closing = $closing; 00172 } 00173 00177 public function reverse() { 00178 return new DiffOpChange( $this->closing, $this->orig ); 00179 } 00180 } 00181 00206 class DiffEngine { 00207 const MAX_XREF_LENGTH = 10000; 00208 00209 protected $xchanged, $ychanged; 00210 00211 protected $xv = array(), $yv = array(); 00212 protected $xind = array(), $yind = array(); 00213 00214 protected $seq = array(), $in_seq = array(); 00215 00216 protected $lcs = 0; 00217 00224 public function diff( $from_lines, $to_lines ) { 00225 wfProfileIn( __METHOD__ ); 00226 00227 // Diff and store locally 00228 $this->diffLocal( $from_lines, $to_lines ); 00229 00230 // Merge edits when possible 00231 $this->shiftBoundaries( $from_lines, $this->xchanged, $this->ychanged ); 00232 $this->shiftBoundaries( $to_lines, $this->ychanged, $this->xchanged ); 00233 00234 // Compute the edit operations. 00235 $n_from = count( $from_lines ); 00236 $n_to = count( $to_lines ); 00237 00238 $edits = array(); 00239 $xi = $yi = 0; 00240 while ( $xi < $n_from || $yi < $n_to ) { 00241 assert( '$yi < $n_to || $this->xchanged[$xi]' ); 00242 assert( '$xi < $n_from || $this->ychanged[$yi]' ); 00243 00244 // Skip matching "snake". 00245 $copy = array(); 00246 while ( $xi < $n_from && $yi < $n_to 00247 && !$this->xchanged[$xi] && !$this->ychanged[$yi] 00248 ) { 00249 $copy[] = $from_lines[$xi++]; 00250 ++$yi; 00251 } 00252 if ( $copy ) { 00253 $edits[] = new DiffOpCopy( $copy ); 00254 } 00255 00256 // Find deletes & adds. 00257 $delete = array(); 00258 while ( $xi < $n_from && $this->xchanged[$xi] ) { 00259 $delete[] = $from_lines[$xi++]; 00260 } 00261 00262 $add = array(); 00263 while ( $yi < $n_to && $this->ychanged[$yi] ) { 00264 $add[] = $to_lines[$yi++]; 00265 } 00266 00267 if ( $delete && $add ) { 00268 $edits[] = new DiffOpChange( $delete, $add ); 00269 } elseif ( $delete ) { 00270 $edits[] = new DiffOpDelete( $delete ); 00271 } elseif ( $add ) { 00272 $edits[] = new DiffOpAdd( $add ); 00273 } 00274 } 00275 wfProfileOut( __METHOD__ ); 00276 00277 return $edits; 00278 } 00279 00284 private function diffLocal( $from_lines, $to_lines ) { 00285 global $wgExternalDiffEngine; 00286 wfProfileIn( __METHOD__ ); 00287 00288 if ( $wgExternalDiffEngine == 'wikidiff3' ) { 00289 // wikidiff3 00290 $wikidiff3 = new WikiDiff3(); 00291 $wikidiff3->diff( $from_lines, $to_lines ); 00292 $this->xchanged = $wikidiff3->removed; 00293 $this->ychanged = $wikidiff3->added; 00294 unset( $wikidiff3 ); 00295 } else { 00296 // old diff 00297 $n_from = count( $from_lines ); 00298 $n_to = count( $to_lines ); 00299 $this->xchanged = $this->ychanged = array(); 00300 $this->xv = $this->yv = array(); 00301 $this->xind = $this->yind = array(); 00302 unset( $this->seq ); 00303 unset( $this->in_seq ); 00304 unset( $this->lcs ); 00305 00306 // Skip leading common lines. 00307 for ( $skip = 0; $skip < $n_from && $skip < $n_to; $skip++ ) { 00308 if ( $from_lines[$skip] !== $to_lines[$skip] ) { 00309 break; 00310 } 00311 $this->xchanged[$skip] = $this->ychanged[$skip] = false; 00312 } 00313 // Skip trailing common lines. 00314 $xi = $n_from; 00315 $yi = $n_to; 00316 for ( $endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++ ) { 00317 if ( $from_lines[$xi] !== $to_lines[$yi] ) { 00318 break; 00319 } 00320 $this->xchanged[$xi] = $this->ychanged[$yi] = false; 00321 } 00322 00323 // Ignore lines which do not exist in both files. 00324 for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) { 00325 $xhash[$this->lineHash( $from_lines[$xi] )] = 1; 00326 } 00327 00328 for ( $yi = $skip; $yi < $n_to - $endskip; $yi++ ) { 00329 $line = $to_lines[$yi]; 00330 if ( ( $this->ychanged[$yi] = empty( $xhash[$this->lineHash( $line )] ) ) ) { 00331 continue; 00332 } 00333 $yhash[$this->lineHash( $line )] = 1; 00334 $this->yv[] = $line; 00335 $this->yind[] = $yi; 00336 } 00337 for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) { 00338 $line = $from_lines[$xi]; 00339 if ( ( $this->xchanged[$xi] = empty( $yhash[$this->lineHash( $line )] ) ) ) { 00340 continue; 00341 } 00342 $this->xv[] = $line; 00343 $this->xind[] = $xi; 00344 } 00345 00346 // Find the LCS. 00347 $this->compareSeq( 0, count( $this->xv ), 0, count( $this->yv ) ); 00348 } 00349 wfProfileOut( __METHOD__ ); 00350 } 00351 00359 private function lineHash( $line ) { 00360 if ( strlen( $line ) > self::MAX_XREF_LENGTH ) { 00361 return md5( $line ); 00362 } else { 00363 return $line; 00364 } 00365 } 00366 00392 private function diag( $xoff, $xlim, $yoff, $ylim, $nchunks ) { 00393 $flip = false; 00394 00395 if ( $xlim - $xoff > $ylim - $yoff ) { 00396 // Things seems faster (I'm not sure I understand why) 00397 // when the shortest sequence in X. 00398 $flip = true; 00399 list( $xoff, $xlim, $yoff, $ylim ) = array( $yoff, $ylim, $xoff, $xlim ); 00400 } 00401 00402 if ( $flip ) { 00403 for ( $i = $ylim - 1; $i >= $yoff; $i-- ) { 00404 $ymatches[$this->xv[$i]][] = $i; 00405 } 00406 } else { 00407 for ( $i = $ylim - 1; $i >= $yoff; $i-- ) { 00408 $ymatches[$this->yv[$i]][] = $i; 00409 } 00410 } 00411 00412 $this->lcs = 0; 00413 $this->seq[0] = $yoff - 1; 00414 $this->in_seq = array(); 00415 $ymids[0] = array(); 00416 00417 $numer = $xlim - $xoff + $nchunks - 1; 00418 $x = $xoff; 00419 for ( $chunk = 0; $chunk < $nchunks; $chunk++ ) { 00420 if ( $chunk > 0 ) { 00421 for ( $i = 0; $i <= $this->lcs; $i++ ) { 00422 $ymids[$i][$chunk - 1] = $this->seq[$i]; 00423 } 00424 } 00425 00426 $x1 = $xoff + (int)( ( $numer + ( $xlim - $xoff ) * $chunk ) / $nchunks ); 00427 for ( ; $x < $x1; $x++ ) { 00428 $line = $flip ? $this->yv[$x] : $this->xv[$x]; 00429 if ( empty( $ymatches[$line] ) ) { 00430 continue; 00431 } 00432 00433 $k = 0; 00434 $matches = $ymatches[$line]; 00435 reset( $matches ); 00436 while ( list( , $y ) = each( $matches ) ) { 00437 if ( empty( $this->in_seq[$y] ) ) { 00438 $k = $this->lcsPos( $y ); 00439 assert( '$k > 0' ); 00440 $ymids[$k] = $ymids[$k - 1]; 00441 break; 00442 } 00443 } 00444 00445 while ( list( , $y ) = each( $matches ) ) { 00446 if ( $y > $this->seq[$k - 1] ) { 00447 assert( '$y < $this->seq[$k]' ); 00448 // Optimization: this is a common case: 00449 // next match is just replacing previous match. 00450 $this->in_seq[$this->seq[$k]] = false; 00451 $this->seq[$k] = $y; 00452 $this->in_seq[$y] = 1; 00453 } elseif ( empty( $this->in_seq[$y] ) ) { 00454 $k = $this->lcsPos( $y ); 00455 assert( '$k > 0' ); 00456 $ymids[$k] = $ymids[$k - 1]; 00457 } 00458 } 00459 } 00460 } 00461 00462 $seps[] = $flip ? array( $yoff, $xoff ) : array( $xoff, $yoff ); 00463 $ymid = $ymids[$this->lcs]; 00464 for ( $n = 0; $n < $nchunks - 1; $n++ ) { 00465 $x1 = $xoff + (int)( ( $numer + ( $xlim - $xoff ) * $n ) / $nchunks ); 00466 $y1 = $ymid[$n] + 1; 00467 $seps[] = $flip ? array( $y1, $x1 ) : array( $x1, $y1 ); 00468 } 00469 $seps[] = $flip ? array( $ylim, $xlim ) : array( $xlim, $ylim ); 00470 00471 return array( $this->lcs, $seps ); 00472 } 00473 00479 private function lcsPos( $ypos ) { 00480 $end = $this->lcs; 00481 if ( $end == 0 || $ypos > $this->seq[$end] ) { 00482 $this->seq[++$this->lcs] = $ypos; 00483 $this->in_seq[$ypos] = 1; 00484 00485 return $this->lcs; 00486 } 00487 00488 $beg = 1; 00489 while ( $beg < $end ) { 00490 $mid = (int)( ( $beg + $end ) / 2 ); 00491 if ( $ypos > $this->seq[$mid] ) { 00492 $beg = $mid + 1; 00493 } else { 00494 $end = $mid; 00495 } 00496 } 00497 00498 assert( '$ypos != $this->seq[$end]' ); 00499 00500 $this->in_seq[$this->seq[$end]] = false; 00501 $this->seq[$end] = $ypos; 00502 $this->in_seq[$ypos] = 1; 00503 00504 return $end; 00505 } 00506 00524 private function compareSeq( $xoff, $xlim, $yoff, $ylim ) { 00525 // Slide down the bottom initial diagonal. 00526 while ( $xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff] ) { 00527 ++$xoff; 00528 ++$yoff; 00529 } 00530 00531 // Slide up the top initial diagonal. 00532 while ( $xlim > $xoff && $ylim > $yoff 00533 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1] 00534 ) { 00535 --$xlim; 00536 --$ylim; 00537 } 00538 00539 if ( $xoff == $xlim || $yoff == $ylim ) { 00540 $lcs = 0; 00541 } else { 00542 // This is ad hoc but seems to work well. 00543 // $nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); 00544 // $nchunks = max(2,min(8,(int)$nchunks)); 00545 $nchunks = min( 7, $xlim - $xoff, $ylim - $yoff ) + 1; 00546 list( $lcs, $seps ) = $this->diag( $xoff, $xlim, $yoff, $ylim, $nchunks ); 00547 } 00548 00549 if ( $lcs == 0 ) { 00550 // X and Y sequences have no common subsequence: 00551 // mark all changed. 00552 while ( $yoff < $ylim ) { 00553 $this->ychanged[$this->yind[$yoff++]] = 1; 00554 } 00555 while ( $xoff < $xlim ) { 00556 $this->xchanged[$this->xind[$xoff++]] = 1; 00557 } 00558 } else { 00559 // Use the partitions to split this problem into subproblems. 00560 reset( $seps ); 00561 $pt1 = $seps[0]; 00562 while ( $pt2 = next( $seps ) ) { 00563 $this->compareSeq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] ); 00564 $pt1 = $pt2; 00565 } 00566 } 00567 } 00568 00582 private function shiftBoundaries( $lines, &$changed, $other_changed ) { 00583 wfProfileIn( __METHOD__ ); 00584 $i = 0; 00585 $j = 0; 00586 00587 assert( 'count($lines) == count($changed)' ); 00588 $len = count( $lines ); 00589 $other_len = count( $other_changed ); 00590 00591 while ( 1 ) { 00592 /* 00593 * Scan forwards to find beginning of another run of changes. 00594 * Also keep track of the corresponding point in the other file. 00595 * 00596 * Throughout this code, $i and $j are adjusted together so that 00597 * the first $i elements of $changed and the first $j elements 00598 * of $other_changed both contain the same number of zeros 00599 * (unchanged lines). 00600 * Furthermore, $j is always kept so that $j == $other_len or 00601 * $other_changed[$j] == false. 00602 */ 00603 while ( $j < $other_len && $other_changed[$j] ) { 00604 $j++; 00605 } 00606 00607 while ( $i < $len && !$changed[$i] ) { 00608 assert( '$j < $other_len && ! $other_changed[$j]' ); 00609 $i++; 00610 $j++; 00611 while ( $j < $other_len && $other_changed[$j] ) { 00612 $j++; 00613 } 00614 } 00615 00616 if ( $i == $len ) { 00617 break; 00618 } 00619 00620 $start = $i; 00621 00622 // Find the end of this run of changes. 00623 while ( ++$i < $len && $changed[$i] ) { 00624 continue; 00625 } 00626 00627 do { 00628 /* 00629 * Record the length of this run of changes, so that 00630 * we can later determine whether the run has grown. 00631 */ 00632 $runlength = $i - $start; 00633 00634 /* 00635 * Move the changed region back, so long as the 00636 * previous unchanged line matches the last changed one. 00637 * This merges with previous changed regions. 00638 */ 00639 while ( $start > 0 && $lines[$start - 1] == $lines[$i - 1] ) { 00640 $changed[--$start] = 1; 00641 $changed[--$i] = false; 00642 while ( $start > 0 && $changed[$start - 1] ) { 00643 $start--; 00644 } 00645 assert( '$j > 0' ); 00646 while ( $other_changed[--$j] ) { 00647 continue; 00648 } 00649 assert( '$j >= 0 && !$other_changed[$j]' ); 00650 } 00651 00652 /* 00653 * Set CORRESPONDING to the end of the changed run, at the last 00654 * point where it corresponds to a changed run in the other file. 00655 * CORRESPONDING == LEN means no such point has been found. 00656 */ 00657 $corresponding = $j < $other_len ? $i : $len; 00658 00659 /* 00660 * Move the changed region forward, so long as the 00661 * first changed line matches the following unchanged one. 00662 * This merges with following changed regions. 00663 * Do this second, so that if there are no merges, 00664 * the changed region is moved forward as far as possible. 00665 */ 00666 while ( $i < $len && $lines[$start] == $lines[$i] ) { 00667 $changed[$start++] = false; 00668 $changed[$i++] = 1; 00669 while ( $i < $len && $changed[$i] ) { 00670 $i++; 00671 } 00672 00673 assert( '$j < $other_len && ! $other_changed[$j]' ); 00674 $j++; 00675 if ( $j < $other_len && $other_changed[$j] ) { 00676 $corresponding = $i; 00677 while ( $j < $other_len && $other_changed[$j] ) { 00678 $j++; 00679 } 00680 } 00681 } 00682 } while ( $runlength != $i - $start ); 00683 00684 /* 00685 * If possible, move the fully-merged run of changes 00686 * back to a corresponding run in the other file. 00687 */ 00688 while ( $corresponding < $i ) { 00689 $changed[--$start] = 1; 00690 $changed[--$i] = 0; 00691 assert( '$j > 0' ); 00692 while ( $other_changed[--$j] ) { 00693 continue; 00694 } 00695 assert( '$j >= 0 && !$other_changed[$j]' ); 00696 } 00697 } 00698 wfProfileOut( __METHOD__ ); 00699 } 00700 } 00701 00708 class Diff { 00709 00713 public $edits; 00714 00723 public function __construct( $from_lines, $to_lines ) { 00724 $eng = new DiffEngine; 00725 $this->edits = $eng->diff( $from_lines, $to_lines ); 00726 } 00727 00731 public function getEdits() { 00732 return $this->edits; 00733 } 00734 00746 public function reverse() { 00747 $rev = $this; 00748 $rev->edits = array(); 00750 foreach ( $this->edits as $edit ) { 00751 $rev->edits[] = $edit->reverse(); 00752 } 00753 00754 return $rev; 00755 } 00756 00762 public function isEmpty() { 00763 foreach ( $this->edits as $edit ) { 00764 if ( $edit->type != 'copy' ) { 00765 return false; 00766 } 00767 } 00768 00769 return true; 00770 } 00771 00779 public function lcs() { 00780 $lcs = 0; 00781 foreach ( $this->edits as $edit ) { 00782 if ( $edit->type == 'copy' ) { 00783 $lcs += count( $edit->orig ); 00784 } 00785 } 00786 00787 return $lcs; 00788 } 00789 00798 public function orig() { 00799 $lines = array(); 00800 00801 foreach ( $this->edits as $edit ) { 00802 if ( $edit->orig ) { 00803 array_splice( $lines, count( $lines ), 0, $edit->orig ); 00804 } 00805 } 00806 00807 return $lines; 00808 } 00809 00818 public function closing() { 00819 $lines = array(); 00820 00821 foreach ( $this->edits as $edit ) { 00822 if ( $edit->closing ) { 00823 array_splice( $lines, count( $lines ), 0, $edit->closing ); 00824 } 00825 } 00826 00827 return $lines; 00828 } 00829 } 00830 00836 class MappedDiff extends Diff { 00857 public function __construct( $from_lines, $to_lines, 00858 $mapped_from_lines, $mapped_to_lines ) { 00859 wfProfileIn( __METHOD__ ); 00860 00861 assert( 'count( $from_lines ) == count( $mapped_from_lines )' ); 00862 assert( 'count( $to_lines ) == count( $mapped_to_lines )' ); 00863 00864 parent::__construct( $mapped_from_lines, $mapped_to_lines ); 00865 00866 $xi = $yi = 0; 00867 $editCount = count( $this->edits ); 00868 for ( $i = 0; $i < $editCount; $i++ ) { 00869 $orig = &$this->edits[$i]->orig; 00870 if ( is_array( $orig ) ) { 00871 $orig = array_slice( $from_lines, $xi, count( $orig ) ); 00872 $xi += count( $orig ); 00873 } 00874 00875 $closing = &$this->edits[$i]->closing; 00876 if ( is_array( $closing ) ) { 00877 $closing = array_slice( $to_lines, $yi, count( $closing ) ); 00878 $yi += count( $closing ); 00879 } 00880 } 00881 wfProfileOut( __METHOD__ ); 00882 } 00883 } 00884 00894 class HWLDFWordAccumulator { 00895 public $insClass = ' class="diffchange diffchange-inline"'; 00896 public $delClass = ' class="diffchange diffchange-inline"'; 00897 00898 private $lines = array(); 00899 private $line = ''; 00900 private $group = ''; 00901 private $tag = ''; 00902 00906 private function flushGroup( $new_tag ) { 00907 if ( $this->group !== '' ) { 00908 if ( $this->tag == 'ins' ) { 00909 $this->line .= "<ins{$this->insClass}>" . 00910 htmlspecialchars( $this->group ) . '</ins>'; 00911 } elseif ( $this->tag == 'del' ) { 00912 $this->line .= "<del{$this->delClass}>" . 00913 htmlspecialchars( $this->group ) . '</del>'; 00914 } else { 00915 $this->line .= htmlspecialchars( $this->group ); 00916 } 00917 } 00918 $this->group = ''; 00919 $this->tag = $new_tag; 00920 } 00921 00925 private function flushLine( $new_tag ) { 00926 $this->flushGroup( $new_tag ); 00927 if ( $this->line != '' ) { 00928 array_push( $this->lines, $this->line ); 00929 } else { 00930 # make empty lines visible by inserting an NBSP 00931 array_push( $this->lines, ' ' ); 00932 } 00933 $this->line = ''; 00934 } 00935 00940 public function addWords( $words, $tag = '' ) { 00941 if ( $tag != $this->tag ) { 00942 $this->flushGroup( $tag ); 00943 } 00944 00945 foreach ( $words as $word ) { 00946 // new-line should only come as first char of word. 00947 if ( $word == '' ) { 00948 continue; 00949 } 00950 if ( $word[0] == "\n" ) { 00951 $this->flushLine( $tag ); 00952 $word = substr( $word, 1 ); 00953 } 00954 assert( '!strstr( $word, "\n" )' ); 00955 $this->group .= $word; 00956 } 00957 } 00958 00962 public function getLines() { 00963 $this->flushLine( '~done' ); 00964 00965 return $this->lines; 00966 } 00967 } 00968 00974 class WordLevelDiff extends MappedDiff { 00975 const MAX_LINE_LENGTH = 10000; 00976 00981 public function __construct( $orig_lines, $closing_lines ) { 00982 wfProfileIn( __METHOD__ ); 00983 00984 list( $orig_words, $orig_stripped ) = $this->split( $orig_lines ); 00985 list( $closing_words, $closing_stripped ) = $this->split( $closing_lines ); 00986 00987 parent::__construct( $orig_words, $closing_words, 00988 $orig_stripped, $closing_stripped ); 00989 wfProfileOut( __METHOD__ ); 00990 } 00991 00997 private function split( $lines ) { 00998 wfProfileIn( __METHOD__ ); 00999 01000 $words = array(); 01001 $stripped = array(); 01002 $first = true; 01003 foreach ( $lines as $line ) { 01004 # If the line is too long, just pretend the entire line is one big word 01005 # This prevents resource exhaustion problems 01006 if ( $first ) { 01007 $first = false; 01008 } else { 01009 $words[] = "\n"; 01010 $stripped[] = "\n"; 01011 } 01012 if ( strlen( $line ) > self::MAX_LINE_LENGTH ) { 01013 $words[] = $line; 01014 $stripped[] = $line; 01015 } else { 01016 $m = array(); 01017 if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs', 01018 $line, $m ) 01019 ) { 01020 foreach ( $m[0] as $word ) { 01021 $words[] = $word; 01022 } 01023 foreach ( $m[1] as $stripped_word ) { 01024 $stripped[] = $stripped_word; 01025 } 01026 } 01027 } 01028 } 01029 wfProfileOut( __METHOD__ ); 01030 01031 return array( $words, $stripped ); 01032 } 01033 01037 public function orig() { 01038 wfProfileIn( __METHOD__ ); 01039 $orig = new HWLDFWordAccumulator; 01040 01041 foreach ( $this->edits as $edit ) { 01042 if ( $edit->type == 'copy' ) { 01043 $orig->addWords( $edit->orig ); 01044 } elseif ( $edit->orig ) { 01045 $orig->addWords( $edit->orig, 'del' ); 01046 } 01047 } 01048 $lines = $orig->getLines(); 01049 wfProfileOut( __METHOD__ ); 01050 01051 return $lines; 01052 } 01053 01057 public function closing() { 01058 wfProfileIn( __METHOD__ ); 01059 $closing = new HWLDFWordAccumulator; 01060 01061 foreach ( $this->edits as $edit ) { 01062 if ( $edit->type == 'copy' ) { 01063 $closing->addWords( $edit->closing ); 01064 } elseif ( $edit->closing ) { 01065 $closing->addWords( $edit->closing, 'ins' ); 01066 } 01067 } 01068 $lines = $closing->getLines(); 01069 wfProfileOut( __METHOD__ ); 01070 01071 return $lines; 01072 } 01073 01074 }