MediaWiki
REL1_24
|
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 // @codingStandardsIgnoreFile Ignore Squiz.WhiteSpace.SemicolonSpacing.Incorrect 00428 for ( ; $x < $x1; $x++ ) { 00429 // @codingStandardsIgnoreEnd 00430 $line = $flip ? $this->yv[$x] : $this->xv[$x]; 00431 if ( empty( $ymatches[$line] ) ) { 00432 continue; 00433 } 00434 00435 $k = 0; 00436 $matches = $ymatches[$line]; 00437 reset( $matches ); 00438 while ( list( , $y ) = each( $matches ) ) { 00439 if ( empty( $this->in_seq[$y] ) ) { 00440 $k = $this->lcsPos( $y ); 00441 assert( '$k > 0' ); 00442 $ymids[$k] = $ymids[$k - 1]; 00443 break; 00444 } 00445 } 00446 00447 while ( list( , $y ) = each( $matches ) ) { 00448 if ( $y > $this->seq[$k - 1] ) { 00449 assert( '$y < $this->seq[$k]' ); 00450 // Optimization: this is a common case: 00451 // next match is just replacing previous match. 00452 $this->in_seq[$this->seq[$k]] = false; 00453 $this->seq[$k] = $y; 00454 $this->in_seq[$y] = 1; 00455 } elseif ( empty( $this->in_seq[$y] ) ) { 00456 $k = $this->lcsPos( $y ); 00457 assert( '$k > 0' ); 00458 $ymids[$k] = $ymids[$k - 1]; 00459 } 00460 } 00461 } 00462 } 00463 00464 $seps[] = $flip ? array( $yoff, $xoff ) : array( $xoff, $yoff ); 00465 $ymid = $ymids[$this->lcs]; 00466 for ( $n = 0; $n < $nchunks - 1; $n++ ) { 00467 $x1 = $xoff + (int)( ( $numer + ( $xlim - $xoff ) * $n ) / $nchunks ); 00468 $y1 = $ymid[$n] + 1; 00469 $seps[] = $flip ? array( $y1, $x1 ) : array( $x1, $y1 ); 00470 } 00471 $seps[] = $flip ? array( $ylim, $xlim ) : array( $xlim, $ylim ); 00472 00473 return array( $this->lcs, $seps ); 00474 } 00475 00481 private function lcsPos( $ypos ) { 00482 $end = $this->lcs; 00483 if ( $end == 0 || $ypos > $this->seq[$end] ) { 00484 $this->seq[++$this->lcs] = $ypos; 00485 $this->in_seq[$ypos] = 1; 00486 00487 return $this->lcs; 00488 } 00489 00490 $beg = 1; 00491 while ( $beg < $end ) { 00492 $mid = (int)( ( $beg + $end ) / 2 ); 00493 if ( $ypos > $this->seq[$mid] ) { 00494 $beg = $mid + 1; 00495 } else { 00496 $end = $mid; 00497 } 00498 } 00499 00500 assert( '$ypos != $this->seq[$end]' ); 00501 00502 $this->in_seq[$this->seq[$end]] = false; 00503 $this->seq[$end] = $ypos; 00504 $this->in_seq[$ypos] = 1; 00505 00506 return $end; 00507 } 00508 00526 private function compareSeq( $xoff, $xlim, $yoff, $ylim ) { 00527 // Slide down the bottom initial diagonal. 00528 while ( $xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff] ) { 00529 ++$xoff; 00530 ++$yoff; 00531 } 00532 00533 // Slide up the top initial diagonal. 00534 while ( $xlim > $xoff && $ylim > $yoff 00535 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1] 00536 ) { 00537 --$xlim; 00538 --$ylim; 00539 } 00540 00541 if ( $xoff == $xlim || $yoff == $ylim ) { 00542 $lcs = 0; 00543 } else { 00544 // This is ad hoc but seems to work well. 00545 // $nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); 00546 // $nchunks = max(2,min(8,(int)$nchunks)); 00547 $nchunks = min( 7, $xlim - $xoff, $ylim - $yoff ) + 1; 00548 list( $lcs, $seps ) = $this->diag( $xoff, $xlim, $yoff, $ylim, $nchunks ); 00549 } 00550 00551 if ( $lcs == 0 ) { 00552 // X and Y sequences have no common subsequence: 00553 // mark all changed. 00554 while ( $yoff < $ylim ) { 00555 $this->ychanged[$this->yind[$yoff++]] = 1; 00556 } 00557 while ( $xoff < $xlim ) { 00558 $this->xchanged[$this->xind[$xoff++]] = 1; 00559 } 00560 } else { 00561 // Use the partitions to split this problem into subproblems. 00562 reset( $seps ); 00563 $pt1 = $seps[0]; 00564 while ( $pt2 = next( $seps ) ) { 00565 $this->compareSeq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] ); 00566 $pt1 = $pt2; 00567 } 00568 } 00569 } 00570 00584 private function shiftBoundaries( $lines, &$changed, $other_changed ) { 00585 wfProfileIn( __METHOD__ ); 00586 $i = 0; 00587 $j = 0; 00588 00589 assert( 'count($lines) == count($changed)' ); 00590 $len = count( $lines ); 00591 $other_len = count( $other_changed ); 00592 00593 while ( 1 ) { 00594 /* 00595 * Scan forwards to find beginning of another run of changes. 00596 * Also keep track of the corresponding point in the other file. 00597 * 00598 * Throughout this code, $i and $j are adjusted together so that 00599 * the first $i elements of $changed and the first $j elements 00600 * of $other_changed both contain the same number of zeros 00601 * (unchanged lines). 00602 * Furthermore, $j is always kept so that $j == $other_len or 00603 * $other_changed[$j] == false. 00604 */ 00605 while ( $j < $other_len && $other_changed[$j] ) { 00606 $j++; 00607 } 00608 00609 while ( $i < $len && !$changed[$i] ) { 00610 assert( '$j < $other_len && ! $other_changed[$j]' ); 00611 $i++; 00612 $j++; 00613 while ( $j < $other_len && $other_changed[$j] ) { 00614 $j++; 00615 } 00616 } 00617 00618 if ( $i == $len ) { 00619 break; 00620 } 00621 00622 $start = $i; 00623 00624 // Find the end of this run of changes. 00625 while ( ++$i < $len && $changed[$i] ) { 00626 continue; 00627 } 00628 00629 do { 00630 /* 00631 * Record the length of this run of changes, so that 00632 * we can later determine whether the run has grown. 00633 */ 00634 $runlength = $i - $start; 00635 00636 /* 00637 * Move the changed region back, so long as the 00638 * previous unchanged line matches the last changed one. 00639 * This merges with previous changed regions. 00640 */ 00641 while ( $start > 0 && $lines[$start - 1] == $lines[$i - 1] ) { 00642 $changed[--$start] = 1; 00643 $changed[--$i] = false; 00644 while ( $start > 0 && $changed[$start - 1] ) { 00645 $start--; 00646 } 00647 assert( '$j > 0' ); 00648 while ( $other_changed[--$j] ) { 00649 continue; 00650 } 00651 assert( '$j >= 0 && !$other_changed[$j]' ); 00652 } 00653 00654 /* 00655 * Set CORRESPONDING to the end of the changed run, at the last 00656 * point where it corresponds to a changed run in the other file. 00657 * CORRESPONDING == LEN means no such point has been found. 00658 */ 00659 $corresponding = $j < $other_len ? $i : $len; 00660 00661 /* 00662 * Move the changed region forward, so long as the 00663 * first changed line matches the following unchanged one. 00664 * This merges with following changed regions. 00665 * Do this second, so that if there are no merges, 00666 * the changed region is moved forward as far as possible. 00667 */ 00668 while ( $i < $len && $lines[$start] == $lines[$i] ) { 00669 $changed[$start++] = false; 00670 $changed[$i++] = 1; 00671 while ( $i < $len && $changed[$i] ) { 00672 $i++; 00673 } 00674 00675 assert( '$j < $other_len && ! $other_changed[$j]' ); 00676 $j++; 00677 if ( $j < $other_len && $other_changed[$j] ) { 00678 $corresponding = $i; 00679 while ( $j < $other_len && $other_changed[$j] ) { 00680 $j++; 00681 } 00682 } 00683 } 00684 } while ( $runlength != $i - $start ); 00685 00686 /* 00687 * If possible, move the fully-merged run of changes 00688 * back to a corresponding run in the other file. 00689 */ 00690 while ( $corresponding < $i ) { 00691 $changed[--$start] = 1; 00692 $changed[--$i] = 0; 00693 assert( '$j > 0' ); 00694 while ( $other_changed[--$j] ) { 00695 continue; 00696 } 00697 assert( '$j >= 0 && !$other_changed[$j]' ); 00698 } 00699 } 00700 wfProfileOut( __METHOD__ ); 00701 } 00702 } 00703 00710 class Diff { 00711 00715 public $edits; 00716 00725 public function __construct( $from_lines, $to_lines ) { 00726 $eng = new DiffEngine; 00727 $this->edits = $eng->diff( $from_lines, $to_lines ); 00728 } 00729 00733 public function getEdits() { 00734 return $this->edits; 00735 } 00736 00748 public function reverse() { 00749 $rev = $this; 00750 $rev->edits = array(); 00752 foreach ( $this->edits as $edit ) { 00753 $rev->edits[] = $edit->reverse(); 00754 } 00755 00756 return $rev; 00757 } 00758 00764 public function isEmpty() { 00765 foreach ( $this->edits as $edit ) { 00766 if ( $edit->type != 'copy' ) { 00767 return false; 00768 } 00769 } 00770 00771 return true; 00772 } 00773 00781 public function lcs() { 00782 $lcs = 0; 00783 foreach ( $this->edits as $edit ) { 00784 if ( $edit->type == 'copy' ) { 00785 $lcs += count( $edit->orig ); 00786 } 00787 } 00788 00789 return $lcs; 00790 } 00791 00800 public function orig() { 00801 $lines = array(); 00802 00803 foreach ( $this->edits as $edit ) { 00804 if ( $edit->orig ) { 00805 array_splice( $lines, count( $lines ), 0, $edit->orig ); 00806 } 00807 } 00808 00809 return $lines; 00810 } 00811 00820 public function closing() { 00821 $lines = array(); 00822 00823 foreach ( $this->edits as $edit ) { 00824 if ( $edit->closing ) { 00825 array_splice( $lines, count( $lines ), 0, $edit->closing ); 00826 } 00827 } 00828 00829 return $lines; 00830 } 00831 } 00832 00838 class MappedDiff extends Diff { 00859 public function __construct( $from_lines, $to_lines, 00860 $mapped_from_lines, $mapped_to_lines ) { 00861 wfProfileIn( __METHOD__ ); 00862 00863 assert( 'count( $from_lines ) == count( $mapped_from_lines )' ); 00864 assert( 'count( $to_lines ) == count( $mapped_to_lines )' ); 00865 00866 parent::__construct( $mapped_from_lines, $mapped_to_lines ); 00867 00868 $xi = $yi = 0; 00869 $editCount = count( $this->edits ); 00870 for ( $i = 0; $i < $editCount; $i++ ) { 00871 $orig = &$this->edits[$i]->orig; 00872 if ( is_array( $orig ) ) { 00873 $orig = array_slice( $from_lines, $xi, count( $orig ) ); 00874 $xi += count( $orig ); 00875 } 00876 00877 $closing = &$this->edits[$i]->closing; 00878 if ( is_array( $closing ) ) { 00879 $closing = array_slice( $to_lines, $yi, count( $closing ) ); 00880 $yi += count( $closing ); 00881 } 00882 } 00883 wfProfileOut( __METHOD__ ); 00884 } 00885 } 00886 00896 class HWLDFWordAccumulator { 00897 public $insClass = ' class="diffchange diffchange-inline"'; 00898 public $delClass = ' class="diffchange diffchange-inline"'; 00899 00900 private $lines = array(); 00901 private $line = ''; 00902 private $group = ''; 00903 private $tag = ''; 00904 00908 private function flushGroup( $new_tag ) { 00909 if ( $this->group !== '' ) { 00910 if ( $this->tag == 'ins' ) { 00911 $this->line .= "<ins{$this->insClass}>" . 00912 htmlspecialchars( $this->group ) . '</ins>'; 00913 } elseif ( $this->tag == 'del' ) { 00914 $this->line .= "<del{$this->delClass}>" . 00915 htmlspecialchars( $this->group ) . '</del>'; 00916 } else { 00917 $this->line .= htmlspecialchars( $this->group ); 00918 } 00919 } 00920 $this->group = ''; 00921 $this->tag = $new_tag; 00922 } 00923 00927 private function flushLine( $new_tag ) { 00928 $this->flushGroup( $new_tag ); 00929 if ( $this->line != '' ) { 00930 array_push( $this->lines, $this->line ); 00931 } else { 00932 # make empty lines visible by inserting an NBSP 00933 array_push( $this->lines, ' ' ); 00934 } 00935 $this->line = ''; 00936 } 00937 00942 public function addWords( $words, $tag = '' ) { 00943 if ( $tag != $this->tag ) { 00944 $this->flushGroup( $tag ); 00945 } 00946 00947 foreach ( $words as $word ) { 00948 // new-line should only come as first char of word. 00949 if ( $word == '' ) { 00950 continue; 00951 } 00952 if ( $word[0] == "\n" ) { 00953 $this->flushLine( $tag ); 00954 $word = substr( $word, 1 ); 00955 } 00956 assert( '!strstr( $word, "\n" )' ); 00957 $this->group .= $word; 00958 } 00959 } 00960 00964 public function getLines() { 00965 $this->flushLine( '~done' ); 00966 00967 return $this->lines; 00968 } 00969 } 00970 00976 class WordLevelDiff extends MappedDiff { 00977 const MAX_LINE_LENGTH = 10000; 00978 00983 public function __construct( $orig_lines, $closing_lines ) { 00984 wfProfileIn( __METHOD__ ); 00985 00986 list( $orig_words, $orig_stripped ) = $this->split( $orig_lines ); 00987 list( $closing_words, $closing_stripped ) = $this->split( $closing_lines ); 00988 00989 parent::__construct( $orig_words, $closing_words, 00990 $orig_stripped, $closing_stripped ); 00991 wfProfileOut( __METHOD__ ); 00992 } 00993 00999 private function split( $lines ) { 01000 wfProfileIn( __METHOD__ ); 01001 01002 $words = array(); 01003 $stripped = array(); 01004 $first = true; 01005 foreach ( $lines as $line ) { 01006 # If the line is too long, just pretend the entire line is one big word 01007 # This prevents resource exhaustion problems 01008 if ( $first ) { 01009 $first = false; 01010 } else { 01011 $words[] = "\n"; 01012 $stripped[] = "\n"; 01013 } 01014 if ( strlen( $line ) > self::MAX_LINE_LENGTH ) { 01015 $words[] = $line; 01016 $stripped[] = $line; 01017 } else { 01018 $m = array(); 01019 if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs', 01020 $line, $m ) 01021 ) { 01022 foreach ( $m[0] as $word ) { 01023 $words[] = $word; 01024 } 01025 foreach ( $m[1] as $stripped_word ) { 01026 $stripped[] = $stripped_word; 01027 } 01028 } 01029 } 01030 } 01031 wfProfileOut( __METHOD__ ); 01032 01033 return array( $words, $stripped ); 01034 } 01035 01039 public function orig() { 01040 wfProfileIn( __METHOD__ ); 01041 $orig = new HWLDFWordAccumulator; 01042 01043 foreach ( $this->edits as $edit ) { 01044 if ( $edit->type == 'copy' ) { 01045 $orig->addWords( $edit->orig ); 01046 } elseif ( $edit->orig ) { 01047 $orig->addWords( $edit->orig, 'del' ); 01048 } 01049 } 01050 $lines = $orig->getLines(); 01051 wfProfileOut( __METHOD__ ); 01052 01053 return $lines; 01054 } 01055 01059 public function closing() { 01060 wfProfileIn( __METHOD__ ); 01061 $closing = new HWLDFWordAccumulator; 01062 01063 foreach ( $this->edits as $edit ) { 01064 if ( $edit->type == 'copy' ) { 01065 $closing->addWords( $edit->closing ); 01066 } elseif ( $edit->closing ) { 01067 $closing->addWords( $edit->closing, 'ins' ); 01068 } 01069 } 01070 $lines = $closing->getLines(); 01071 wfProfileOut( __METHOD__ ); 01072 01073 return $lines; 01074 } 01075 01076 }