r38623 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r38622‎ | r38623 | r38624 >
Date:13:45, 5 August 2008
Author:guyvdb
Status:old
Tags:
Comment:
Enable new diff in LocalSettings
Modified paths:
  • /branches/visual_diff/phase3/includes/Diff.php (modified) (history)
  • /branches/visual_diff/phase3/includes/DifferenceEngine.php (modified) (history)

Diff [purge]

Index: branches/visual_diff/phase3/includes/Diff.php
@@ -19,18 +19,22 @@
2020
2121 /**
2222 * Implementation of the Diff algorithm.
23 - *
 23+ *
 24+ * DO NOT USE ON PRODUCTION SYSTEMS
 25+ *
2426 * The algorithm is based on the O(NP) LCS algorithm as descibed by Wu et al. in "An O(NP) Sequence Comparison Algorithm"
2527 * and extended with my own ideas.
2628 *
2729 * @return array($from_removed, $to_added)
28 - * | TRUE if the token was removed or added.
 30+ * TRUE if the token was removed or added.
2931 *
3032 * @author Guy Van den Broeck
3133 */
3234 function wikidiff3_diff(array $from, array $to, $boundRunningTime=FALSE, $max_NP_before_bound = 800000){
3335 wfProfileIn( __METHOD__ );
3436
 37+ $time = microtime(true);
 38+
3539 $m = sizeof($from);
3640 $n = sizeof($to);
3741
@@ -41,8 +45,7 @@
4246 //remove common tokens at the start
4347 $i=0;
4448 while($i<$m && $i<$n && $from[$i]===$to[$i]){
45 - $result_from[$i] = FALSE;
46 - $result_to[$i] = FALSE;
 49+ $result_from[$i] = $result_to[$i] = FALSE;
4750 unset($from[$i],$to[$i]);
4851 ++$i;
4952 }
@@ -50,16 +53,12 @@
5154 //remove common tokens at the end
5255 $j=1;
5356 while($i+$j<=$m && $i+$j<=$n && $from[$m-$j]===$to[$n-$j]){
54 - $result_from[$m-$j] = FALSE;
55 - $result_to[$n-$j] = FALSE;
 57+ $result_from[$m-$j] = $result_to[$n-$j] = FALSE;
5658 unset($from[$m-$j],$to[$n-$j]);
5759 ++$j;
5860 }
5961
60 - $newFrom = array();
61 - $newFromIndex = array();
62 - $newTo = array();
63 - $newToIndex = array();
 62+ $newFrom = $newFromIndex = $newTo = $newToIndex = array();
6463
6564 //remove tokens not in both sequences
6665 $shared = array_fill_keys($from,FALSE);
@@ -79,16 +78,15 @@
8079 }
8180 }
8281
83 - unset($from, $to);
 82+ unset($from, $to, $shared);
8483
85 - $m = sizeof($newFrom);
86 - $n = sizeof($newTo);
87 - $offsetx = 0;
88 - $offsety = 0;
89 - $from_inLcs = new InLcs($m);
90 - $to_inLcs = new InLcs($n);
 84+ $m2 = sizeof($newFrom);
 85+ $n2 = sizeof($newTo);
 86+ $offsetx = $offsety = 0;
 87+ $from_inLcs = new InLcs($m2);
 88+ $to_inLcs = new InLcs($n2);
9189
92 - wikidiff3_diffPart($newFrom, $newTo, $from_inLcs, $to_inLcs, $m, $n, $offsetx, $offsety, $m, $boundRunningTime, $max_NP_before_bound);
 90+ wikidiff3_diffPart($newFrom, $newTo, $from_inLcs, $to_inLcs, $m2, $n2, $offsetx, $offsety, $m2, $boundRunningTime, $max_NP_before_bound);
9391
9492 foreach($from_inLcs->inLcs as $key => $in){
9593 if($in){
@@ -100,6 +98,7 @@
10199 $result_to[$newToIndex[$key]] = FALSE;
102100 }
103101 }
 102+
104103 wfProfileOut( __METHOD__ );
105104 return array($result_from, $result_to);
106105 }
@@ -108,62 +107,52 @@
109108 if($bestKnownLcs==0 || $m==0 || $n==0){
110109 return;
111110 }
 111+
112112 if($m>$n){
113113 return wikidiff3_diffPart($b, $a, $b_inLcs, $a_inLcs, $n, $m, $offsety, $offsetx, $bestKnownLcs, $boundRunningTime, $max_NP_before_bound);
114114 }
115115
116 - wfProfileIn( __METHOD__ );
117 -
118116 $a_inLcs_sym = &$a_inLcs->inLcs;
119117 $b_inLcs_sym = &$b_inLcs->inLcs;
120118
121119 //remove common tokens at the start
122120 while($m>0 && $n>0 && $a[0]===$b[0]){
123 - $a_inLcs_sym[$offsetx] = TRUE;
124 - $b_inLcs_sym[$offsety] = TRUE;
 121+ $a_inLcs_sym[$offsetx] = $b_inLcs_sym[$offsety] = TRUE;
125122 ++$offsetx; ++$offsety;
126 - $m--; $n--;
127 - $bestKnownLcs--;
 123+ --$m; --$n;
 124+ --$bestKnownLcs;
128125 array_shift($a);
129126 array_shift($b);
130127 }
131128
132129 //remove common tokens at the end
133130 while($m>0 && $n>0 && $a[$m-1]===$b[$n-1]){
134 - $a_inLcs_sym[$offsetx+$m-1] = TRUE;
135 - $b_inLcs_sym[$offsety+$n-1] = TRUE;
 131+ $a_inLcs_sym[$offsetx+$m-1] = $b_inLcs_sym[$offsety+$n-1] = TRUE;
136132 --$m; --$n;
137 - $bestKnownLcs--;
 133+ --$bestKnownLcs;
138134 array_pop($a);
139135 array_pop($b);
140136 }
141137
 138+ if($bestKnownLcs==0 || $m==0 || $n==0){
 139+ return;
 140+ }
 141+ if($m>$n){
 142+ return wikidiff3_diffPart($b, $a, $b_inLcs, $a_inLcs, $n, $m, $offsety, $offsetx, $bestKnownLcs, $boundRunningTime, $max_NP_before_bound);
 143+ }
 144+
142145 $delta = $n-$m;
143146 $delta_plus_1 = $delta+1;
144147 $delta_min_1 = $delta-1;
145148
146 - $fpForw = array_fill( 0, $delta_plus_1, -1);
147 - $lcsSizeForw = array_fill( 0, $delta_plus_1, 0);
148 - $snakeBeginForw = array_fill( 0, $delta_plus_1, -1);
149 - $snakeEndForw = array_fill( 0, $delta_plus_1, -1);
150 - $snakekForw = array_fill( 0, $delta_plus_1, 0);
151 -
152 - $fpBack = array_fill( 0, $delta_plus_1, $n);
153 - $lcsSizeBack = array_fill( 0, $delta_plus_1, 0);
154 - $snakeBeginBack = array_fill( 0, $delta_plus_1, $n);
155 - $snakeEndBack = array_fill( 0, $delta_plus_1, $n);
156 - $snakekBack = array_fill( 0, $delta_plus_1, 0);
157 -
 149+ $fpForw = $snakeBeginForw = $snakeEndForw = array_fill( 0, $delta_plus_1, -1);
 150+ $lcsSizeForw = $snakekForw = $lcsSizeBack = $snakekBack = array_fill( 0, $delta_plus_1, 0);
 151+ $fpBack = $snakeBeginBack = $snakeEndBack = array_fill( 0, $delta_plus_1, $n);
158152 $overlap = $delta>1 ? array_fill( 1, $delta_min_1, FALSE) : array();
 153+ $bestLcsLength = $bestLcsLengthTop = $bestLcsLengthBottom = -1;
159154
160 - $bestKnownLcs = $bestKnownLcs;
161 -
162 - $bestLcsLength = -1;
163 - $bestLcsLengthTop = -1;
164 - $bestLcsLengthBottom = -1;
165 -
166155 if($boundRunningTime){
167 - $maxp_before_bound = max($max_NP_before_bound/$n,10);
 156+ $maxp_before_bound = max((-$delta+sqrt($delta*$delta+($max_NP_before_bound<<2)))>>1,2);
168157 if($maxp_before_bound>=$m){
169158 $boundRunningTime = false;
170159 unset($maxp_before_bound);
@@ -178,40 +167,44 @@
179168
180169 if($boundRunningTime && $p>$maxp_before_bound){
181170 // bound the running time by stopping early
182 - if($bestLcsLength>0){
 171+ if($bestLcsLength>=0){
 172+ // accept the best LCS thusfar
183173 break;
184174 }else{
185 - $bestLcsProgressForw=0;
186 - $bestkForw = 0;
 175+ $bestLcsProgressForw = $bestkForw = 0;
187176 foreach($lcsSizeForw as $k => $localLcsProgress){
188 - if($localLcsProgress>$bestLcsProgressForw){
 177+ if($localLcsProgress>$bestLcsProgressForw || ($localLcsProgress==$bestLcsProgressForw && $bestkForw > $k)){
189178 $bestLcsProgressForw = $localLcsProgress;
190179 $bestkForw = $k;
191180 }
192181 }
193 - $bestLcsProgressBack=0;
194 - $bestkBack = 0;
 182+ $bestLcsProgressBack = $bestkBack = 0;
195183 foreach($lcsSizeBack as $k => $localLcsProgress){
196 - if($localLcsProgress-max(0,-$k,$k-$delta)>$bestLcsProgressBack){
197 - $bestLcsProgressBack = $localLcsProgress-max(0,-$k,$k-$delta);
 184+ if($localLcsProgress>$bestLcsProgressBack || ($localLcsProgress==$bestLcsProgressBack && $bestkBack < $k)){
 185+ $bestLcsProgressBack = $localLcsProgress;
198186 $bestkBack = $k;
199187 }
200188 }
201 - if($lcsSizeForw[$bestkForw]>0 || $lcsSizeForw[$bestkBack]>0){
202 - if($fpForw[$bestkForw]>$fpBack[$bestkBack] || $fpForw[$bestkForw]-$bestkForw>$fpBack[$bestkBack]-$bestkBack){
203 - // This is hard, maybe try some more? Can this even happen?
204 - }else{
205 - $topSnakeStart = $snakeBeginForw[$bestkForw];
206 - $topSnakeEnd = $snakeEndForw[$bestkForw];
207 - $topSnakek = $snakekForw[$bestkForw];
208 - $bottomSnakeStart = $snakeEndBack[$bestkBack]+1;
209 - $bottomSnakeEnd = $snakeBeginBack[$bestkBack]+1;
210 - $bottomSnakek = $snakekBack[$bestkBack];
211 - $bestLcsLengthTop = $lcsSizeForw[$bestkBack] + $topSnakeStart - $topSnakeEnd;
212 - $bestLcsLengthBottom = $lcsSizeBack[$bestkBack] + $bottomSnakeStart - $bottomSnakeEnd;
 189+ if($fpForw[$bestkForw]-$bestkForw>$fpBack[$bestkBack]-$bestkBack){
 190+ // This is hard, maybe try some more? Can this even happen? A solution will be found soon anyway.
 191+ }else{
 192+ // lets do this
 193+ $topSnakeStart = $snakeBeginForw[$bestkForw];
 194+ $topSnakeEnd = $snakeEndForw[$bestkForw];
 195+ $topSnakek = $snakekForw[$bestkForw];
 196+ $bestLcsLengthTop = $lcsSizeForw[$bestkBack] + $topSnakeStart - $topSnakeEnd;
213197
 198+ $bottomSnakeStart = $snakeEndBack[$bestkBack]+1;
 199+ $bottomSnakeEnd = $snakeBeginBack[$bestkBack]+1;
 200+ $bottomSnakek = $snakekBack[$bestkBack];
 201+ $bestLcsLengthBottom = $lcsSizeBack[$bestkBack] + $bottomSnakeStart - $bottomSnakeEnd;
 202+
 203+ if($bottomSnakeStart-$topSnakeEnd>$n*0.6 && ($bottomSnakeStart-$bottomSnakek)-($topSnakeEnd-$topSnakek)>$m*0.6){
 204+ // cut the sequence from both sides (there isn't enough progress, 60% of the sequence is not covered)
214205 // also diff the middle now
215206 if($bottomSnakeEnd>($fpForw[$bestkForw]>>1)){
 207+ // do the middle diff between the snakes
 208+ wfDebug(" Limiting diff run time from both sides, middle between snakes\n");
216209 $m_t = ($bottomSnakeStart-$bottomSnakek)-($topSnakeEnd-$topSnakek);
217210 $n_t = $bottomSnakeStart-$topSnakeEnd;
218211 $a_t = array_slice($a, $topSnakeEnd-$topSnakek, $m_t);
@@ -219,6 +212,8 @@
220213 $offsetx_t = $offsetx+($topSnakeEnd-$topSnakek);
221214 $offsety_t = $offsety+$topSnakeEnd;
222215 }else{
 216+ // the snake is too early in the sequence, do the middle diff between endpoints of progress
 217+ wfDebug(" Limiting diff run time from both sides, middle between endpoints\n");
223218 $m_t = ($fpBack[$bestkBack]+1-$bestkBack)-($fpForw[$bestkForw]-$bestkForw);
224219 $n_t = $fpBack[$bestkBack]+1-$fpForw[$bestkForw];
225220 $a_t = array_slice($a, $fpForw[$bestkForw]-$bestkForw, $m_t);
@@ -227,33 +222,37 @@
228223 $offsety_t = $offsety+$fpForw[$bestkForw];
229224 }
230225 wikidiff3_diffPart($a_t, $b_t, $a_inLcs, $b_inLcs, $m_t, $n_t, $offsetx_t, $offsety_t, $m_t, $boundRunningTime, $max_NP_before_bound);
231 - break;
 226+ }else if(min($n-$bottomSnakeStart, $m-($bottomSnakeStart-$bottomSnakek))>min($topSnakeEnd, $topSnakeEnd-$topSnakek)){
 227+ // the bottom snake is more interesting
 228+ wfDebug("Limiting diff run time from bottom side\n");
 229+ $topSnakeStart = $bottomSnakeStart;
 230+ $topSnakeEnd = $bottomSnakeEnd;
 231+ $topSnakek = $bottomSnakek;
 232+ $bestLcsLengthTop = $topSnakeEnd-$topSnakek;
 233+ $bottomSnakeStart = $bottomSnakeEnd;
 234+ }else{
 235+ // the top snake is more interesting
 236+ wfDebug(" Limiting diff run time from top side\n");
 237+ $bottomSnakeStart = $topSnakeEnd;
 238+ $bottomSnakeEnd = $topSnakeEnd;
 239+ $bottomSnakek = $topSnakek;
 240+ $bestLcsLengthBottom = $m-($bottomSnakeEnd-$bottomSnakek);
232241 }
 242+ break;
233243 }
234 - //OOPS, problem
235 - //Maybe try a little more?
236244 }
237245 }
238246 ++$p;
239 - $overlap[-$p] = FALSE;
240 - $overlap[$delta+$p] = FALSE;
 247+ $overlap[-$p] = $overlap[$delta+$p] = FALSE;
241248
242249 $min_p_min_1 = -$p-1;
243250 $delta_plus_1_plus_p = $delta_plus_1+$p;
244251
245252 // forward
246 - $fpForw[$min_p_min_1] = -1;
247 - $lcsSizeForw[$min_p_min_1] = 0;
248 - $snakeBeginForw[$min_p_min_1]=-1;
249 - $snakeEndForw[$min_p_min_1]=-1;
250 - $snakekForw[$min_p_min_1]=-1;
 253+ $fpForw[$min_p_min_1] = $snakeEndForw[$min_p_min_1] = $snakekForw[$min_p_min_1] = $snakeBeginForw[$min_p_min_1] = $fpForw[$delta_plus_1_plus_p] =
 254+ $snakeBeginForw[$delta_plus_1_plus_p] = $snakeEndForw[$delta_plus_1_plus_p] = $snakekForw[$delta_plus_1_plus_p] = -1;
 255+ $lcsSizeForw[$min_p_min_1] = $lcsSizeForw[$delta_plus_1_plus_p] = 0;
251256
252 - $fpForw[$delta_plus_1_plus_p] = -1;
253 - $lcsSizeForw[$delta_plus_1_plus_p] = 0;
254 - $snakeBeginForw[$delta_plus_1_plus_p]=-1;
255 - $snakeEndForw[$delta_plus_1_plus_p]=-1;
256 - $snakekForw[$delta_plus_1_plus_p]=-1;
257 -
258257 $k=-$p;
259258 do {
260259 $k_plus_1 = $k+1;
@@ -263,19 +262,18 @@
264263 $fpAbove = $fpForw[$k_plus_1];
265264 $y = &$fpForw[$k];
266265 if($fpBelow>$fpAbove){
267 - $y = $fpBelow;
 266+ $oldy = $y = $fpBelow;
268267 $lcsSizeForw[$k] = $lcsSizeForw[$k_min_1];
269268 $snakeBeginForw[$k] = $snakeBeginForw[$k_min_1];
270269 $snakeEndForw[$k] = $snakeEndForw[$k_min_1];
271270 $snakekForw[$k] = $snakekForw[$k_min_1];
272271 }else{
273 - $y = $fpAbove;
 272+ $oldy = $y = $fpAbove;
274273 $lcsSizeForw[$k] = $lcsSizeForw[$k_plus_1];
275274 $snakeBeginForw[$k] = $snakeBeginForw[$k_plus_1];
276275 $snakeEndForw[$k] = $snakeEndForw[$k_plus_1];
277276 $snakekForw[$k] = $snakekForw[$k_plus_1];
278277 }
279 - $oldy = $y;
280278 $x = $y-$k;
281279 while($x < $m && $y < $n && $a[$x]===$b[$y]){
282280 ++$x;
@@ -336,18 +334,10 @@
337335 } while(TRUE);
338336
339337 // backward
340 - $fpBack[$min_p_min_1] = $n;
341 - $lcsSizeBack[$min_p_min_1] = 0;
342 - $snakeBeginBack[$min_p_min_1]=$n;
343 - $snakeEndBack[$min_p_min_1]=$n;
344 - $snakekBack[$min_p_min_1]=$n;
 338+ $fpBack[$min_p_min_1] = $snakeBeginBack[$min_p_min_1] = $snakeEndBack[$min_p_min_1] = $snakekBack[$min_p_min_1] =
 339+ $fpBack[$delta_plus_1_plus_p] = $snakeBeginBack[$delta_plus_1_plus_p] = $snakeEndBack[$delta_plus_1_plus_p] = $snakekBack[$delta_plus_1_plus_p] = $n;
 340+ $lcsSizeBack[$min_p_min_1] = $lcsSizeBack[$delta_plus_1_plus_p] = 0;
345341
346 - $fpBack[$delta_plus_1_plus_p] = $n;
347 - $lcsSizeBack[$delta_plus_1_plus_p] = 0;
348 - $snakeBeginBack[$delta_plus_1_plus_p]=$n;
349 - $snakeEndBack[$delta_plus_1_plus_p]=$n;
350 - $snakekBack[$delta_plus_1_plus_p]=$n;
351 -
352342 $k=$delta+$p;
353343 do {
354344 $k_plus_1 = $k+1;
@@ -357,19 +347,18 @@
358348 $fpAbove = $fpBack[$k_plus_1]-1;
359349 $y = &$fpBack[$k];
360350 if($fpBelow<$fpAbove){
361 - $y = $fpBelow;
 351+ $oldy = $y = $fpBelow;
362352 $lcsSizeBack[$k] = $lcsSizeBack[$k_min_1];
363353 $snakeBeginBack[$k] = $snakeBeginBack[$k_min_1];
364354 $snakeEndBack[$k] = $snakeEndBack[$k_min_1];
365355 $snakekBack[$k] = $snakekBack[$k_min_1];
366356 }else{
367 - $y = $fpAbove;
 357+ $oldy = $y = $fpAbove;
368358 $lcsSizeBack[$k] = $lcsSizeBack[$k_plus_1];
369359 $snakeBeginBack[$k] = $snakeBeginBack[$k_plus_1];
370360 $snakeEndBack[$k] = $snakeEndBack[$k_plus_1];
371361 $snakekBack[$k] = $snakekBack[$k_plus_1];
372362 }
373 - $oldy = $y;
374363 $x = $y-$k;
375364 while($x > -1 && $y > -1 && $a[$x]===$b[$y]){
376365 --$x;
@@ -464,8 +453,6 @@
465454 wikidiff3_diffPart($a_t, $b_t, $a_inLcs, $b_inLcs, $m_t, $topSnakeStart, $offsetx, $offsety, $bestLcsLengthTop, $boundRunningTime, $max_NP_before_bound);
466455
467456 wikidiff3_diffPart($a_b, $b_b, $a_inLcs, $b_inLcs, $m_b, $n_b, $offsetx+($bottomSnakeEnd-$bottomSnakek), $offsety+$bottomSnakeEnd, $bestLcsLengthBottom, $boundRunningTime, $max_NP_before_bound);
468 -
469 - wfProfileOut( __METHOD__ );
470457 }
471458
472459 class InLcs {
@@ -484,268 +471,4 @@
485472 }
486473
487474 }
488 -
489 -/**
490 - * DISCLAIMER: The next classes are made obsolete by the previous function.
491 - * They implement Wu's O(NP) algorithm in a literal way and only compute the LCS.
492 - */
493 -
494 -/**
495 - * Class providing primitives for LCS or Diff.
496 - *
497 - * @author Guy Van den Broeck
498 - */
499 -class AbstractDiffAlgorithm {
500 -
501 - /**
502 - * Slide down the snake untill reaching an inequality.
503 - */
504 - protected function forward_snake( $k, $y, $maxx, $maxy, $a, $b, $offsetx=0, $offsety=0){
505 - $x = $y-$k;
506 - while($x < $maxx && $y < $maxy && $this->equals($a[$offsetx+$x],$b[$offsety+$y])){
507 - ++$x;
508 - ++$y;
509 - }
510 - return $y;
511 - }
512 -
513 - /**
514 - * Slide up the snake until reaching an inequality.
515 - */
516 - protected function backward_snake( $k, $y, $maxx, $maxy, $a, $b, $offsetx=0, $offsety=0){
517 - $x = $y-$k;
518 - while($x > $maxx && $y > $maxy && $this->equals($a[$offsetx+$x],$b[$offsety+$y])){
519 - --$x;
520 - --$y;
521 - }
522 - return $y;
523 - }
524 -
525 - /**
526 - * Override this method to compute the LCS with different equality measures.
527 - */
528 - public function equals($left, $right){
529 - return $left == $right;
530 - }
531 -}
532 -
533 -/**
534 - * Class implementing the LCS algorithm by backward propagation.
535 - *
536 - * This is the O(NP) variant as descibed by Wu et al. in "An O(NP) Sequence Comparison Algorithm"
537 - *
538 - * @author Guy Van den Broeck
539 - */
540 -class BackwardLcs extends AbstractDiffAlgorithm {
541 -
542 - /*
543 - * Computers the length of the longest common subsequence of
544 - * the arrays $from and $to.
545 - */
546 - public function lcs (array $from, array $to) {
547 - return (sizeof($from)+sizeof($to)-$this->ses( $from, $to))/2;
548 - }
549 -
550 - /*
551 - * Computers the length of the shortest edit script of
552 - * the arrays $from and $to.
553 - */
554 - public function ses (array $from, array $to) {
555 - if(sizeof($from)>sizeof($to)){
556 - $temp = $from;
557 - $from = $to;
558 - $to = $temp;
559 - }
560 - $m = sizeof($from);
561 - $n = sizeof($to);
562 -
563 - if($n==0){
564 - return $m;
565 - }else if($m==0){
566 - return $n;
567 - }
568 -
569 - $delta = $n-$m;
570 - $p = -1;
571 - $fp = array_fill( 0, $delta+1, $n);
572 -
573 - do{
574 - ++$p;
575 -
576 - $fp[-$p-1] = $n;
577 - $fp[$delta+$p+1] = $n;
578 -
579 - for($k=$delta+$p;$k>0;$k--){
580 - $fp[$k] = $this->backward_snake( $k, min( $fp[$k+1]-1, $fp[$k-1]), -1, -1, $from, $to);
581 - }
582 - for($k=-$p;$k<0;$k++){
583 - $fp[$k] = $this->backward_snake( $k, min( $fp[$k+1]-1, $fp[$k-1]), -1, -1, $from, $to);
584 - }
585 - $fp[0] = $this->backward_snake( 0, min( $fp[1]-1, $fp[-1]), -1, -1, $from, $to);
586 - }while($fp[0]!=-1);
587 -
588 - return $delta+2*$p;
589 - }
590 -
591 -}
592 -
593 -/**
594 - * Class implementing the LCS algorithm by forward propagation.
595 - *
596 - * This is the O(NP) variant as descibed by Wu et al. in "An O(NP) Sequence Comparison Algorithm"
597 - *
598 - * @author Guy Van den Broeck
599 - */
600 -class ForwardLcs extends AbstractDiffAlgorithm {
601 -
602 - /*
603 - * Computers the length of the longest common subsequence of
604 - * the arrays $from and $to.
605 - */
606 - public function lcs (array $from, array $to) {
607 - return (sizeof($from)+sizeof($to)-$this->ses( $from, $to))/2;
608 - }
609 -
610 - /*
611 - * Computers the length of the shortest edit script of
612 - * the arrays $from and $to.
613 - */
614 - public function ses (array $from, array $to) {
615 - if(sizeof($from)>sizeof($to)){
616 - $temp = $from;
617 - $from = $to;
618 - $to = $temp;
619 - }
620 - $m = sizeof($from);
621 - $n = sizeof($to);
622 -
623 - if($n==0){
624 - return $m;
625 - }else if($m==0){
626 - return $n;
627 - }
628 -
629 - $delta = $n-$m;
630 - $p = -1;
631 - $fp = array_fill( 0, $delta+1, -1);
632 - do{
633 - ++$p;
634 -
635 - $fp[-$p-1] = -1;
636 - $fp[$delta+$p+1] = -1;
637 -
638 - for($k=-$p;$k<$delta;$k++){
639 - $fp[$k] = $this->forward_snake( $k, max( $fp[$k-1]+1, $fp[$k+1]), $m, $n, $from, $to);
640 - }
641 - for($k=$delta+$p;$k>$delta;$k--){
642 - $fp[$k] = $this->forward_snake( $k, max( $fp[$k-1]+1, $fp[$k+1]), $m, $n, $from, $to);
643 - }
644 - $fp[$delta] = $this->forward_snake( $delta, max( $fp[$delta-1]+1, $fp[$delta+1]), $m, $n, $from, $to);
645 - }while($fp[$delta]!=$n);
646 -
647 - return $delta+2*$p;
648 - }
649 -
650 -}
651 -
652 -/**
653 - * Class implementing the LCS algorithm by bidirectional propagation.
654 - *
655 - * !!!!!!!!!!!!!!!!!!!!!!
656 - * BEWARE HERE BE DRAGONS
657 - * This algorithm is not guaranteed to find the exact size of the LCS.
658 - * There are border cases where it will be 1 (or more) off.
659 - * This implmentation is useful because it is very fast.
660 - * !!!!!!!!!!!!!!!!!!!!!!
661 - *
662 - * This is the O(NP) variant as descibed by Wu et al. in "An O(NP) Sequence Comparison Algorithm"
663 - *
664 - * @author Guy Van den Broeck
665 - */
666 -class BidirectionalLcs extends AbstractDiffAlgorithm {
667 -
668 - /*
669 - * Computers the length of the longest common subsequence of
670 - * the arrays $from and $to.
671 - */
672 - public function lcs (array $from, array $to) {
673 - return (sizeof($from)+sizeof($to)-$this->ses( $from, $to))/2;
674 - }
675 -
676 - /*
677 - * Computers the length of the shortest edit script of
678 - * the arrays $from and $to.
679 - */
680 - public function ses (array $from, array $to) {
681 - if(sizeof($from)>sizeof($to)){
682 - $temp = $from;
683 - $from = $to;
684 - $to = $temp;
685 - }
686 - $m = sizeof($from);
687 - $n = sizeof($to);
688 -
689 - if($n==0){
690 - return $m;
691 - }else if($m==0){
692 - return $n;
693 - }
694 -
695 - $delta = $n-$m;
696 - $p = -1;
697 - $fpf = array_fill( 0, $delta+1, -1);
698 - $fpb = array_fill( 0, $delta+1, $n);
699 -
700 - $p = -1;
701 -
702 - while(1){
703 - ++$p;
704 -
705 - //forward
706 - $fpf[-$p-1] = -1;
707 - $fpf[$delta+$p+1] = -1;
708 -
709 - for($k=-$p;$k<$delta;$k++){
710 - $fpf[$k] = $this->forward_snake( $k, max( $fpf[$k-1]+1, $fpf[$k+1]), $m, $n, $from, $to);
711 - if($fpf[$k]>$fpb[$k]+1){
712 - return $delta+4*$p-4;
713 - }
714 - }
715 - for($k=$delta+$p;$k>$delta;$k--){
716 - $fpf[$k] = $this->forward_snake( $k, max( $fpf[$k-1]+1, $fpf[$k+1]), $m, $n, $from, $to);
717 - if($fpf[$k]>$fpb[$k]+1){
718 - return $delta+4*$p-4;
719 - }
720 - }
721 - $fpf[$delta] = $this->forward_snake( $delta, max( $fpf[$delta-1]+1, $fpf[$delta+1]), $m, $n, $from, $to);
722 -
723 - if($fpf[$delta]>$fpb[$delta]+1){
724 - return $delta+4*$p-4;
725 - }
726 -
727 - //backward
728 - $fpb[-$p-1] = $n;
729 - $fpb[$delta+$p+1] = $n;
730 -
731 - for($k=$delta+$p;$k>0;$k--){
732 - $fpb[$k] = $this->backward_snake( $k, min( $fpb[$k+1]-1, $fpb[$k-1]), -1, -1, $from, $to);
733 - if($fpf[$k]>$fpb[$k]+1){
734 - return $delta+4*$p;
735 - }
736 - }
737 - for($k=-$p;$k<0;$k++){
738 - $fpb[$k] = $this->backward_snake( $k, min( $fpb[$k+1]-1, $fpb[$k-1]), -1, -1, $from, $to);
739 - if($fpf[$k]>$fpb[$k]+1){
740 - return $delta+4*$p;
741 - }
742 - }
743 - $fpb[0] = $this->backward_snake( 0, min( $fpb[1]-1, $fpb[-1]), -1, -1, $from, $to);
744 -
745 - if($fpf[0]>$fpb[0]+1){
746 - return $delta+4*$p;
747 - }
748 -
749 - }
750 - }
751 -}
752475 ?>
\ No newline at end of file
Index: branches/visual_diff/phase3/includes/DifferenceEngine.php
@@ -83,7 +83,7 @@
8484 global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
8585 wfProfileIn( __METHOD__ );
8686
87 - # If external diffs are enabled both globally and for the user,
 87+ # If external diffs are enabled both globally and for the user,
8888 # we'll use the application/x-external-editor interface to call
8989 # an external diff tool like kompare, kdiff3, etc.
9090 if($wgUseExternalEditor && $wgUser->getOption('externaldiff')) {
@@ -94,19 +94,19 @@
9595 $url2=$this->mTitle->getFullURL("action=raw&oldid=".$this->mNewid);
9696 $special=$wgLang->getNsText(NS_SPECIAL);
9797 $control=<<<CONTROL
98 -[Process]
99 -Type=Diff text
100 -Engine=MediaWiki
101 -Script={$wgServer}{$wgScript}
102 -Special namespace={$special}
 98+ [Process]
 99+ Type=Diff text
 100+ Engine=MediaWiki
 101+ Script={$wgServer}{$wgScript}
 102+ Special namespace={$special}
103103
104 -[File]
105 -Extension=wiki
106 -URL=$url1
 104+ [File]
 105+ Extension=wiki
 106+ URL=$url1
107107
108 -[File 2]
109 -Extension=wiki
110 -URL=$url2
 108+ [File 2]
 109+ Extension=wiki
 110+ URL=$url2
111111 CONTROL;
112112 echo($control);
113113 return;
@@ -176,15 +176,15 @@
177177 // Look for an unpatrolled change corresponding to this diff
178178 $db = wfGetDB( DB_SLAVE );
179179 $change = RecentChange::newFromConds(
180 - array(
181 - // Add redundant user,timestamp condition so we can use the existing index
 180+ array(
 181+ // Add redundant user,timestamp condition so we can use the existing index
182182 'rc_user_text' => $this->mNewRev->getRawUserText(),
183183 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
184184 'rc_this_oldid' => $this->mNewid,
185185 'rc_last_oldid' => $this->mOldid,
186186 'rc_patrolled' => 0
187 - ),
188 - __METHOD__
 187+ ),
 188+ __METHOD__
189189 );
190190 if( $change instanceof RecentChange ) {
191191 $rcid = $change->mAttribs['rc_id'];
@@ -196,8 +196,8 @@
197197 // Build the link
198198 if( $rcid ) {
199199 $patrol = ' <span class="patrollink">[' . $sk->makeKnownLinkObj(
200 - $this->mTitle,
201 - wfMsgHtml( 'markaspatrolleddiff' ),
 200+ $this->mTitle,
 201+ wfMsgHtml( 'markaspatrolleddiff' ),
202202 "action=markpatrolled&rcid={$rcid}"
203203 ) . ']</span>';
204204 } else {
@@ -231,33 +231,33 @@
232232 if( $wgUser->isAllowed( 'deleterevision' ) ) {
233233 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
234234 if( !$this->mOldRev->userCan( Revision::DELETED_RESTRICTED ) ) {
235 - // If revision was hidden from sysops
 235+ // If revision was hidden from sysops
236236 $ldel = wfMsgHtml('rev-delundel');
237237 } else {
238238 $ldel = $sk->makeKnownLinkObj( $revdel,
239 - wfMsgHtml('rev-delundel'),
 239+ wfMsgHtml('rev-delundel'),
240240 'target=' . urlencode( $this->mOldRev->mTitle->getPrefixedDbkey() ) .
241241 '&oldid=' . urlencode( $this->mOldRev->getId() ) );
242242 // Bolden oversighted content
243243 if( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) )
244 - $ldel = "<strong>$ldel</strong>";
 244+ $ldel = "<strong>$ldel</strong>";
245245 }
246246 $ldel = "&nbsp;&nbsp;&nbsp;<tt>(<small>$ldel</small>)</tt> ";
247247 // We don't currently handle well changing the top revision's settings
248248 if( $this->mNewRev->isCurrent() ) {
249 - // If revision was hidden from sysops
 249+ // If revision was hidden from sysops
250250 $rdel = wfMsgHtml('rev-delundel');
251251 } else if( !$this->mNewRev->userCan( Revision::DELETED_RESTRICTED ) ) {
252 - // If revision was hidden from sysops
 252+ // If revision was hidden from sysops
253253 $rdel = wfMsgHtml('rev-delundel');
254254 } else {
255255 $rdel = $sk->makeKnownLinkObj( $revdel,
256 - wfMsgHtml('rev-delundel'),
 256+ wfMsgHtml('rev-delundel'),
257257 'target=' . urlencode( $this->mNewRev->mTitle->getPrefixedDbkey() ) .
258258 '&oldid=' . urlencode( $this->mNewRev->getId() ) );
259259 // Bolden oversighted content
260260 if( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
261 - $rdel = "<strong>$rdel</strong>";
 261+ $rdel = "<strong>$rdel</strong>";
262262 }
263263 $rdel = "&nbsp;&nbsp;&nbsp;<tt>(<small>$rdel</small>)</tt> ";
264264 }
@@ -274,7 +274,7 @@
275275 $this->showDiff( $oldHeader, $newHeader );
276276
277277 if ( !$diffOnly )
278 - $this->renderNewRevision();
 278+ $this->renderNewRevision();
279279
280280 wfProfileOut( __METHOD__ );
281281 }
@@ -289,9 +289,9 @@
290290 $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
291291 #add deleted rev tag if needed
292292 if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
293 - $wgOut->addWikiMsg( 'rev-deleted-text-permission' );
 293+ $wgOut->addWikiMsg( 'rev-deleted-text-permission' );
294294 } else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
295 - $wgOut->addWikiMsg( 'rev-deleted-text-view' );
 295+ $wgOut->addWikiMsg( 'rev-deleted-text-view' );
296296 }
297297
298298 if( !$this->mNewRev->isCurrent() ) {
@@ -316,7 +316,7 @@
317317 $wgOut->addHtml( "\n</pre>\n" );
318318 }
319319 } else
320 - $wgOut->addWikiTextTidy( $this->mNewtext );
 320+ $wgOut->addWikiTextTidy( $this->mNewtext );
321321
322322 if( !$this->mNewRev->isCurrent() ) {
323323 $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
@@ -362,9 +362,9 @@
363363
364364 $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'nextdiff' ), 'diff=next&oldid='.$this->mNewid, '', '', 'id="differences-nextlink"' );
365365 $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />" .
366 - $sk->revUserTools( $this->mNewRev ) . "<br />" .
367 - $sk->revComment( $this->mNewRev ) . "<br />" .
368 - $nextlink . "</div>\n";
 366+ $sk->revUserTools( $this->mNewRev ) . "<br />" .
 367+ $sk->revComment( $this->mNewRev ) . "<br />" .
 368+ $nextlink . "</div>\n";
369369
370370 $wgOut->addHTML( $header );
371371
@@ -429,9 +429,9 @@
430430 wfProfileIn( __METHOD__ );
431431 // Check if the diff should be hidden from this user
432432 if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
433 - return '';
 433+ return '';
434434 } else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
435 - return '';
 435+ return '';
436436 }
437437 // Cacheable?
438438 $key = false;
@@ -492,7 +492,7 @@
493493 dl('php_wikidiff.so');
494494 }
495495 return $wgContLang->unsegementForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
496 - $this->debug( 'wikidiff1' );
 496+ $this->debug( 'wikidiff1' );
497497 }
498498
499499 if ( $wgExternalDiffEngine == 'wikidiff2' ) {
@@ -511,7 +511,7 @@
512512 return $text;
513513 }
514514 }
515 - if ( $wgExternalDiffEngine !== false ) {
 515+ if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) {
516516 # Diff via the shell
517517 global $wgTmpDirectory;
518518 $tempName1 = tempnam( $wgTmpDirectory, 'diff_' );
@@ -547,9 +547,9 @@
548548 $diffs = new Diff( $ota, $nta );
549549 $formatter = new TableDiffFormatter();
550550 return $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
551 - $this->debug();
 551+ $this->debug();
552552 }
553 -
 553+
554554 /**
555555 * Generate a debug comment indicating diff generating time,
556556 * server node, and generator backend.
@@ -562,10 +562,10 @@
563563 }
564564 $data[] = wfTimestamp( TS_DB );
565565 return "<!-- diff generator: " .
566 - implode( " ",
567 - array_map(
 566+ implode( " ",
 567+ array_map(
568568 "htmlspecialchars",
569 - $data ) ) .
 569+ $data ) ) .
570570 " -->\n";
571571 }
572572
@@ -574,7 +574,7 @@
575575 */
576576 function localiseLineNumbers( $text ) {
577577 return preg_replace_callback( '/<!--LINE (\d+)-->/',
578 - array( &$this, 'localiseLineNumbersCb' ), $text );
 578+ array( &$this, 'localiseLineNumbersCb' ), $text );
579579 }
580580
581581 function localiseLineNumbersCb( $matches ) {
@@ -588,7 +588,7 @@
589589 */
590590 function getMultiNotice() {
591591 if ( !is_object($this->mOldRev) || !is_object($this->mNewRev) )
592 - return '';
 592+ return '';
593593
594594 if( !$this->mOldPage->equals( $this->mNewPage ) ) {
595595 // Comparing two different pages? Count would be meaningless.
@@ -603,7 +603,7 @@
604604
605605 $n = $this->mTitle->countRevisionsBetween( $oldid, $newid );
606606 if ( !$n )
607 - return '';
 607+ return '';
608608
609609 return wfMsgExt( 'diff-multi', array( 'parseinline' ), $n );
610610 }
@@ -616,19 +616,19 @@
617617 global $wgOut;
618618
619619 $header = "
620 - <table class='diff'>
621 - <col class='diff-marker' />
622 - <col class='diff-content' />
623 - <col class='diff-marker' />
624 - <col class='diff-content' />
625 - <tr valign='top'>
626 - <td colspan='2' class='diff-otitle'>{$otitle}</td>
627 - <td colspan='2' class='diff-ntitle'>{$ntitle}</td>
628 - </tr>
 620+ <table class='diff'>
 621+ <col class='diff-marker' />
 622+ <col class='diff-content' />
 623+ <col class='diff-marker' />
 624+ <col class='diff-content' />
 625+ <tr valign='top'>
 626+ <td colspan='2' class='diff-otitle'>{$otitle}</td>
 627+ <td colspan='2' class='diff-ntitle'>{$ntitle}</td>
 628+ </tr>
629629 ";
630630
631631 if ( $multi != '' )
632 - $header .= "<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>";
 632+ $header .= "<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>";
633633
634634 return $header . $diff . "</table>";
635635 }
@@ -663,10 +663,10 @@
664664
665665 // Load the new revision object
666666 $this->mNewRev = $this->mNewid
667 - ? Revision::newFromId( $this->mNewid )
668 - : Revision::newFromTitle( $this->mTitle );
 667+ ? Revision::newFromId( $this->mNewid )
 668+ : Revision::newFromTitle( $this->mTitle );
669669 if( !$this->mNewRev instanceof Revision )
670 - return false;
 670+ return false;
671671
672672 // Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
673673 $this->mNewid = $this->mNewRev->getId();
@@ -694,9 +694,9 @@
695695 $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
696696 }
697697 if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
698 - $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
 698+ $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
699699 } else if ( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
700 - $this->mNewtitle = '<span class="history-deleted">'.$this->mNewtitle.'</span>';
 700+ $this->mNewtitle = '<span class="history-deleted">'.$this->mNewtitle.'</span>';
701701 }
702702
703703 // Load the old revision object
@@ -728,7 +728,7 @@
729729 $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t ) );
730730
731731 $this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
732 - . " (<a href='$oldEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
 732+ . " (<a href='$oldEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
733733 // Add an "undo" link
734734 $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undoafter=' . $this->mOldid . '&undo=' . $this->mNewid);
735735 if( $editable && !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
@@ -736,9 +736,9 @@
737737 }
738738
739739 if( !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) {
740 - $this->mOldtitle = '<span class="history-deleted">' . $this->mOldPagetitle . '</span>';
 740+ $this->mOldtitle = '<span class="history-deleted">' . $this->mOldPagetitle . '</span>';
741741 } else if( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
742 - $this->mOldtitle = '<span class="history-deleted">' . $this->mOldtitle . '</span>';
 742+ $this->mOldtitle = '<span class="history-deleted">' . $this->mOldtitle . '</span>';
743743 }
744744 }
745745
@@ -834,7 +834,7 @@
835835
836836 function _DiffOp_Copy ($orig, $closing = false) {
837837 if (!is_array($closing))
838 - $closing = $orig;
 838+ $closing = $orig;
839839 $this->orig = $orig;
840840 $this->closing = $closing;
841841 }
@@ -898,66 +898,146 @@
899899 }
900900 }
901901
902 -
903902 /**
904903 * Class used internally by Diff to actually compute the diffs.
905904 *
906 - * The actual diff algorithm is replaced byt he wikidiff3_diff function in Diff.php.
 905+ * The algorithm used here is mostly lifted from the perl module
 906+ * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
 907+ * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
907908 *
 909+ * More ideas are taken from:
 910+ * http://www.ics.uci.edu/~eppstein/161/960229.html
 911+ *
 912+ * Some ideas are (and a bit of code) are from from analyze.c, from GNU
 913+ * diffutils-2.7, which can be found at:
 914+ * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
 915+ *
 916+ * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
 917+ * are my own.
 918+ *
 919+ * Line length limits for robustness added by Tim Starling, 2005-08-31
 920+ * Alternative implementation added by Guy Van den Broeck, 2008-07-30
 921+ *
908922 * @author Geoffrey T. Dairiki, Tim Starling, Guy Van den Broeck
909923 * @private
910924 * @ingroup DifferenceEngine
911925 */
912926 class _DiffEngine {
913927
 928+ const MAX_XREF_LENGTH = 10000;
 929+
914930 function diff ($from_lines, $to_lines) {
915931 wfProfileIn( __METHOD__ );
916 - // Perform the basic diff algorithm
917 - list($xchanged, $ychanged) = wikidiff3_diff($from_lines, $to_lines);
918 -
 932+
 933+ global $wgExternalDiffEngine;
 934+
 935+ $n_from = sizeof($from_lines);
 936+ $n_to = sizeof($to_lines);
 937+
 938+ if($wgExternalDiffEngine == 'wikidiff3'){
 939+ // wikidiff3
 940+ list($this->xchanged, $this->ychanged) = wikidiff3_diff($from_lines, $to_lines, TRUE, 100000);
 941+ }else{
 942+ // old diff
 943+ wfProfileIn( __METHOD__ ." - basicdiff");
 944+ $this->xchanged = $this->ychanged = array();
 945+ $this->xv = $this->yv = array();
 946+ $this->xind = $this->yind = array();
 947+ unset($this->seq);
 948+ unset($this->in_seq);
 949+ unset($this->lcs);
 950+
 951+ // Skip leading common lines.
 952+ for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
 953+ if ($from_lines[$skip] !== $to_lines[$skip])
 954+ break;
 955+ $this->xchanged[$skip] = $this->ychanged[$skip] = false;
 956+ }
 957+ // Skip trailing common lines.
 958+ $xi = $n_from; $yi = $n_to;
 959+ for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
 960+ if ($from_lines[$xi] !== $to_lines[$yi])
 961+ break;
 962+ $this->xchanged[$xi] = $this->ychanged[$yi] = false;
 963+ }
 964+
 965+ // Ignore lines which do not exist in both files.
 966+ for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
 967+ $xhash[$this->_line_hash($from_lines[$xi])] = 1;
 968+ }
 969+
 970+ for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
 971+ $line = $to_lines[$yi];
 972+ if ( ($this->ychanged[$yi] = empty($xhash[$this->_line_hash($line)])) )
 973+ continue;
 974+ $yhash[$this->_line_hash($line)] = 1;
 975+ $this->yv[] = $line;
 976+ $this->yind[] = $yi;
 977+ }
 978+ for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
 979+ $line = $from_lines[$xi];
 980+ if ( ($this->xchanged[$xi] = empty($yhash[$this->_line_hash($line)])) )
 981+ continue;
 982+ $this->xv[] = $line;
 983+ $this->xind[] = $xi;
 984+ }
 985+
 986+ // Find the LCS.
 987+ $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv));
 988+ wfProfileOut( __METHOD__ ." - basicdiff");
 989+ }
 990+
919991 // Merge edits when possible
920 - $this->_shift_boundaries($from_lines, $xchanged, $ychanged);
921 - $this->_shift_boundaries($to_lines, $ychanged, $xchanged);
 992+ $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
 993+ $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
922994
923995 // Compute the edit operations.
924 - $n_from = sizeof($from_lines);
925 - $n_to = sizeof($to_lines);
926996 $edits = array();
927997 $xi = $yi = 0;
928998 while ($xi < $n_from || $yi < $n_to) {
929 - USE_ASSERTS && assert($yi < $n_to || $xchanged[$xi]);
930 - USE_ASSERTS && assert($xi < $n_from || $ychanged[$yi]);
 999+ USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
 1000+ USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
9311001
9321002 // Skip matching "snake".
9331003 $copy = array();
9341004 while ( $xi < $n_from && $yi < $n_to
935 - && !$xchanged[$xi] && !$ychanged[$yi]) {
 1005+ && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
9361006 $copy[] = $from_lines[$xi++];
9371007 ++$yi;
9381008 }
9391009 if ($copy)
940 - $edits[] = new _DiffOp_Copy($copy);
 1010+ $edits[] = new _DiffOp_Copy($copy);
9411011
9421012 // Find deletes & adds.
9431013 $delete = array();
944 - while ($xi < $n_from && $xchanged[$xi])
945 - $delete[] = $from_lines[$xi++];
 1014+ while ($xi < $n_from && $this->xchanged[$xi])
 1015+ $delete[] = $from_lines[$xi++];
9461016
9471017 $add = array();
948 - while ($yi < $n_to && $ychanged[$yi])
949 - $add[] = $to_lines[$yi++];
 1018+ while ($yi < $n_to && $this->ychanged[$yi])
 1019+ $add[] = $to_lines[$yi++];
9501020
9511021 if ($delete && $add)
952 - $edits[] = new _DiffOp_Change($delete, $add);
 1022+ $edits[] = new _DiffOp_Change($delete, $add);
9531023 elseif ($delete)
954 - $edits[] = new _DiffOp_Delete($delete);
 1024+ $edits[] = new _DiffOp_Delete($delete);
9551025 elseif ($add)
956 - $edits[] = new _DiffOp_Add($add);
 1026+ $edits[] = new _DiffOp_Add($add);
9571027 }
9581028 wfProfileOut( __METHOD__ );
9591029 return $edits;
9601030 }
9611031
 1032+ /**
 1033+ * Returns the whole line if it's small enough, or the MD5 hash otherwise
 1034+ */
 1035+ function _line_hash( $line ) {
 1036+ if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
 1037+ return md5( $line );
 1038+ } else {
 1039+ return $line;
 1040+ }
 1041+ }
9621042
9631043 /* Divide the Largest Common Subsequence (LCS) of the sequences
9641044 * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
@@ -976,23 +1056,22 @@
9771057 * of the portions it is going to specify.
9781058 */
9791059 function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
980 - wfProfileIn( __METHOD__ );
9811060 $flip = false;
9821061
9831062 if ($xlim - $xoff > $ylim - $yoff) {
9841063 // Things seems faster (I'm not sure I understand why)
985 - // when the shortest sequence in X.
986 - $flip = true;
 1064+ // when the shortest sequence in X.
 1065+ $flip = true;
9871066 list ($xoff, $xlim, $yoff, $ylim)
9881067 = array( $yoff, $ylim, $xoff, $xlim);
9891068 }
9901069
9911070 if ($flip)
992 - for ($i = $ylim - 1; $i >= $yoff; $i--)
993 - $ymatches[$this->xv[$i]][] = $i;
 1071+ for ($i = $ylim - 1; $i >= $yoff; $i--)
 1072+ $ymatches[$this->xv[$i]][] = $i;
9941073 else
995 - for ($i = $ylim - 1; $i >= $yoff; $i--)
996 - $ymatches[$this->yv[$i]][] = $i;
 1074+ for ($i = $ylim - 1; $i >= $yoff; $i--)
 1075+ $ymatches[$this->yv[$i]][] = $i;
9971076
9981077 $this->lcs = 0;
9991078 $this->seq[0]= $yoff - 1;
@@ -1004,23 +1083,23 @@
10051084 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
10061085 wfProfileIn( __METHOD__ . "-chunk" );
10071086 if ($chunk > 0)
1008 - for ($i = 0; $i <= $this->lcs; $i++)
1009 - $ymids[$i][$chunk-1] = $this->seq[$i];
 1087+ for ($i = 0; $i <= $this->lcs; $i++)
 1088+ $ymids[$i][$chunk-1] = $this->seq[$i];
10101089
10111090 $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
10121091 for ( ; $x < $x1; $x++) {
10131092 $line = $flip ? $this->yv[$x] : $this->xv[$x];
1014 - if (empty($ymatches[$line]))
1015 - continue;
 1093+ if (empty($ymatches[$line]))
 1094+ continue;
10161095 $matches = $ymatches[$line];
10171096 reset($matches);
10181097 while (list ($junk, $y) = each($matches))
1019 - if (empty($this->in_seq[$y])) {
1020 - $k = $this->_lcs_pos($y);
1021 - USE_ASSERTS && assert($k > 0);
1022 - $ymids[$k] = $ymids[$k-1];
1023 - break;
1024 - }
 1098+ if (empty($this->in_seq[$y])) {
 1099+ $k = $this->_lcs_pos($y);
 1100+ USE_ASSERTS && assert($k > 0);
 1101+ $ymids[$k] = $ymids[$k-1];
 1102+ break;
 1103+ }
10251104 while (list ( /* $junk */, $y) = each($matches)) {
10261105 if ($y > $this->seq[$k-1]) {
10271106 USE_ASSERTS && assert($y < $this->seq[$k]);
@@ -1036,7 +1115,6 @@
10371116 }
10381117 }
10391118 }
1040 - wfProfileOut( __METHOD__ . "-chunk" );
10411119 }
10421120
10431121 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
@@ -1048,13 +1126,10 @@
10491127 }
10501128 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
10511129
1052 - wfProfileOut( __METHOD__ );
10531130 return array($this->lcs, $seps);
10541131 }
10551132
10561133 function _lcs_pos ($ypos) {
1057 - wfProfileIn( __METHOD__ );
1058 -
10591134 $end = $this->lcs;
10601135 if ($end == 0 || $ypos > $this->seq[$end]) {
10611136 $this->seq[++$this->lcs] = $ypos;
@@ -1067,9 +1142,9 @@
10681143 while ($beg < $end) {
10691144 $mid = (int)(($beg + $end) / 2);
10701145 if ( $ypos > $this->seq[$mid] )
1071 - $beg = $mid + 1;
 1146+ $beg = $mid + 1;
10721147 else
1073 - $end = $mid;
 1148+ $end = $mid;
10741149 }
10751150
10761151 USE_ASSERTS && assert($ypos != $this->seq[$end]);
@@ -1077,7 +1152,6 @@
10781153 $this->in_seq[$this->seq[$end]] = false;
10791154 $this->seq[$end] = $ypos;
10801155 $this->in_seq[$ypos] = 1;
1081 - wfProfileOut( __METHOD__ );
10821156 return $end;
10831157 }
10841158
@@ -1093,24 +1167,22 @@
10941168 * All line numbers are origin-0 and discarded lines are not counted.
10951169 */
10961170 function _compareseq ($xoff, $xlim, $yoff, $ylim) {
1097 - wfProfileIn( __METHOD__ );
1098 -
10991171 // Slide down the bottom initial diagonal.
11001172 while ($xoff < $xlim && $yoff < $ylim
1101 - && $this->xv[$xoff] == $this->yv[$yoff]) {
 1173+ && $this->xv[$xoff] == $this->yv[$yoff]) {
11021174 ++$xoff;
11031175 ++$yoff;
11041176 }
11051177
11061178 // Slide up the top initial diagonal.
11071179 while ($xlim > $xoff && $ylim > $yoff
1108 - && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
 1180+ && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
11091181 --$xlim;
11101182 --$ylim;
11111183 }
11121184
11131185 if ($xoff == $xlim || $yoff == $ylim)
1114 - $lcs = 0;
 1186+ $lcs = 0;
11151187 else {
11161188 // This is ad hoc but seems to work well.
11171189 //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
@@ -1124,9 +1196,9 @@
11251197 // X and Y sequences have no common subsequence:
11261198 // mark all changed.
11271199 while ($yoff < $ylim)
1128 - $this->ychanged[$this->yind[$yoff++]] = 1;
 1200+ $this->ychanged[$this->yind[$yoff++]] = 1;
11291201 while ($xoff < $xlim)
1130 - $this->xchanged[$this->xind[$xoff++]] = 1;
 1202+ $this->xchanged[$this->xind[$xoff++]] = 1;
11311203 } else {
11321204 // Use the partitions to split this problem into subproblems.
11331205 reset($seps);
@@ -1136,7 +1208,6 @@
11371209 $pt1 = $pt2;
11381210 }
11391211 }
1140 - wfProfileOut( __METHOD__ );
11411212 }
11421213
11431214 /* Adjust inserts/deletes of identical lines to join changes
@@ -1173,23 +1244,23 @@
11741245 * $other_changed[$j] == false.
11751246 */
11761247 while ($j < $other_len && $other_changed[$j])
1177 - $j++;
 1248+ $j++;
11781249
11791250 while ($i < $len && ! $changed[$i]) {
11801251 USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
11811252 $i++; $j++;
11821253 while ($j < $other_len && $other_changed[$j])
1183 - $j++;
 1254+ $j++;
11841255 }
11851256
11861257 if ($i == $len)
1187 - break;
 1258+ break;
11881259
11891260 $start = $i;
11901261
11911262 // Find the end of this run of changes.
11921263 while (++$i < $len && $changed[$i])
1193 - continue;
 1264+ continue;
11941265
11951266 do {
11961267 /*
@@ -1207,10 +1278,10 @@
12081279 $changed[--$start] = 1;
12091280 $changed[--$i] = false;
12101281 while ($start > 0 && $changed[$start - 1])
1211 - $start--;
 1282+ $start--;
12121283 USE_ASSERTS && assert('$j > 0');
12131284 while ($other_changed[--$j])
1214 - continue;
 1285+ continue;
12151286 USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
12161287 }
12171288
@@ -1232,14 +1303,14 @@
12331304 $changed[$start++] = false;
12341305 $changed[$i++] = 1;
12351306 while ($i < $len && $changed[$i])
1236 - $i++;
 1307+ $i++;
12371308
12381309 USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
12391310 $j++;
12401311 if ($j < $other_len && $other_changed[$j]) {
12411312 $corresponding = $i;
12421313 while ($j < $other_len && $other_changed[$j])
1243 - $j++;
 1314+ $j++;
12441315 }
12451316 }
12461317 } while ($runlength != $i - $start);
@@ -1253,7 +1324,7 @@
12541325 $changed[--$i] = 0;
12551326 USE_ASSERTS && assert('$j > 0');
12561327 while ($other_changed[--$j])
1257 - continue;
 1328+ continue;
12581329 USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
12591330 }
12601331 }
@@ -1312,7 +1383,7 @@
13131384 function isEmpty () {
13141385 foreach ($this->edits as $edit) {
13151386 if ($edit->type != 'copy')
1316 - return false;
 1387+ return false;
13171388 }
13181389 return true;
13191390 }
@@ -1328,7 +1399,7 @@
13291400 $lcs = 0;
13301401 foreach ($this->edits as $edit) {
13311402 if ($edit->type == 'copy')
1332 - $lcs += sizeof($edit->orig);
 1403+ $lcs += sizeof($edit->orig);
13331404 }
13341405 return $lcs;
13351406 }
@@ -1346,7 +1417,7 @@
13471418
13481419 foreach ($this->edits as $edit) {
13491420 if ($edit->orig)
1350 - array_splice($lines, sizeof($lines), 0, $edit->orig);
 1421+ array_splice($lines, sizeof($lines), 0, $edit->orig);
13511422 }
13521423 return $lines;
13531424 }
@@ -1364,7 +1435,7 @@
13651436
13661437 foreach ($this->edits as $edit) {
13671438 if ($edit->closing)
1368 - array_splice($lines, sizeof($lines), 0, $edit->closing);
 1439+ array_splice($lines, sizeof($lines), 0, $edit->closing);
13691440 }
13701441 return $lines;
13711442 }
@@ -1377,21 +1448,21 @@
13781449 function _check ($from_lines, $to_lines) {
13791450 wfProfileIn( __METHOD__ );
13801451 if (serialize($from_lines) != serialize($this->orig()))
1381 - trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
 1452+ trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
13821453 if (serialize($to_lines) != serialize($this->closing()))
1383 - trigger_error("Reconstructed closing doesn't match", E_USER_ERROR);
 1454+ trigger_error("Reconstructed closing doesn't match", E_USER_ERROR);
13841455
13851456 $rev = $this->reverse();
13861457 if (serialize($to_lines) != serialize($rev->orig()))
1387 - trigger_error("Reversed original doesn't match", E_USER_ERROR);
 1458+ trigger_error("Reversed original doesn't match", E_USER_ERROR);
13881459 if (serialize($from_lines) != serialize($rev->closing()))
1389 - trigger_error("Reversed closing doesn't match", E_USER_ERROR);
 1460+ trigger_error("Reversed closing doesn't match", E_USER_ERROR);
13901461
13911462
13921463 $prevtype = 'none';
13931464 foreach ($this->edits as $edit) {
13941465 if ( $prevtype == $edit->type )
1395 - trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
 1466+ trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
13961467 $prevtype = $edit->type;
13971468 }
13981469
@@ -1432,7 +1503,7 @@
14331504 * have the same number of elements as $to_lines.
14341505 */
14351506 function MappedDiff($from_lines, $to_lines,
1436 - $mapped_from_lines, $mapped_to_lines) {
 1507+ $mapped_from_lines, $mapped_to_lines) {
14371508 wfProfileIn( __METHOD__ );
14381509
14391510 assert(sizeof($from_lines) == sizeof($mapped_from_lines));
@@ -1515,8 +1586,8 @@
15161587 $block[] = new _DiffOp_Copy($context);
15171588 }
15181589 $this->_block($x0, $ntrail + $xi - $x0,
1519 - $y0, $ntrail + $yi - $y0,
1520 - $block);
 1590+ $y0, $ntrail + $yi - $y0,
 1591+ $block);
15211592 $block = false;
15221593 }
15231594 }
@@ -1529,21 +1600,21 @@
15301601 $y0 = $yi - sizeof($context);
15311602 $block = array();
15321603 if ($context)
1533 - $block[] = new _DiffOp_Copy($context);
 1604+ $block[] = new _DiffOp_Copy($context);
15341605 }
15351606 $block[] = $edit;
15361607 }
15371608
15381609 if ($edit->orig)
1539 - $xi += sizeof($edit->orig);
 1610+ $xi += sizeof($edit->orig);
15401611 if ($edit->closing)
1541 - $yi += sizeof($edit->closing);
 1612+ $yi += sizeof($edit->closing);
15421613 }
15431614
15441615 if (is_array($block))
1545 - $this->_block($x0, $xi - $x0,
1546 - $y0, $yi - $y0,
1547 - $block);
 1616+ $this->_block($x0, $xi - $x0,
 1617+ $y0, $yi - $y0,
 1618+ $block);
15481619
15491620 $end = $this->_end_diff();
15501621 wfProfileOut( __METHOD__ );
@@ -1555,15 +1626,15 @@
15561627 $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
15571628 foreach ($edits as $edit) {
15581629 if ($edit->type == 'copy')
1559 - $this->_context($edit->orig);
 1630+ $this->_context($edit->orig);
15601631 elseif ($edit->type == 'add')
1561 - $this->_added($edit->closing);
 1632+ $this->_added($edit->closing);
15621633 elseif ($edit->type == 'delete')
1563 - $this->_deleted($edit->orig);
 1634+ $this->_deleted($edit->orig);
15641635 elseif ($edit->type == 'change')
1565 - $this->_changed($edit->orig, $edit->closing);
 1636+ $this->_changed($edit->orig, $edit->closing);
15661637 else
1567 - trigger_error('Unknown edit type', E_USER_ERROR);
 1638+ trigger_error('Unknown edit type', E_USER_ERROR);
15681639 }
15691640 $this->_end_block();
15701641 wfProfileOut( __METHOD__ );
@@ -1581,9 +1652,9 @@
15821653
15831654 function _block_header($xbeg, $xlen, $ybeg, $ylen) {
15841655 if ($xlen > 1)
1585 - $xbeg .= "," . ($xbeg + $xlen - 1);
 1656+ $xbeg .= "," . ($xbeg + $xlen - 1);
15861657 if ($ylen > 1)
1587 - $ybeg .= "," . ($ybeg + $ylen - 1);
 1658+ $ybeg .= "," . ($ybeg + $ylen - 1);
15881659
15891660 return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
15901661 }
@@ -1597,7 +1668,7 @@
15981669
15991670 function _lines($lines, $prefix = ' ') {
16001671 foreach ($lines as $line)
1601 - echo "$prefix $line\n";
 1672+ echo "$prefix $line\n";
16021673 }
16031674
16041675 function _context($lines) {
@@ -1652,40 +1723,40 @@
16531724 $newline = 1;
16541725 $retval = array();
16551726 foreach($diff->edits as $edit)
1656 - switch($edit->type) {
1657 - case 'add':
1658 - foreach($edit->closing as $l) {
1659 - $retval[] = array(
 1727+ switch($edit->type) {
 1728+ case 'add':
 1729+ foreach($edit->closing as $l) {
 1730+ $retval[] = array(
16601731 'action' => 'add',
16611732 'new'=> $l,
16621733 'newline' => $newline++
1663 - );
1664 - }
1665 - break;
1666 - case 'delete':
1667 - foreach($edit->orig as $l) {
1668 - $retval[] = array(
 1734+ );
 1735+ }
 1736+ break;
 1737+ case 'delete':
 1738+ foreach($edit->orig as $l) {
 1739+ $retval[] = array(
16691740 'action' => 'delete',
16701741 'old' => $l,
16711742 'oldline' => $oldline++,
1672 - );
1673 - }
1674 - break;
1675 - case 'change':
1676 - foreach($edit->orig as $i => $l) {
1677 - $retval[] = array(
 1743+ );
 1744+ }
 1745+ break;
 1746+ case 'change':
 1747+ foreach($edit->orig as $i => $l) {
 1748+ $retval[] = array(
16781749 'action' => 'change',
16791750 'old' => $l,
16801751 'new' => @$edit->closing[$i],
16811752 'oldline' => $oldline++,
16821753 'newline' => $newline++,
1683 - );
1684 - }
1685 - break;
1686 - case 'copy':
1687 - $oldline += count($edit->orig);
1688 - $newline += count($edit->orig);
1689 - }
 1754+ );
 1755+ }
 1756+ break;
 1757+ case 'copy':
 1758+ $oldline += count($edit->orig);
 1759+ $newline += count($edit->orig);
 1760+ }
16901761 return $retval;
16911762 }
16921763 }
@@ -1713,13 +1784,13 @@
17141785 function _flushGroup ($new_tag) {
17151786 if ($this->_group !== '') {
17161787 if ($this->_tag == 'ins')
1717 - $this->_line .= '<ins class="diffchange diffchange-inline">' .
1718 - htmlspecialchars ( $this->_group ) . '</ins>';
 1788+ $this->_line .= '<ins class="diffchange diffchange-inline">' .
 1789+ htmlspecialchars ( $this->_group ) . '</ins>';
17191790 elseif ($this->_tag == 'del')
1720 - $this->_line .= '<del class="diffchange diffchange-inline">' .
1721 - htmlspecialchars ( $this->_group ) . '</del>';
 1791+ $this->_line .= '<del class="diffchange diffchange-inline">' .
 1792+ htmlspecialchars ( $this->_group ) . '</del>';
17221793 else
1723 - $this->_line .= htmlspecialchars ( $this->_group );
 1794+ $this->_line .= htmlspecialchars ( $this->_group );
17241795 }
17251796 $this->_group = '';
17261797 $this->_tag = $new_tag;
@@ -1728,21 +1799,21 @@
17291800 function _flushLine ($new_tag) {
17301801 $this->_flushGroup($new_tag);
17311802 if ($this->_line != '')
1732 - array_push ( $this->_lines, $this->_line );
 1803+ array_push ( $this->_lines, $this->_line );
17331804 else
1734 - # make empty lines visible by inserting an NBSP
1735 - array_push ( $this->_lines, NBSP );
 1805+ # make empty lines visible by inserting an NBSP
 1806+ array_push ( $this->_lines, NBSP );
17361807 $this->_line = '';
17371808 }
17381809
17391810 function addWords ($words, $tag = '') {
17401811 if ($tag != $this->_tag)
1741 - $this->_flushGroup($tag);
 1812+ $this->_flushGroup($tag);
17421813
17431814 foreach ($words as $word) {
17441815 // new-line should only come as first char of word.
17451816 if ($word == '')
1746 - continue;
 1817+ continue;
17471818 if ($word[0] == "\n") {
17481819 $this->_flushLine($tag);
17491820 $word = substr($word, 1);
@@ -1773,7 +1844,7 @@
17741845 list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
17751846
17761847 $this->MappedDiff($orig_words, $closing_words,
1777 - $orig_stripped, $closing_stripped);
 1848+ $orig_stripped, $closing_stripped);
17781849 wfProfileOut( __METHOD__ );
17791850 }
17801851
@@ -1798,7 +1869,7 @@
17991870 } else {
18001871 $m = array();
18011872 if (preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1802 - $line, $m))
 1873+ $line, $m))
18031874 {
18041875 $words = array_merge( $words, $m[0] );
18051876 $stripped = array_merge( $stripped, $m[1] );
@@ -1815,9 +1886,9 @@
18161887
18171888 foreach ($this->edits as $edit) {
18181889 if ($edit->type == 'copy')
1819 - $orig->addWords($edit->orig);
 1890+ $orig->addWords($edit->orig);
18201891 elseif ($edit->orig)
1821 - $orig->addWords($edit->orig, 'del');
 1892+ $orig->addWords($edit->orig, 'del');
18221893 }
18231894 $lines = $orig->getLines();
18241895 wfProfileOut( __METHOD__ );
@@ -1830,9 +1901,9 @@
18311902
18321903 foreach ($this->edits as $edit) {
18331904 if ($edit->type == 'copy')
1834 - $closing->addWords($edit->closing);
 1905+ $closing->addWords($edit->closing);
18351906 elseif ($edit->closing)
1836 - $closing->addWords($edit->closing, 'ins');
 1907+ $closing->addWords($edit->closing, 'ins');
18371908 }
18381909 $lines = $closing->getLines();
18391910 wfProfileOut( __METHOD__ );
@@ -1905,24 +1976,24 @@
19061977 function _added( $lines ) {
19071978 foreach ($lines as $line) {
19081979 echo '<tr>' . $this->emptyLine() .
1909 - $this->addedLine( '<ins class="diffchange">' .
1910 - htmlspecialchars ( $line ) . '</ins>' ) . "</tr>\n";
 1980+ $this->addedLine( '<ins class="diffchange">' .
 1981+ htmlspecialchars ( $line ) . '</ins>' ) . "</tr>\n";
19111982 }
19121983 }
19131984
19141985 function _deleted($lines) {
19151986 foreach ($lines as $line) {
19161987 echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
1917 - htmlspecialchars ( $line ) . '</del>' ) .
1918 - $this->emptyLine() . "</tr>\n";
 1988+ htmlspecialchars ( $line ) . '</del>' ) .
 1989+ $this->emptyLine() . "</tr>\n";
19191990 }
19201991 }
19211992
19221993 function _context( $lines ) {
19231994 foreach ($lines as $line) {
19241995 echo '<tr>' .
1925 - $this->contextLine( htmlspecialchars ( $line ) ) .
1926 - $this->contextLine( htmlspecialchars ( $line ) ) . "</tr>\n";
 1996+ $this->contextLine( htmlspecialchars ( $line ) ) .
 1997+ $this->contextLine( htmlspecialchars ( $line ) ) . "</tr>\n";
19271998 }
19281999 }
19292000
@@ -1939,11 +2010,11 @@
19402011 while ( $line = array_shift( $del ) ) {
19412012 $aline = array_shift( $add );
19422013 echo '<tr>' . $this->deletedLine( $line ) .
1943 - $this->addedLine( $aline ) . "</tr>\n";
 2014+ $this->addedLine( $aline ) . "</tr>\n";
19442015 }
19452016 foreach ($add as $line) { # If any leftovers
19462017 echo '<tr>' . $this->emptyLine() .
1947 - $this->addedLine( $line ) . "</tr>\n";
 2018+ $this->addedLine( $line ) . "</tr>\n";
19482019 }
19492020 wfProfileOut( __METHOD__ );
19502021 }

Status & tagging log