This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3) |
||
4 | * |
||
5 | * Additions by Axel Boldt for MediaWiki |
||
6 | * |
||
7 | * @copyright (C) 2000, 2001 Geoffrey T. Dairiki <[email protected]> |
||
8 | * @license You may copy this code freely under the conditions of the GPL. |
||
9 | */ |
||
10 | define('USE_ASSERTS', function_exists('assert')); |
||
11 | |||
12 | class _DiffOp { |
||
13 | var $type; |
||
14 | var $orig; |
||
15 | var $closing; |
||
16 | |||
17 | /** |
||
18 | * @return _DiffOp |
||
19 | */ |
||
20 | function reverse() { |
||
21 | trigger_error("pure virtual", E_USER_ERROR); |
||
22 | } |
||
23 | |||
24 | function norig() { |
||
25 | return $this->orig ? count($this->orig) : 0; |
||
26 | } |
||
27 | |||
28 | function nclosing() { |
||
29 | return $this->closing ? count($this->closing) : 0; |
||
30 | } |
||
31 | } |
||
32 | |||
33 | class _DiffOp_Copy extends _DiffOp { |
||
34 | var $type = 'copy'; |
||
35 | |||
36 | function __construct($orig, $closing = false) { |
||
37 | if (!is_array($closing)) |
||
38 | $closing = $orig; |
||
39 | $this->orig = $orig; |
||
40 | $this->closing = $closing; |
||
41 | } |
||
42 | |||
43 | function reverse() { |
||
44 | return new _DiffOp_Copy($this->closing, $this->orig); |
||
45 | } |
||
46 | } |
||
47 | |||
48 | class _DiffOp_Delete extends _DiffOp { |
||
49 | var $type = 'delete'; |
||
50 | |||
51 | function __construct($lines) { |
||
52 | $this->orig = $lines; |
||
53 | $this->closing = false; |
||
54 | } |
||
55 | |||
56 | function reverse() { |
||
57 | return new _DiffOp_Add($this->orig); |
||
58 | } |
||
59 | } |
||
60 | |||
61 | class _DiffOp_Add extends _DiffOp { |
||
62 | var $type = 'add'; |
||
63 | |||
64 | function __construct($lines) { |
||
65 | $this->closing = $lines; |
||
66 | $this->orig = false; |
||
67 | } |
||
68 | |||
69 | function reverse() { |
||
70 | return new _DiffOp_Delete($this->closing); |
||
71 | } |
||
72 | } |
||
73 | |||
74 | class _DiffOp_Change extends _DiffOp { |
||
75 | var $type = 'change'; |
||
76 | |||
77 | function __construct($orig, $closing) { |
||
78 | $this->orig = $orig; |
||
79 | $this->closing = $closing; |
||
80 | } |
||
81 | |||
82 | function reverse() { |
||
83 | return new _DiffOp_Change($this->closing, $this->orig); |
||
84 | } |
||
85 | } |
||
86 | |||
87 | |||
88 | /** |
||
89 | * Class used internally by Diff to actually compute the diffs. |
||
90 | * |
||
91 | * The algorithm used here is mostly lifted from the perl module |
||
92 | * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: |
||
93 | * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip |
||
94 | * |
||
95 | * More ideas are taken from: |
||
96 | * http://www.ics.uci.edu/~eppstein/161/960229.html |
||
97 | * |
||
98 | * Some ideas are (and a bit of code) are from from analyze.c, from GNU |
||
99 | * diffutils-2.7, which can be found at: |
||
100 | * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz |
||
101 | * |
||
102 | * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations) |
||
103 | * are my own. |
||
104 | * |
||
105 | * @author Geoffrey T. Dairiki |
||
106 | * @access private |
||
107 | */ |
||
108 | class _DiffEngine { |
||
109 | |||
110 | var $xchanged = array(); |
||
111 | var $ychanged = array(); |
||
112 | var $xv = array(); |
||
113 | var $yv = array(); |
||
114 | var $xind = array(); |
||
115 | var $yind = array(); |
||
116 | var $seq; |
||
117 | var $in_seq; |
||
118 | var $lcs; |
||
119 | |||
120 | /** |
||
121 | * @param array $from_lines |
||
122 | * @param array $to_lines |
||
123 | * @return _DiffOp[] |
||
124 | */ |
||
125 | function diff($from_lines, $to_lines) { |
||
126 | $n_from = count($from_lines); |
||
127 | $n_to = count($to_lines); |
||
128 | |||
129 | $this->xchanged = $this->ychanged = array(); |
||
130 | $this->xv = $this->yv = array(); |
||
131 | $this->xind = $this->yind = array(); |
||
132 | unset($this->seq); |
||
133 | unset($this->in_seq); |
||
134 | unset($this->lcs); |
||
135 | |||
136 | // Skip leading common lines. |
||
137 | for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { |
||
138 | if ($from_lines[$skip] != $to_lines[$skip]) |
||
139 | break; |
||
140 | $this->xchanged[$skip] = $this->ychanged[$skip] = false; |
||
141 | } |
||
142 | // Skip trailing common lines. |
||
143 | $xi = $n_from; |
||
144 | $yi = $n_to; |
||
145 | for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { |
||
146 | if ($from_lines[$xi] != $to_lines[$yi]) |
||
147 | break; |
||
148 | $this->xchanged[$xi] = $this->ychanged[$yi] = false; |
||
149 | } |
||
150 | |||
151 | // Ignore lines which do not exist in both files. |
||
152 | for ($xi = $skip; $xi < $n_from - $endskip; $xi++) |
||
153 | $xhash[$from_lines[$xi]] = 1; |
||
154 | for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { |
||
155 | $line = $to_lines[$yi]; |
||
156 | if (($this->ychanged[$yi] = empty($xhash[$line]))) |
||
157 | continue; |
||
158 | $yhash[$line] = 1; |
||
159 | $this->yv[] = $line; |
||
160 | $this->yind[] = $yi; |
||
161 | } |
||
162 | for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { |
||
163 | $line = $from_lines[$xi]; |
||
164 | if (($this->xchanged[$xi] = empty($yhash[$line]))) |
||
165 | continue; |
||
166 | $this->xv[] = $line; |
||
167 | $this->xind[] = $xi; |
||
168 | } |
||
169 | |||
170 | // Find the LCS. |
||
171 | $this->_compareseq(0, count($this->xv), 0, count($this->yv)); |
||
172 | |||
173 | // Merge edits when possible |
||
174 | $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); |
||
175 | $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); |
||
176 | |||
177 | // Compute the edit operations. |
||
178 | $edits = array(); |
||
179 | $xi = $yi = 0; |
||
180 | while ($xi < $n_from || $yi < $n_to) { |
||
181 | USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); |
||
182 | USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]); |
||
183 | |||
184 | // Skip matching "snake". |
||
185 | $copy = array(); |
||
186 | while ($xi < $n_from && $yi < $n_to && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { |
||
187 | $copy[] = $from_lines[$xi++]; |
||
188 | ++$yi; |
||
189 | } |
||
190 | if ($copy) |
||
191 | $edits[] = new _DiffOp_Copy($copy); |
||
192 | |||
193 | // Find deletes & adds. |
||
194 | $delete = array(); |
||
195 | while ($xi < $n_from && $this->xchanged[$xi]) |
||
196 | $delete[] = $from_lines[$xi++]; |
||
197 | |||
198 | $add = array(); |
||
199 | while ($yi < $n_to && $this->ychanged[$yi]) |
||
200 | $add[] = $to_lines[$yi++]; |
||
201 | |||
202 | if ($delete && $add) |
||
203 | $edits[] = new _DiffOp_Change($delete, $add); |
||
204 | elseif ($delete) |
||
205 | $edits[] = new _DiffOp_Delete($delete); |
||
206 | elseif ($add) |
||
207 | $edits[] = new _DiffOp_Add($add); |
||
208 | } |
||
209 | return $edits; |
||
210 | } |
||
211 | |||
212 | |||
213 | /** |
||
214 | * Divide the Largest Common Subsequence (LCS) of the sequences |
||
215 | * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally |
||
216 | * sized segments. |
||
217 | * |
||
218 | * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an |
||
219 | * array of NCHUNKS+1 (X, Y) indexes giving the diving points between |
||
220 | * sub sequences. The first sub-sequence is contained in [X0, X1), |
||
221 | * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note |
||
222 | * that (X0, Y0) == (XOFF, YOFF) and |
||
223 | * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). |
||
224 | * |
||
225 | * This function assumes that the first lines of the specified portions |
||
226 | * of the two files do not match, and likewise that the last lines do not |
||
227 | * match. The caller must trim matching lines from the beginning and end |
||
228 | * of the portions it is going to specify. |
||
229 | * |
||
230 | * @param integer $xoff |
||
231 | * @param integer $xlim |
||
232 | * @param integer $yoff |
||
233 | * @param integer $ylim |
||
234 | * @param integer $nchunks |
||
235 | * |
||
236 | * @return array |
||
237 | */ |
||
238 | function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) { |
||
239 | $flip = false; |
||
240 | |||
241 | if ($xlim - $xoff > $ylim - $yoff) { |
||
242 | // Things seems faster (I'm not sure I understand why) |
||
243 | // when the shortest sequence in X. |
||
244 | $flip = true; |
||
245 | list ($xoff, $xlim, $yoff, $ylim) = array($yoff, $ylim, $xoff, $xlim); |
||
246 | } |
||
247 | |||
248 | if ($flip) |
||
249 | for ($i = $ylim - 1; $i >= $yoff; $i--) |
||
250 | $ymatches[$this->xv[$i]][] = $i; |
||
251 | else |
||
252 | for ($i = $ylim - 1; $i >= $yoff; $i--) |
||
253 | $ymatches[$this->yv[$i]][] = $i; |
||
254 | |||
255 | $this->lcs = 0; |
||
256 | $this->seq[0]= $yoff - 1; |
||
257 | $this->in_seq = array(); |
||
258 | $ymids[0] = array(); |
||
259 | |||
260 | $numer = $xlim - $xoff + $nchunks - 1; |
||
261 | $x = $xoff; |
||
262 | for ($chunk = 0; $chunk < $nchunks; $chunk++) { |
||
263 | if ($chunk > 0) |
||
264 | for ($i = 0; $i <= $this->lcs; $i++) |
||
265 | $ymids[$i][$chunk-1] = $this->seq[$i]; |
||
266 | |||
267 | $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); |
||
268 | for ( ; $x < $x1; $x++) { |
||
269 | $line = $flip ? $this->yv[$x] : $this->xv[$x]; |
||
270 | if (empty($ymatches[$line])) |
||
271 | continue; |
||
272 | $matches = $ymatches[$line]; |
||
273 | $switch = false; |
||
274 | foreach ($matches as $y) { |
||
275 | if ($switch && $y > $this->seq[$k-1]) { |
||
276 | USE_ASSERTS && assert($y < $this->seq[$k]); |
||
277 | // Optimization: this is a common case: |
||
278 | // next match is just replacing previous match. |
||
279 | $this->in_seq[$this->seq[$k]] = false; |
||
280 | $this->seq[$k] = $y; |
||
281 | $this->in_seq[$y] = 1; |
||
282 | } |
||
283 | else if (empty($this->in_seq[$y])) { |
||
284 | $k = $this->_lcs_pos($y); |
||
285 | USE_ASSERTS && assert($k > 0); |
||
286 | $ymids[$k] = $ymids[$k-1]; |
||
287 | $switch = true; |
||
288 | } |
||
289 | } |
||
290 | } |
||
291 | } |
||
292 | |||
293 | $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); |
||
294 | $ymid = $ymids[$this->lcs]; |
||
295 | for ($n = 0; $n < $nchunks - 1; $n++) { |
||
296 | $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); |
||
297 | $y1 = $ymid[$n] + 1; |
||
298 | $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); |
||
299 | } |
||
300 | $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); |
||
301 | |||
302 | return array($this->lcs, $seps); |
||
303 | } |
||
304 | |||
305 | function _lcs_pos($ypos) { |
||
306 | $end = $this->lcs; |
||
307 | if ($end == 0 || $ypos > $this->seq[$end]) { |
||
308 | $this->seq[++$this->lcs] = $ypos; |
||
309 | $this->in_seq[$ypos] = 1; |
||
310 | return $this->lcs; |
||
311 | } |
||
312 | |||
313 | $beg = 1; |
||
314 | while ($beg < $end) { |
||
315 | $mid = (int)(($beg + $end) / 2); |
||
316 | if ($ypos > $this->seq[$mid]) |
||
317 | $beg = $mid + 1; |
||
318 | else |
||
319 | $end = $mid; |
||
320 | } |
||
321 | |||
322 | USE_ASSERTS && assert($ypos != $this->seq[$end]); |
||
323 | |||
324 | $this->in_seq[$this->seq[$end]] = false; |
||
325 | $this->seq[$end] = $ypos; |
||
326 | $this->in_seq[$ypos] = 1; |
||
327 | return $end; |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Find LCS of two sequences. |
||
332 | * |
||
333 | * The results are recorded in the vectors $this->{x,y}changed[], by |
||
334 | * storing a 1 in the element for each line that is an insertion |
||
335 | * or deletion (ie. is not in the LCS). |
||
336 | * |
||
337 | * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. |
||
338 | * |
||
339 | * Note that XLIM, YLIM are exclusive bounds. |
||
340 | * All line numbers are origin-0 and discarded lines are not counted. |
||
341 | * |
||
342 | * @param integer $xoff |
||
343 | * @param integer $xlim |
||
344 | * @param integer $yoff |
||
345 | * @param integer $ylim |
||
346 | */ |
||
347 | function _compareseq($xoff, $xlim, $yoff, $ylim) { |
||
348 | // Slide down the bottom initial diagonal. |
||
349 | while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) { |
||
350 | ++$xoff; |
||
351 | ++$yoff; |
||
352 | } |
||
353 | |||
354 | // Slide up the top initial diagonal. |
||
355 | while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { |
||
356 | --$xlim; |
||
357 | --$ylim; |
||
358 | } |
||
359 | |||
360 | if ($xoff == $xlim || $yoff == $ylim) |
||
361 | $lcs = 0; |
||
362 | else { |
||
363 | // This is ad hoc but seems to work well. |
||
364 | //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); |
||
365 | //$nchunks = max(2,min(8,(int)$nchunks)); |
||
366 | $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; |
||
367 | list ($lcs, $seps) |
||
368 | = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); |
||
369 | } |
||
370 | |||
371 | if ($lcs == 0) { |
||
372 | // X and Y sequences have no common subsequence: |
||
373 | // mark all changed. |
||
374 | while ($yoff < $ylim) |
||
375 | $this->ychanged[$this->yind[$yoff++]] = 1; |
||
376 | while ($xoff < $xlim) |
||
377 | $this->xchanged[$this->xind[$xoff++]] = 1; |
||
378 | } |
||
379 | else { |
||
380 | // Use the partitions to split this problem into subproblems. |
||
381 | reset($seps); |
||
382 | $pt1 = $seps[0]; |
||
383 | while ($pt2 = next($seps)) { |
||
384 | $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); |
||
385 | $pt1 = $pt2; |
||
386 | } |
||
387 | } |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Adjust inserts/deletes of identical lines to join changes |
||
392 | * as much as possible. |
||
393 | * |
||
394 | * We do something when a run of changed lines include a |
||
395 | * line at one end and has an excluded, identical line at the other. |
||
396 | * We are free to choose which identical line is included. |
||
397 | * `compareseq' usually chooses the one at the beginning, |
||
398 | * but usually it is cleaner to consider the following identical line |
||
399 | * to be the "change". |
||
400 | * |
||
401 | * This is extracted verbatim from analyze.c (GNU diffutils-2.7). |
||
402 | * |
||
403 | * @param array $lines |
||
404 | * @param array $changed |
||
405 | * @param array $other_changed |
||
406 | */ |
||
407 | function _shift_boundaries($lines, &$changed, $other_changed) { |
||
408 | $i = 0; |
||
409 | $j = 0; |
||
410 | |||
411 | USE_ASSERTS && assert(count($lines) == count($changed)); |
||
412 | $len = count($lines); |
||
413 | $other_len = count($other_changed); |
||
414 | |||
415 | while (1) { |
||
416 | /* |
||
417 | * Scan forwards to find beginning of another run of changes. |
||
418 | * Also keep track of the corresponding point in the other file. |
||
419 | * |
||
420 | * Throughout this code, $i and $j are adjusted together so that |
||
421 | * the first $i elements of $changed and the first $j elements |
||
422 | * of $other_changed both contain the same number of zeros |
||
423 | * (unchanged lines). |
||
424 | * Furthermore, $j is always kept so that $j == $other_len or |
||
425 | * $other_changed[$j] == false. |
||
426 | */ |
||
427 | while ($j < $other_len && $other_changed[$j]) |
||
428 | $j++; |
||
429 | |||
430 | while ($i < $len && ! $changed[$i]) { |
||
431 | USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]); |
||
432 | $i++; |
||
433 | $j++; |
||
434 | while ($j < $other_len && $other_changed[$j]) |
||
435 | $j++; |
||
436 | } |
||
437 | |||
438 | if ($i == $len) |
||
439 | break; |
||
440 | |||
441 | $start = $i; |
||
442 | |||
443 | // Find the end of this run of changes. |
||
444 | while (++$i < $len && $changed[$i]) |
||
445 | continue; |
||
446 | |||
447 | do { |
||
448 | /* |
||
449 | * Record the length of this run of changes, so that |
||
450 | * we can later determine whether the run has grown. |
||
451 | */ |
||
452 | $runlength = $i - $start; |
||
453 | |||
454 | /* |
||
455 | * Move the changed region back, so long as the |
||
456 | * previous unchanged line matches the last changed one. |
||
457 | * This merges with previous changed regions. |
||
458 | */ |
||
459 | while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { |
||
460 | $changed[--$start] = 1; |
||
461 | $changed[--$i] = false; |
||
462 | while ($start > 0 && $changed[$start - 1]) |
||
463 | $start--; |
||
464 | USE_ASSERTS && assert($j > 0); |
||
465 | while ($other_changed[--$j]) |
||
466 | continue; |
||
467 | USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]); |
||
468 | } |
||
469 | |||
470 | /* |
||
471 | * Set CORRESPONDING to the end of the changed run, at the last |
||
472 | * point where it corresponds to a changed run in the other file. |
||
473 | * CORRESPONDING == LEN means no such point has been found. |
||
474 | */ |
||
475 | $corresponding = $j < $other_len ? $i : $len; |
||
476 | |||
477 | /* |
||
478 | * Move the changed region forward, so long as the |
||
479 | * first changed line matches the following unchanged one. |
||
480 | * This merges with following changed regions. |
||
481 | * Do this second, so that if there are no merges, |
||
482 | * the changed region is moved forward as far as possible. |
||
483 | */ |
||
484 | while ($i < $len && $lines[$start] == $lines[$i]) { |
||
485 | $changed[$start++] = false; |
||
486 | $changed[$i++] = 1; |
||
487 | while ($i < $len && $changed[$i]) |
||
488 | $i++; |
||
489 | |||
490 | USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]); |
||
491 | $j++; |
||
492 | if ($j < $other_len && $other_changed[$j]) { |
||
493 | $corresponding = $i; |
||
494 | while ($j < $other_len && $other_changed[$j]) |
||
495 | $j++; |
||
496 | } |
||
497 | } |
||
498 | } while ($runlength != $i - $start); |
||
499 | |||
500 | /* |
||
501 | * If possible, move the fully-merged run of changes |
||
502 | * back to a corresponding run in the other file. |
||
503 | */ |
||
504 | while ($corresponding < $i) { |
||
505 | $changed[--$start] = 1; |
||
506 | $changed[--$i] = 0; |
||
507 | USE_ASSERTS && assert($j > 0); |
||
508 | while ($other_changed[--$j]) |
||
509 | continue; |
||
510 | USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]); |
||
511 | } |
||
512 | } |
||
513 | } |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * Class representing a 'diff' between two sequences of strings. |
||
518 | */ |
||
519 | class Diff { |
||
520 | |||
521 | var $edits; |
||
522 | |||
523 | /** |
||
524 | * Constructor. |
||
525 | * Computes diff between sequences of strings. |
||
526 | * |
||
527 | * @param array $from_lines An array of strings. |
||
528 | * (Typically these are lines from a file.) |
||
529 | * @param array $to_lines An array of strings. |
||
530 | */ |
||
531 | function __construct($from_lines, $to_lines) { |
||
532 | $eng = new _DiffEngine; |
||
533 | $this->edits = $eng->diff($from_lines, $to_lines); |
||
534 | //$this->_check($from_lines, $to_lines); |
||
535 | } |
||
536 | |||
537 | /** |
||
538 | * Compute reversed Diff. |
||
539 | * |
||
540 | * SYNOPSIS: |
||
541 | * |
||
542 | * $diff = new Diff($lines1, $lines2); |
||
543 | * $rev = $diff->reverse(); |
||
544 | * |
||
545 | * @return Diff A Diff object representing the inverse of the |
||
546 | * original diff. |
||
547 | */ |
||
548 | function reverse() { |
||
549 | $rev = $this; |
||
550 | $rev->edits = array(); |
||
551 | foreach ($this->edits as $edit) { |
||
552 | $rev->edits[] = $edit->reverse(); |
||
553 | } |
||
554 | return $rev; |
||
555 | } |
||
556 | |||
557 | /** |
||
558 | * Check for empty diff. |
||
559 | * |
||
560 | * @return bool True iff two sequences were identical. |
||
561 | */ |
||
562 | function isEmpty() { |
||
563 | foreach ($this->edits as $edit) { |
||
564 | if ($edit->type != 'copy') |
||
565 | return false; |
||
566 | } |
||
567 | return true; |
||
568 | } |
||
569 | |||
570 | /** |
||
571 | * Compute the length of the Longest Common Subsequence (LCS). |
||
572 | * |
||
573 | * This is mostly for diagnostic purposed. |
||
574 | * |
||
575 | * @return int The length of the LCS. |
||
576 | */ |
||
577 | function lcs() { |
||
578 | $lcs = 0; |
||
579 | foreach ($this->edits as $edit) { |
||
580 | if ($edit->type == 'copy') |
||
581 | $lcs += count($edit->orig); |
||
582 | } |
||
583 | return $lcs; |
||
584 | } |
||
585 | |||
586 | /** |
||
587 | * Get the original set of lines. |
||
588 | * |
||
589 | * This reconstructs the $from_lines parameter passed to the |
||
590 | * constructor. |
||
591 | * |
||
592 | * @return array The original sequence of strings. |
||
593 | */ |
||
594 | function orig() { |
||
595 | $lines = array(); |
||
596 | |||
597 | foreach ($this->edits as $edit) { |
||
598 | if ($edit->orig) |
||
599 | array_splice($lines, count($lines), 0, $edit->orig); |
||
600 | } |
||
601 | return $lines; |
||
602 | } |
||
603 | |||
604 | /** |
||
605 | * Get the closing set of lines. |
||
606 | * |
||
607 | * This reconstructs the $to_lines parameter passed to the |
||
608 | * constructor. |
||
609 | * |
||
610 | * @return array The sequence of strings. |
||
611 | */ |
||
612 | function closing() { |
||
613 | $lines = array(); |
||
614 | |||
615 | foreach ($this->edits as $edit) { |
||
616 | if ($edit->closing) |
||
617 | array_splice($lines, count($lines), 0, $edit->closing); |
||
618 | } |
||
619 | return $lines; |
||
620 | } |
||
621 | |||
622 | /** |
||
623 | * Check a Diff for validity. |
||
624 | * |
||
625 | * This is here only for debugging purposes. |
||
626 | * |
||
627 | * @param mixed $from_lines |
||
628 | * @param mixed $to_lines |
||
629 | */ |
||
630 | function _check($from_lines, $to_lines) { |
||
631 | if (serialize($from_lines) != serialize($this->orig())) |
||
632 | trigger_error("Reconstructed original doesn't match", E_USER_ERROR); |
||
633 | if (serialize($to_lines) != serialize($this->closing())) |
||
634 | trigger_error("Reconstructed closing doesn't match", E_USER_ERROR); |
||
635 | |||
636 | $rev = $this->reverse(); |
||
637 | if (serialize($to_lines) != serialize($rev->orig())) |
||
638 | trigger_error("Reversed original doesn't match", E_USER_ERROR); |
||
639 | if (serialize($from_lines) != serialize($rev->closing())) |
||
640 | trigger_error("Reversed closing doesn't match", E_USER_ERROR); |
||
641 | |||
642 | $prevtype = 'none'; |
||
643 | foreach ($this->edits as $edit) { |
||
644 | if ($prevtype == $edit->type) |
||
645 | trigger_error("Edit sequence is non-optimal", E_USER_ERROR); |
||
646 | $prevtype = $edit->type; |
||
647 | } |
||
648 | |||
649 | $lcs = $this->lcs(); |
||
650 | trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); |
||
651 | } |
||
652 | } |
||
653 | |||
654 | /** |
||
655 | * FIXME: bad name. |
||
656 | */ |
||
657 | class MappedDiff extends Diff { |
||
658 | /** |
||
659 | * Constructor. |
||
660 | * |
||
661 | * Computes diff between sequences of strings. |
||
662 | * |
||
663 | * This can be used to compute things like |
||
664 | * case-insensitve diffs, or diffs which ignore |
||
665 | * changes in white-space. |
||
666 | * |
||
667 | * @param string[] $from_lines An array of strings. |
||
668 | * (Typically these are lines from a file.) |
||
669 | * |
||
670 | * @param string[] $to_lines An array of strings. |
||
671 | * |
||
672 | * @param string[] $mapped_from_lines This array should |
||
673 | * have the same size number of elements as $from_lines. |
||
674 | * The elements in $mapped_from_lines and |
||
675 | * $mapped_to_lines are what is actually compared |
||
676 | * when computing the diff. |
||
677 | * |
||
678 | * @param string[] $mapped_to_lines This array should |
||
679 | * have the same number of elements as $to_lines. |
||
680 | */ |
||
681 | function __construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) { |
||
682 | |||
683 | assert(count($from_lines) == count($mapped_from_lines)); |
||
684 | assert(count($to_lines) == count($mapped_to_lines)); |
||
685 | |||
686 | parent::__construct($mapped_from_lines, $mapped_to_lines); |
||
687 | |||
688 | $xi = $yi = 0; |
||
689 | $ecnt = count($this->edits); |
||
690 | for ($i = 0; $i < $ecnt; $i++) { |
||
691 | $orig = &$this->edits[$i]->orig; |
||
692 | if (is_array($orig)) { |
||
693 | $orig = array_slice($from_lines, $xi, count($orig)); |
||
694 | $xi += count($orig); |
||
695 | } |
||
696 | |||
697 | $closing = &$this->edits[$i]->closing; |
||
698 | if (is_array($closing)) { |
||
699 | $closing = array_slice($to_lines, $yi, count($closing)); |
||
700 | $yi += count($closing); |
||
701 | } |
||
702 | } |
||
703 | } |
||
704 | } |
||
705 | |||
706 | /** |
||
707 | * A class to format Diffs |
||
708 | * |
||
709 | * This class formats the diff in classic diff format. |
||
710 | * It is intended that this class be customized via inheritance, |
||
711 | * to obtain fancier outputs. |
||
712 | */ |
||
713 | class DiffFormatter { |
||
714 | /** |
||
715 | * Number of leading context "lines" to preserve. |
||
716 | * |
||
717 | * This should be left at zero for this class, but subclasses |
||
718 | * may want to set this to other values. |
||
719 | */ |
||
720 | var $leading_context_lines = 0; |
||
721 | |||
722 | /** |
||
723 | * Number of trailing context "lines" to preserve. |
||
724 | * |
||
725 | * This should be left at zero for this class, but subclasses |
||
726 | * may want to set this to other values. |
||
727 | */ |
||
728 | var $trailing_context_lines = 0; |
||
729 | |||
730 | /** |
||
731 | * Format a diff. |
||
732 | * |
||
733 | * @param Diff $diff A Diff object. |
||
734 | * @return string The formatted output. |
||
735 | */ |
||
736 | function format($diff) { |
||
737 | |||
738 | $xi = $yi = 1; |
||
739 | $x0 = $y0 = 0; |
||
740 | $block = false; |
||
741 | $context = array(); |
||
742 | |||
743 | $nlead = $this->leading_context_lines; |
||
744 | $ntrail = $this->trailing_context_lines; |
||
745 | |||
746 | $this->_start_diff(); |
||
747 | |||
748 | foreach ($diff->edits as $edit) { |
||
749 | if ($edit->type == 'copy') { |
||
750 | if (is_array($block)) { |
||
751 | if (count($edit->orig) <= $nlead + $ntrail) { |
||
752 | $block[] = $edit; |
||
753 | } |
||
754 | else{ |
||
755 | if ($ntrail) { |
||
756 | $context = array_slice($edit->orig, 0, $ntrail); |
||
757 | $block[] = new _DiffOp_Copy($context); |
||
758 | } |
||
759 | $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block); |
||
760 | $block = false; |
||
761 | } |
||
762 | } |
||
763 | $context = $edit->orig; |
||
764 | } |
||
765 | else { |
||
766 | if (! is_array($block)) { |
||
767 | $context = array_slice($context, count($context) - $nlead); |
||
768 | $x0 = $xi - count($context); |
||
769 | $y0 = $yi - count($context); |
||
770 | $block = array(); |
||
771 | if ($context) |
||
772 | $block[] = new _DiffOp_Copy($context); |
||
773 | } |
||
774 | $block[] = $edit; |
||
775 | } |
||
776 | |||
777 | if ($edit->orig) |
||
778 | $xi += count($edit->orig); |
||
779 | if ($edit->closing) |
||
780 | $yi += count($edit->closing); |
||
781 | } |
||
782 | |||
783 | if (is_array($block)) |
||
784 | $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block); |
||
785 | |||
786 | return $this->_end_diff(); |
||
787 | } |
||
788 | |||
789 | /** |
||
790 | * @param int $xbeg |
||
791 | * @param int $xlen |
||
792 | * @param int $ybeg |
||
793 | * @param int $ylen |
||
794 | * @param array $edits |
||
795 | */ |
||
796 | function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { |
||
797 | $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); |
||
798 | foreach ($edits as $edit) { |
||
799 | if ($edit->type == 'copy') |
||
800 | $this->_context($edit->orig); |
||
801 | elseif ($edit->type == 'add') |
||
802 | $this->_added($edit->closing); |
||
803 | elseif ($edit->type == 'delete') |
||
804 | $this->_deleted($edit->orig); |
||
805 | elseif ($edit->type == 'change') |
||
806 | $this->_changed($edit->orig, $edit->closing); |
||
807 | else |
||
808 | trigger_error("Unknown edit type", E_USER_ERROR); |
||
809 | } |
||
810 | $this->_end_block(); |
||
811 | } |
||
812 | |||
813 | function _start_diff() { |
||
814 | ob_start(); |
||
815 | } |
||
816 | |||
817 | function _end_diff() { |
||
818 | $val = ob_get_contents(); |
||
819 | ob_end_clean(); |
||
820 | return $val; |
||
821 | } |
||
822 | |||
823 | /** |
||
824 | * @param int $xbeg |
||
825 | * @param int $xlen |
||
826 | * @param int $ybeg |
||
827 | * @param int $ylen |
||
828 | * @return string |
||
829 | */ |
||
830 | function _block_header($xbeg, $xlen, $ybeg, $ylen) { |
||
831 | if ($xlen > 1) |
||
832 | $xbeg .= "," . ($xbeg + $xlen - 1); |
||
833 | if ($ylen > 1) |
||
834 | $ybeg .= "," . ($ybeg + $ylen - 1); |
||
835 | |||
836 | return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; |
||
837 | } |
||
838 | |||
839 | /** |
||
840 | * @param string $header |
||
841 | */ |
||
842 | function _start_block($header) { |
||
843 | echo $header; |
||
844 | } |
||
845 | |||
846 | function _end_block() { |
||
847 | } |
||
848 | |||
849 | function _lines($lines, $prefix = ' ') { |
||
850 | foreach ($lines as $line) |
||
851 | echo "$prefix ".$this->_escape($line)."\n"; |
||
852 | } |
||
853 | |||
854 | function _context($lines) { |
||
855 | $this->_lines($lines); |
||
856 | } |
||
857 | |||
858 | function _added($lines) { |
||
859 | $this->_lines($lines, ">"); |
||
860 | } |
||
861 | function _deleted($lines) { |
||
862 | $this->_lines($lines, "<"); |
||
863 | } |
||
864 | |||
865 | function _changed($orig, $closing) { |
||
866 | $this->_deleted($orig); |
||
867 | echo "---\n"; |
||
868 | $this->_added($closing); |
||
869 | } |
||
870 | |||
871 | /** |
||
872 | * Escape string |
||
873 | * |
||
874 | * Override this method within other formatters if escaping required. |
||
875 | * Base class requires $str to be returned WITHOUT escaping. |
||
876 | * |
||
877 | * @param $str string Text string to escape |
||
878 | * @return string The escaped string. |
||
879 | */ |
||
880 | function _escape($str){ |
||
881 | return $str; |
||
882 | } |
||
883 | } |
||
884 | |||
885 | /** |
||
886 | * Utilityclass for styling HTML formatted diffs |
||
887 | * |
||
888 | * Depends on global var $DIFF_INLINESTYLES, if true some minimal predefined |
||
889 | * inline styles are used. Useful for HTML mails and RSS feeds |
||
890 | * |
||
891 | * @author Andreas Gohr <[email protected]> |
||
892 | */ |
||
893 | class HTMLDiff { |
||
894 | /** |
||
895 | * Holds the style names and basic CSS |
||
896 | */ |
||
897 | static public $styles = array( |
||
898 | 'diff-addedline' => 'background-color: #ddffdd;', |
||
899 | 'diff-deletedline' => 'background-color: #ffdddd;', |
||
900 | 'diff-context' => 'background-color: #f5f5f5;', |
||
901 | 'diff-mark' => 'color: #ff0000;', |
||
902 | ); |
||
903 | |||
904 | /** |
||
905 | * Return a class or style parameter |
||
906 | * |
||
907 | * @param string $classname |
||
908 | * |
||
909 | * @return string |
||
910 | */ |
||
911 | static function css($classname){ |
||
912 | global $DIFF_INLINESTYLES; |
||
913 | |||
914 | if($DIFF_INLINESTYLES){ |
||
915 | if(!isset(self::$styles[$classname])) return ''; |
||
916 | return 'style="'.self::$styles[$classname].'"'; |
||
917 | }else{ |
||
918 | return 'class="'.$classname.'"'; |
||
919 | } |
||
920 | } |
||
921 | } |
||
922 | |||
923 | /** |
||
924 | * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 |
||
925 | * |
||
926 | */ |
||
927 | |||
928 | define('NBSP', "\xC2\xA0"); // utf-8 non-breaking space. |
||
929 | |||
930 | class _HWLDF_WordAccumulator { |
||
931 | |||
932 | function __construct() { |
||
933 | $this->_lines = array(); |
||
934 | $this->_line = ''; |
||
935 | $this->_group = ''; |
||
936 | $this->_tag = ''; |
||
937 | } |
||
938 | |||
939 | function _flushGroup($new_tag) { |
||
940 | if ($this->_group !== '') { |
||
941 | if ($this->_tag == 'mark') |
||
942 | $this->_line .= '<strong '.HTMLDiff::css('diff-mark').'>'.$this->_escape($this->_group).'</strong>'; |
||
943 | elseif ($this->_tag == 'add') |
||
944 | $this->_line .= '<span '.HTMLDiff::css('diff-addedline').'>'.$this->_escape($this->_group).'</span>'; |
||
945 | elseif ($this->_tag == 'del') |
||
946 | $this->_line .= '<span '.HTMLDiff::css('diff-deletedline').'><del>'.$this->_escape($this->_group).'</del></span>'; |
||
947 | else |
||
948 | $this->_line .= $this->_escape($this->_group); |
||
949 | } |
||
950 | $this->_group = ''; |
||
951 | $this->_tag = $new_tag; |
||
952 | } |
||
953 | |||
954 | /** |
||
955 | * @param string $new_tag |
||
956 | */ |
||
957 | function _flushLine($new_tag) { |
||
958 | $this->_flushGroup($new_tag); |
||
959 | if ($this->_line != '') |
||
960 | $this->_lines[] = $this->_line; |
||
961 | $this->_line = ''; |
||
962 | } |
||
963 | |||
964 | function addWords($words, $tag = '') { |
||
965 | if ($tag != $this->_tag) |
||
966 | $this->_flushGroup($tag); |
||
967 | |||
968 | foreach ($words as $word) { |
||
969 | // new-line should only come as first char of word. |
||
970 | if ($word == '') |
||
971 | continue; |
||
972 | if ($word[0] == "\n") { |
||
973 | $this->_group .= NBSP; |
||
974 | $this->_flushLine($tag); |
||
975 | $word = substr($word, 1); |
||
976 | } |
||
977 | assert(!strstr($word, "\n")); |
||
978 | $this->_group .= $word; |
||
979 | } |
||
980 | } |
||
981 | |||
982 | function getLines() { |
||
983 | $this->_flushLine('~done'); |
||
984 | return $this->_lines; |
||
985 | } |
||
986 | |||
987 | function _escape($str){ |
||
988 | return hsc($str); |
||
989 | } |
||
990 | } |
||
991 | |||
992 | class WordLevelDiff extends MappedDiff { |
||
993 | |||
994 | function __construct($orig_lines, $closing_lines) { |
||
995 | list ($orig_words, $orig_stripped) = $this->_split($orig_lines); |
||
996 | list ($closing_words, $closing_stripped) = $this->_split($closing_lines); |
||
997 | |||
998 | parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped); |
||
999 | } |
||
1000 | |||
1001 | function _split($lines) { |
||
1002 | if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu', |
||
1003 | implode("\n", $lines), $m)) { |
||
1004 | return array(array(''), array('')); |
||
1005 | } |
||
1006 | return array($m[0], $m[1]); |
||
1007 | } |
||
1008 | |||
1009 | function orig() { |
||
1010 | $orig = new _HWLDF_WordAccumulator; |
||
1011 | |||
1012 | foreach ($this->edits as $edit) { |
||
1013 | if ($edit->type == 'copy') |
||
1014 | $orig->addWords($edit->orig); |
||
1015 | elseif ($edit->orig) |
||
1016 | $orig->addWords($edit->orig, 'mark'); |
||
1017 | } |
||
1018 | return $orig->getLines(); |
||
1019 | } |
||
1020 | |||
1021 | function closing() { |
||
1022 | $closing = new _HWLDF_WordAccumulator; |
||
1023 | |||
1024 | foreach ($this->edits as $edit) { |
||
1025 | if ($edit->type == 'copy') |
||
1026 | $closing->addWords($edit->closing); |
||
1027 | elseif ($edit->closing) |
||
1028 | $closing->addWords($edit->closing, 'mark'); |
||
1029 | } |
||
1030 | return $closing->getLines(); |
||
1031 | } |
||
1032 | } |
||
1033 | |||
1034 | class InlineWordLevelDiff extends MappedDiff { |
||
1035 | |||
1036 | function __construct($orig_lines, $closing_lines) { |
||
1037 | list ($orig_words, $orig_stripped) = $this->_split($orig_lines); |
||
1038 | list ($closing_words, $closing_stripped) = $this->_split($closing_lines); |
||
1039 | |||
1040 | parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped); |
||
1041 | } |
||
1042 | |||
1043 | function _split($lines) { |
||
0 ignored issues
–
show
|
|||
1044 | if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu', |
||
1045 | implode("\n", $lines), $m)) { |
||
1046 | return array(array(''), array('')); |
||
1047 | } |
||
1048 | return array($m[0], $m[1]); |
||
1049 | } |
||
1050 | |||
1051 | function inline() { |
||
0 ignored issues
–
show
|
|||
1052 | $orig = new _HWLDF_WordAccumulator; |
||
1053 | foreach ($this->edits as $edit) { |
||
1054 | if ($edit->type == 'copy') |
||
1055 | $orig->addWords($edit->closing); |
||
1056 | elseif ($edit->type == 'change'){ |
||
1057 | $orig->addWords($edit->orig, 'del'); |
||
1058 | $orig->addWords($edit->closing, 'add'); |
||
1059 | } elseif ($edit->type == 'delete') |
||
1060 | $orig->addWords($edit->orig, 'del'); |
||
1061 | elseif ($edit->type == 'add') |
||
1062 | $orig->addWords($edit->closing, 'add'); |
||
1063 | elseif ($edit->orig) |
||
1064 | $orig->addWords($edit->orig, 'del'); |
||
1065 | } |
||
1066 | return $orig->getLines(); |
||
1067 | } |
||
1068 | } |
||
1069 | |||
1070 | /** |
||
1071 | * "Unified" diff formatter. |
||
1072 | * |
||
1073 | * This class formats the diff in classic "unified diff" format. |
||
1074 | * |
||
1075 | * NOTE: output is plain text and unsafe for use in HTML without escaping. |
||
1076 | */ |
||
1077 | class UnifiedDiffFormatter extends DiffFormatter { |
||
1078 | |||
1079 | function __construct($context_lines = 4) { |
||
1080 | $this->leading_context_lines = $context_lines; |
||
1081 | $this->trailing_context_lines = $context_lines; |
||
1082 | } |
||
1083 | |||
1084 | function _block_header($xbeg, $xlen, $ybeg, $ylen) { |
||
1085 | if ($xlen != 1) |
||
1086 | $xbeg .= "," . $xlen; |
||
1087 | if ($ylen != 1) |
||
1088 | $ybeg .= "," . $ylen; |
||
1089 | return "@@ -$xbeg +$ybeg @@\n"; |
||
1090 | } |
||
1091 | |||
1092 | function _added($lines) { |
||
1093 | $this->_lines($lines, "+"); |
||
1094 | } |
||
1095 | function _deleted($lines) { |
||
1096 | $this->_lines($lines, "-"); |
||
1097 | } |
||
1098 | function _changed($orig, $final) { |
||
1099 | $this->_deleted($orig); |
||
1100 | $this->_added($final); |
||
1101 | } |
||
1102 | } |
||
1103 | |||
1104 | /** |
||
1105 | * Wikipedia Table style diff formatter. |
||
1106 | * |
||
1107 | */ |
||
1108 | class TableDiffFormatter extends DiffFormatter { |
||
1109 | var $colspan = 2; |
||
1110 | |||
1111 | function __construct() { |
||
1112 | $this->leading_context_lines = 2; |
||
1113 | $this->trailing_context_lines = 2; |
||
1114 | } |
||
1115 | |||
1116 | /** |
||
1117 | * @param Diff $diff |
||
1118 | * @return string |
||
1119 | */ |
||
1120 | function format($diff) { |
||
0 ignored issues
–
show
|
|||
1121 | // Preserve whitespaces by converting some to non-breaking spaces. |
||
1122 | // Do not convert all of them to allow word-wrap. |
||
1123 | $val = parent::format($diff); |
||
1124 | $val = str_replace(' ','  ', $val); |
||
1125 | $val = preg_replace('/ (?=<)|(?<=[ >]) /', ' ', $val); |
||
1126 | return $val; |
||
1127 | } |
||
1128 | |||
1129 | function _pre($text){ |
||
1130 | $text = htmlspecialchars($text); |
||
1131 | return $text; |
||
1132 | } |
||
1133 | |||
1134 | function _block_header($xbeg, $xlen, $ybeg, $ylen) { |
||
1135 | global $lang; |
||
1136 | $l1 = $lang['line'].' '.$xbeg; |
||
1137 | $l2 = $lang['line'].' '.$ybeg; |
||
1138 | $r = '<tr><td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l1.":</td>\n". |
||
1139 | '<td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l2.":</td>\n". |
||
1140 | "</tr>\n"; |
||
1141 | return $r; |
||
1142 | } |
||
1143 | |||
1144 | function _start_block($header) { |
||
1145 | print($header); |
||
1146 | } |
||
1147 | |||
1148 | function _end_block() { |
||
1149 | } |
||
1150 | |||
1151 | function _lines($lines, $prefix=' ', $color="white") { |
||
1152 | } |
||
1153 | |||
1154 | function addedLine($line,$escaped=false) { |
||
1155 | if (!$escaped){ |
||
1156 | $line = $this->_escape($line); |
||
1157 | } |
||
1158 | return '<td '.HTMLDiff::css('diff-lineheader').'>+</td>'. |
||
1159 | '<td '.HTMLDiff::css('diff-addedline').'>' . $line.'</td>'; |
||
1160 | } |
||
1161 | |||
1162 | function deletedLine($line,$escaped=false) { |
||
1163 | if (!$escaped){ |
||
1164 | $line = $this->_escape($line); |
||
1165 | } |
||
1166 | return '<td '.HTMLDiff::css('diff-lineheader').'>-</td>'. |
||
1167 | '<td '.HTMLDiff::css('diff-deletedline').'>' . $line.'</td>'; |
||
1168 | } |
||
1169 | |||
1170 | function emptyLine() { |
||
1171 | return '<td colspan="'.$this->colspan.'"> </td>'; |
||
1172 | } |
||
1173 | |||
1174 | function contextLine($line) { |
||
1175 | return '<td '.HTMLDiff::css('diff-lineheader').'> </td>'. |
||
1176 | '<td '.HTMLDiff::css('diff-context').'>'.$this->_escape($line).'</td>'; |
||
1177 | } |
||
1178 | |||
1179 | function _added($lines) { |
||
1180 | $this->_addedLines($lines,false); |
||
1181 | } |
||
1182 | |||
1183 | function _addedLines($lines,$escaped=false){ |
||
1184 | foreach ($lines as $line) { |
||
1185 | print('<tr>' . $this->emptyLine() . $this->addedLine($line,$escaped) . "</tr>\n"); |
||
1186 | } |
||
1187 | } |
||
1188 | |||
1189 | function _deleted($lines) { |
||
1190 | foreach ($lines as $line) { |
||
1191 | print('<tr>' . $this->deletedLine($line) . $this->emptyLine() . "</tr>\n"); |
||
1192 | } |
||
1193 | } |
||
1194 | |||
1195 | function _context($lines) { |
||
1196 | foreach ($lines as $line) { |
||
1197 | print('<tr>' . $this->contextLine($line) . $this->contextLine($line) . "</tr>\n"); |
||
1198 | } |
||
1199 | } |
||
1200 | |||
1201 | function _changed($orig, $closing) { |
||
1202 | $diff = new WordLevelDiff($orig, $closing); // this escapes the diff data |
||
1203 | $del = $diff->orig(); |
||
1204 | $add = $diff->closing(); |
||
1205 | |||
1206 | while ($line = array_shift($del)) { |
||
1207 | $aline = array_shift($add); |
||
1208 | print('<tr>' . $this->deletedLine($line,true) . $this->addedLine($aline,true) . "</tr>\n"); |
||
1209 | } |
||
1210 | $this->_addedLines($add,true); # If any leftovers |
||
1211 | } |
||
1212 | |||
1213 | function _escape($str) { |
||
1214 | return hsc($str); |
||
1215 | } |
||
1216 | } |
||
1217 | |||
1218 | /** |
||
1219 | * Inline style diff formatter. |
||
1220 | * |
||
1221 | */ |
||
1222 | class InlineDiffFormatter extends DiffFormatter { |
||
1223 | var $colspan = 2; |
||
1224 | |||
1225 | function __construct() { |
||
1226 | $this->leading_context_lines = 2; |
||
1227 | $this->trailing_context_lines = 2; |
||
1228 | } |
||
1229 | |||
1230 | /** |
||
1231 | * @param Diff $diff |
||
1232 | * @return string |
||
1233 | */ |
||
1234 | function format($diff) { |
||
0 ignored issues
–
show
|
|||
1235 | // Preserve whitespaces by converting some to non-breaking spaces. |
||
1236 | // Do not convert all of them to allow word-wrap. |
||
1237 | $val = parent::format($diff); |
||
1238 | $val = str_replace(' ','  ', $val); |
||
1239 | $val = preg_replace('/ (?=<)|(?<=[ >]) /', ' ', $val); |
||
1240 | return $val; |
||
1241 | } |
||
1242 | |||
1243 | function _pre($text){ |
||
0 ignored issues
–
show
|
|||
1244 | $text = htmlspecialchars($text); |
||
1245 | return $text; |
||
1246 | } |
||
1247 | |||
1248 | function _block_header($xbeg, $xlen, $ybeg, $ylen) { |
||
0 ignored issues
–
show
|
|||
1249 | global $lang; |
||
1250 | if ($xlen != 1) |
||
1251 | $xbeg .= "," . $xlen; |
||
1252 | if ($ylen != 1) |
||
1253 | $ybeg .= "," . $ylen; |
||
1254 | $r = '<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-blockheader').'>@@ '.$lang['line']." -$xbeg +$ybeg @@"; |
||
1255 | $r .= ' <span '.HTMLDiff::css('diff-deletedline').'><del>'.$lang['deleted'].'</del></span>'; |
||
1256 | $r .= ' <span '.HTMLDiff::css('diff-addedline').'>'.$lang['created'].'</span>'; |
||
1257 | $r .= "</td></tr>\n"; |
||
1258 | return $r; |
||
1259 | } |
||
1260 | |||
1261 | function _start_block($header) { |
||
0 ignored issues
–
show
|
|||
1262 | print($header."\n"); |
||
1263 | } |
||
1264 | |||
1265 | function _end_block() { |
||
0 ignored issues
–
show
|
|||
1266 | } |
||
1267 | |||
1268 | function _lines($lines, $prefix=' ', $color="white") { |
||
0 ignored issues
–
show
|
|||
1269 | } |
||
1270 | |||
1271 | function _added($lines) { |
||
0 ignored issues
–
show
|
|||
1272 | foreach ($lines as $line) { |
||
1273 | print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td '.HTMLDiff::css('diff-addedline').'>'. $this->_escape($line) . "</td></tr>\n"); |
||
1274 | } |
||
1275 | } |
||
1276 | |||
1277 | function _deleted($lines) { |
||
0 ignored issues
–
show
|
|||
1278 | foreach ($lines as $line) { |
||
1279 | print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td '.HTMLDiff::css('diff-deletedline').'><del>' . $this->_escape($line) . "</del></td></tr>\n"); |
||
1280 | } |
||
1281 | } |
||
1282 | |||
1283 | function _context($lines) { |
||
0 ignored issues
–
show
|
|||
1284 | foreach ($lines as $line) { |
||
1285 | print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td '.HTMLDiff::css('diff-context').'>'. $this->_escape($line) ."</td></tr>\n"); |
||
1286 | } |
||
1287 | } |
||
1288 | |||
1289 | function _changed($orig, $closing) { |
||
0 ignored issues
–
show
|
|||
1290 | $diff = new InlineWordLevelDiff($orig, $closing); // this escapes the diff data |
||
1291 | $add = $diff->inline(); |
||
1292 | |||
1293 | foreach ($add as $line) |
||
1294 | print('<tr><td '.HTMLDiff::css('diff-lineheader').'> </td><td>'.$line."</td></tr>\n"); |
||
1295 | } |
||
1296 | |||
1297 | function _escape($str) { |
||
1298 | return hsc($str); |
||
1299 | } |
||
1300 | } |
||
1301 | |||
1302 | /** |
||
1303 | * A class for computing three way diffs. |
||
1304 | * |
||
1305 | * @author Geoffrey T. Dairiki <[email protected]> |
||
1306 | */ |
||
1307 | class Diff3 extends Diff { |
||
1308 | |||
1309 | /** |
||
1310 | * Conflict counter. |
||
1311 | * |
||
1312 | * @var integer |
||
1313 | */ |
||
1314 | var $_conflictingBlocks = 0; |
||
1315 | |||
1316 | /** |
||
1317 | * Computes diff between 3 sequences of strings. |
||
1318 | * |
||
1319 | * @param array $orig The original lines to use. |
||
1320 | * @param array $final1 The first version to compare to. |
||
1321 | * @param array $final2 The second version to compare to. |
||
1322 | */ |
||
1323 | function __construct($orig, $final1, $final2) { |
||
1324 | $engine = new _DiffEngine(); |
||
1325 | |||
1326 | $this->_edits = $this->_diff3($engine->diff($orig, $final1), |
||
1327 | $engine->diff($orig, $final2)); |
||
1328 | } |
||
1329 | |||
1330 | /** |
||
1331 | * Returns the merged lines |
||
1332 | * |
||
1333 | * @param string $label1 label for first version |
||
1334 | * @param string $label2 label for second version |
||
1335 | * @param string $label3 separator between versions |
||
1336 | * @return array lines of the merged text |
||
1337 | */ |
||
1338 | function mergedOutput($label1='<<<<<<<',$label2='>>>>>>>',$label3='=======') { |
||
1339 | $lines = array(); |
||
1340 | foreach ($this->_edits as $edit) { |
||
1341 | if ($edit->isConflict()) { |
||
1342 | /* FIXME: this should probably be moved somewhere else. */ |
||
1343 | $lines = array_merge($lines, |
||
1344 | array($label1), |
||
1345 | $edit->final1, |
||
1346 | array($label3), |
||
1347 | $edit->final2, |
||
1348 | array($label2)); |
||
1349 | $this->_conflictingBlocks++; |
||
1350 | } else { |
||
1351 | $lines = array_merge($lines, $edit->merged()); |
||
1352 | } |
||
1353 | } |
||
1354 | |||
1355 | return $lines; |
||
1356 | } |
||
1357 | |||
1358 | /** |
||
1359 | * @access private |
||
1360 | * |
||
1361 | * @param array $edits1 |
||
1362 | * @param array $edits2 |
||
1363 | * |
||
1364 | * @return array |
||
1365 | */ |
||
1366 | function _diff3($edits1, $edits2) { |
||
1367 | $edits = array(); |
||
1368 | $bb = new _Diff3_BlockBuilder(); |
||
1369 | |||
1370 | $e1 = current($edits1); |
||
1371 | $e2 = current($edits2); |
||
1372 | while ($e1 || $e2) { |
||
1373 | if ($e1 && $e2 && is_a($e1, '_DiffOp_copy') && is_a($e2, '_DiffOp_copy')) { |
||
1374 | /* We have copy blocks from both diffs. This is the (only) |
||
1375 | * time we want to emit a diff3 copy block. Flush current |
||
1376 | * diff3 diff block, if any. */ |
||
1377 | if ($edit = $bb->finish()) { |
||
1378 | $edits[] = $edit; |
||
1379 | } |
||
1380 | |||
1381 | $ncopy = min($e1->norig(), $e2->norig()); |
||
1382 | assert($ncopy > 0); |
||
1383 | $edits[] = new _Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy)); |
||
1384 | |||
1385 | if ($e1->norig() > $ncopy) { |
||
1386 | array_splice($e1->orig, 0, $ncopy); |
||
1387 | array_splice($e1->closing, 0, $ncopy); |
||
1388 | } else { |
||
1389 | $e1 = next($edits1); |
||
1390 | } |
||
1391 | |||
1392 | if ($e2->norig() > $ncopy) { |
||
1393 | array_splice($e2->orig, 0, $ncopy); |
||
1394 | array_splice($e2->closing, 0, $ncopy); |
||
1395 | } else { |
||
1396 | $e2 = next($edits2); |
||
1397 | } |
||
1398 | } else { |
||
1399 | if ($e1 && $e2) { |
||
1400 | if ($e1->orig && $e2->orig) { |
||
1401 | $norig = min($e1->norig(), $e2->norig()); |
||
1402 | $orig = array_splice($e1->orig, 0, $norig); |
||
1403 | array_splice($e2->orig, 0, $norig); |
||
1404 | $bb->input($orig); |
||
1405 | } |
||
1406 | |||
1407 | if (is_a($e1, '_DiffOp_copy')) { |
||
1408 | $bb->out1(array_splice($e1->closing, 0, $norig)); |
||
1409 | } |
||
1410 | |||
1411 | if (is_a($e2, '_DiffOp_copy')) { |
||
1412 | $bb->out2(array_splice($e2->closing, 0, $norig)); |
||
1413 | } |
||
1414 | } |
||
1415 | |||
1416 | if ($e1 && ! $e1->orig) { |
||
1417 | $bb->out1($e1->closing); |
||
1418 | $e1 = next($edits1); |
||
1419 | } |
||
1420 | if ($e2 && ! $e2->orig) { |
||
1421 | $bb->out2($e2->closing); |
||
1422 | $e2 = next($edits2); |
||
1423 | } |
||
1424 | } |
||
1425 | } |
||
1426 | |||
1427 | if ($edit = $bb->finish()) { |
||
1428 | $edits[] = $edit; |
||
1429 | } |
||
1430 | |||
1431 | return $edits; |
||
1432 | } |
||
1433 | } |
||
1434 | |||
1435 | /** |
||
1436 | * @author Geoffrey T. Dairiki <[email protected]> |
||
1437 | * |
||
1438 | * @access private |
||
1439 | */ |
||
1440 | class _Diff3_Op { |
||
1441 | |||
1442 | function __construct($orig = false, $final1 = false, $final2 = false) { |
||
1443 | $this->orig = $orig ? $orig : array(); |
||
1444 | $this->final1 = $final1 ? $final1 : array(); |
||
1445 | $this->final2 = $final2 ? $final2 : array(); |
||
1446 | } |
||
1447 | |||
1448 | function merged() { |
||
1449 | if (!isset($this->_merged)) { |
||
1450 | if ($this->final1 === $this->final2) { |
||
1451 | $this->_merged = &$this->final1; |
||
1452 | } elseif ($this->final1 === $this->orig) { |
||
1453 | $this->_merged = &$this->final2; |
||
1454 | } elseif ($this->final2 === $this->orig) { |
||
1455 | $this->_merged = &$this->final1; |
||
1456 | } else { |
||
1457 | $this->_merged = false; |
||
1458 | } |
||
1459 | } |
||
1460 | |||
1461 | return $this->_merged; |
||
1462 | } |
||
1463 | |||
1464 | function isConflict() { |
||
1465 | return $this->merged() === false; |
||
1466 | } |
||
1467 | |||
1468 | } |
||
1469 | |||
1470 | /** |
||
1471 | * @author Geoffrey T. Dairiki <[email protected]> |
||
1472 | * |
||
1473 | * @access private |
||
1474 | */ |
||
1475 | class _Diff3_Op_copy extends _Diff3_Op { |
||
1476 | |||
1477 | function __construct($lines = false) { |
||
1478 | $this->orig = $lines ? $lines : array(); |
||
1479 | $this->final1 = &$this->orig; |
||
1480 | $this->final2 = &$this->orig; |
||
1481 | } |
||
1482 | |||
1483 | function merged() { |
||
1484 | return $this->orig; |
||
1485 | } |
||
1486 | |||
1487 | function isConflict() { |
||
1488 | return false; |
||
1489 | } |
||
1490 | } |
||
1491 | |||
1492 | /** |
||
1493 | * @author Geoffrey T. Dairiki <[email protected]> |
||
1494 | * |
||
1495 | * @access private |
||
1496 | */ |
||
1497 | class _Diff3_BlockBuilder { |
||
1498 | |||
1499 | function __construct() { |
||
1500 | $this->_init(); |
||
1501 | } |
||
1502 | |||
1503 | function input($lines) { |
||
1504 | if ($lines) { |
||
1505 | $this->_append($this->orig, $lines); |
||
1506 | } |
||
1507 | } |
||
1508 | |||
1509 | function out1($lines) { |
||
1510 | if ($lines) { |
||
1511 | $this->_append($this->final1, $lines); |
||
1512 | } |
||
1513 | } |
||
1514 | |||
1515 | function out2($lines) { |
||
1516 | if ($lines) { |
||
1517 | $this->_append($this->final2, $lines); |
||
1518 | } |
||
1519 | } |
||
1520 | |||
1521 | function isEmpty() { |
||
1522 | return !$this->orig && !$this->final1 && !$this->final2; |
||
1523 | } |
||
1524 | |||
1525 | function finish() { |
||
1526 | if ($this->isEmpty()) { |
||
1527 | return false; |
||
1528 | } else { |
||
1529 | $edit = new _Diff3_Op($this->orig, $this->final1, $this->final2); |
||
1530 | $this->_init(); |
||
1531 | return $edit; |
||
1532 | } |
||
1533 | } |
||
1534 | |||
1535 | function _init() { |
||
1536 | $this->orig = $this->final1 = $this->final2 = array(); |
||
1537 | } |
||
1538 | |||
1539 | function _append(&$array, $lines) { |
||
1540 | array_splice($array, sizeof($array), 0, $lines); |
||
1541 | } |
||
1542 | } |
||
1543 | |||
1544 | //Setup VIM: ex: et ts=4 : |
||
1545 |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.