xref: /dokuwiki/inc/DifferenceEngine.php (revision 0f7af8fc15eb9fba16bfb0870949756495d96a36)
1f3f0262cSandi<?php
215fae107Sandi/**
315fae107Sandi * A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
415fae107Sandi *
515fae107Sandi * Additions by Axel Boldt for MediaWiki
615fae107Sandi *
715fae107Sandi * @copyright (C) 2000, 2001 Geoffrey T. Dairiki <[email protected]>
815fae107Sandi * @license  You may copy this code freely under the conditions of the GPL.
915fae107Sandi */
10f3f0262cSandidefine('USE_ASSERTS', function_exists('assert'));
11f3f0262cSandi
12f3f0262cSandiclass _DiffOp {
13f3f0262cSandi    var $type;
14f3f0262cSandi    var $orig;
15f3f0262cSandi    var $closing;
16f3f0262cSandi
1742ea7f44SGerrit Uitslag    /**
1842ea7f44SGerrit Uitslag     * @return _DiffOp
1942ea7f44SGerrit Uitslag     */
20f3f0262cSandi    function reverse() {
21*0f7af8fcSAndreas Gohr        throw new \RuntimeException("pure virtual");
22f3f0262cSandi    }
23f3f0262cSandi
24f3f0262cSandi    function norig() {
25f5b78785SAndreas Gohr        return $this->orig ? count($this->orig) : 0;
26f3f0262cSandi    }
27f3f0262cSandi
28f3f0262cSandi    function nclosing() {
29f5b78785SAndreas Gohr        return $this->closing ? count($this->closing) : 0;
30f3f0262cSandi    }
31f3f0262cSandi}
32f3f0262cSandi
33f3f0262cSandiclass _DiffOp_Copy extends _DiffOp {
34f3f0262cSandi    var $type = 'copy';
35988c1340SPiyush Mishra
36988c1340SPiyush Mishra    function __construct($orig, $closing = false) {
37f3f0262cSandi        if (!is_array($closing))
38f3f0262cSandi            $closing = $orig;
39f3f0262cSandi        $this->orig = $orig;
40f3f0262cSandi        $this->closing = $closing;
41f3f0262cSandi    }
42f3f0262cSandi
43f3f0262cSandi    function reverse() {
44f3f0262cSandi        return new _DiffOp_Copy($this->closing, $this->orig);
45f3f0262cSandi    }
46f3f0262cSandi}
47f3f0262cSandi
48f3f0262cSandiclass _DiffOp_Delete extends _DiffOp {
49f3f0262cSandi    var $type = 'delete';
50988c1340SPiyush Mishra
51988c1340SPiyush Mishra    function __construct($lines) {
52f3f0262cSandi        $this->orig = $lines;
53f3f0262cSandi        $this->closing = false;
54f3f0262cSandi    }
55f3f0262cSandi
56f3f0262cSandi    function reverse() {
57f3f0262cSandi        return new _DiffOp_Add($this->orig);
58f3f0262cSandi    }
59f3f0262cSandi}
60f3f0262cSandi
61f3f0262cSandiclass _DiffOp_Add extends _DiffOp {
62f3f0262cSandi    var $type = 'add';
63988c1340SPiyush Mishra
64988c1340SPiyush Mishra    function __construct($lines) {
65f3f0262cSandi        $this->closing = $lines;
66f3f0262cSandi        $this->orig = false;
67f3f0262cSandi    }
68f3f0262cSandi
69f3f0262cSandi    function reverse() {
70f3f0262cSandi        return new _DiffOp_Delete($this->closing);
71f3f0262cSandi    }
72f3f0262cSandi}
73f3f0262cSandi
74f3f0262cSandiclass _DiffOp_Change extends _DiffOp {
75f3f0262cSandi    var $type = 'change';
76988c1340SPiyush Mishra
77988c1340SPiyush Mishra    function __construct($orig, $closing) {
78f3f0262cSandi        $this->orig = $orig;
79f3f0262cSandi        $this->closing = $closing;
80f3f0262cSandi    }
81f3f0262cSandi
82f3f0262cSandi    function reverse() {
83f3f0262cSandi        return new _DiffOp_Change($this->closing, $this->orig);
84f3f0262cSandi    }
85f3f0262cSandi}
86f3f0262cSandi
87f3f0262cSandi
88f3f0262cSandi/**
89f3f0262cSandi * Class used internally by Diff to actually compute the diffs.
90f3f0262cSandi *
91f3f0262cSandi * The algorithm used here is mostly lifted from the perl module
92f3f0262cSandi * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
93f3f0262cSandi *   http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
94f3f0262cSandi *
95f3f0262cSandi * More ideas are taken from:
96f3f0262cSandi *   http://www.ics.uci.edu/~eppstein/161/960229.html
97f3f0262cSandi *
98f3f0262cSandi * Some ideas are (and a bit of code) are from from analyze.c, from GNU
99f3f0262cSandi * diffutils-2.7, which can be found at:
100f3f0262cSandi *   ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
101f3f0262cSandi *
102f3f0262cSandi * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
103f3f0262cSandi * are my own.
104f3f0262cSandi *
105f3f0262cSandi * @author Geoffrey T. Dairiki
106f3f0262cSandi * @access private
107f3f0262cSandi */
108f5b78785SAndreas Gohrclass _DiffEngine {
109f5b78785SAndreas Gohr
11042ea7f44SGerrit Uitslag    var $xchanged = array();
11142ea7f44SGerrit Uitslag    var $ychanged = array();
11242ea7f44SGerrit Uitslag    var $xv = array();
11342ea7f44SGerrit Uitslag    var $yv = array();
11442ea7f44SGerrit Uitslag    var $xind = array();
11542ea7f44SGerrit Uitslag    var $yind = array();
11642ea7f44SGerrit Uitslag    var $seq;
11742ea7f44SGerrit Uitslag    var $in_seq;
11842ea7f44SGerrit Uitslag    var $lcs;
11942ea7f44SGerrit Uitslag
12042ea7f44SGerrit Uitslag    /**
12142ea7f44SGerrit Uitslag     * @param array $from_lines
12242ea7f44SGerrit Uitslag     * @param array $to_lines
12342ea7f44SGerrit Uitslag     * @return _DiffOp[]
12442ea7f44SGerrit Uitslag     */
125f3f0262cSandi    function diff($from_lines, $to_lines) {
126f5b78785SAndreas Gohr        $n_from = count($from_lines);
127f5b78785SAndreas Gohr        $n_to = count($to_lines);
128f3f0262cSandi
129f3f0262cSandi        $this->xchanged = $this->ychanged = array();
130f3f0262cSandi        $this->xv = $this->yv = array();
131f3f0262cSandi        $this->xind = $this->yind = array();
132f3f0262cSandi        unset($this->seq);
133f3f0262cSandi        unset($this->in_seq);
134f3f0262cSandi        unset($this->lcs);
135f3f0262cSandi
136f3f0262cSandi        // Skip leading common lines.
137f3f0262cSandi        for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
138f3f0262cSandi            if ($from_lines[$skip] != $to_lines[$skip])
139f3f0262cSandi                break;
140f3f0262cSandi            $this->xchanged[$skip] = $this->ychanged[$skip] = false;
141f3f0262cSandi        }
142f3f0262cSandi        // Skip trailing common lines.
143f5b78785SAndreas Gohr        $xi = $n_from;
144f5b78785SAndreas Gohr        $yi = $n_to;
145f3f0262cSandi        for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
146f3f0262cSandi            if ($from_lines[$xi] != $to_lines[$yi])
147f3f0262cSandi                break;
148f3f0262cSandi            $this->xchanged[$xi] = $this->ychanged[$yi] = false;
149f3f0262cSandi        }
150f3f0262cSandi
151f3f0262cSandi        // Ignore lines which do not exist in both files.
152f3f0262cSandi        for ($xi = $skip; $xi < $n_from - $endskip; $xi++)
153f3f0262cSandi            $xhash[$from_lines[$xi]] = 1;
154f3f0262cSandi        for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
155f3f0262cSandi            $line = $to_lines[$yi];
156f3f0262cSandi            if (($this->ychanged[$yi] = empty($xhash[$line])))
157f3f0262cSandi                continue;
158f3f0262cSandi            $yhash[$line] = 1;
159f3f0262cSandi            $this->yv[] = $line;
160f3f0262cSandi            $this->yind[] = $yi;
161f3f0262cSandi        }
162f3f0262cSandi        for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
163f3f0262cSandi            $line = $from_lines[$xi];
164f3f0262cSandi            if (($this->xchanged[$xi] = empty($yhash[$line])))
165f3f0262cSandi                continue;
166f3f0262cSandi            $this->xv[] = $line;
167f3f0262cSandi            $this->xind[] = $xi;
168f3f0262cSandi        }
169f3f0262cSandi
170f3f0262cSandi        // Find the LCS.
171f5b78785SAndreas Gohr        $this->_compareseq(0, count($this->xv), 0, count($this->yv));
172f3f0262cSandi
173f3f0262cSandi        // Merge edits when possible
174f3f0262cSandi        $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
175f3f0262cSandi        $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
176f3f0262cSandi
177f3f0262cSandi        // Compute the edit operations.
178f3f0262cSandi        $edits = array();
179f3f0262cSandi        $xi = $yi = 0;
180f3f0262cSandi        while ($xi < $n_from || $yi < $n_to) {
181f3f0262cSandi            USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
182f3f0262cSandi            USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
183f3f0262cSandi
184f3f0262cSandi            // Skip matching "snake".
185f3f0262cSandi            $copy = array();
1867deca91bSRobin Getz            while ($xi < $n_from && $yi < $n_to && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
187f3f0262cSandi                $copy[] = $from_lines[$xi++];
188f3f0262cSandi                ++$yi;
189f3f0262cSandi            }
190f3f0262cSandi            if ($copy)
191f3f0262cSandi                $edits[] = new _DiffOp_Copy($copy);
192f3f0262cSandi
193f3f0262cSandi            // Find deletes & adds.
194f3f0262cSandi            $delete = array();
195f3f0262cSandi            while ($xi < $n_from && $this->xchanged[$xi])
196f3f0262cSandi                $delete[] = $from_lines[$xi++];
197f3f0262cSandi
198f3f0262cSandi            $add = array();
199f3f0262cSandi            while ($yi < $n_to && $this->ychanged[$yi])
200f3f0262cSandi                $add[] = $to_lines[$yi++];
201f3f0262cSandi
202f3f0262cSandi            if ($delete && $add)
203f3f0262cSandi                $edits[] = new _DiffOp_Change($delete, $add);
204f3f0262cSandi            elseif ($delete)
205f3f0262cSandi                $edits[] = new _DiffOp_Delete($delete);
206f3f0262cSandi            elseif ($add)
207f3f0262cSandi                $edits[] = new _DiffOp_Add($add);
208f3f0262cSandi        }
209f3f0262cSandi        return $edits;
210f3f0262cSandi    }
211f3f0262cSandi
212f3f0262cSandi
21315fae107Sandi    /**
21415fae107Sandi     * Divide the Largest Common Subsequence (LCS) of the sequences
215f3f0262cSandi     * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
216f3f0262cSandi     * sized segments.
217f3f0262cSandi     *
218f3f0262cSandi     * Returns (LCS, PTS).  LCS is the length of the LCS. PTS is an
219f3f0262cSandi     * array of NCHUNKS+1 (X, Y) indexes giving the diving points between
220f3f0262cSandi     * sub sequences.  The first sub-sequence is contained in [X0, X1),
221f3f0262cSandi     * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on.  Note
222f3f0262cSandi     * that (X0, Y0) == (XOFF, YOFF) and
223f3f0262cSandi     * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
224f3f0262cSandi     *
225f3f0262cSandi     * This function assumes that the first lines of the specified portions
226f3f0262cSandi     * of the two files do not match, and likewise that the last lines do not
227f3f0262cSandi     * match.  The caller must trim matching lines from the beginning and end
228f3f0262cSandi     * of the portions it is going to specify.
229f50a239bSTakamura     *
230f50a239bSTakamura     * @param integer $xoff
231f50a239bSTakamura     * @param integer $xlim
232f50a239bSTakamura     * @param integer $yoff
233f50a239bSTakamura     * @param integer $ylim
234f50a239bSTakamura     * @param integer $nchunks
235f50a239bSTakamura     *
236f50a239bSTakamura     * @return array
237f3f0262cSandi     */
238f3f0262cSandi    function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) {
239f3f0262cSandi        $flip = false;
240f3f0262cSandi
241f3f0262cSandi        if ($xlim - $xoff > $ylim - $yoff) {
242f3f0262cSandi            // Things seems faster (I'm not sure I understand why)
243f3f0262cSandi            // when the shortest sequence in X.
244f3f0262cSandi            $flip = true;
2457deca91bSRobin Getz            list ($xoff, $xlim, $yoff, $ylim) = array($yoff, $ylim, $xoff, $xlim);
246f3f0262cSandi        }
247f3f0262cSandi
248f3f0262cSandi        if ($flip)
249f3f0262cSandi            for ($i = $ylim - 1; $i >= $yoff; $i--)
250f3f0262cSandi                $ymatches[$this->xv[$i]][] = $i;
251f3f0262cSandi        else
252f3f0262cSandi            for ($i = $ylim - 1; $i >= $yoff; $i--)
253f3f0262cSandi                $ymatches[$this->yv[$i]][] = $i;
254f3f0262cSandi
255f3f0262cSandi        $this->lcs = 0;
256f3f0262cSandi        $this->seq[0]= $yoff - 1;
257f3f0262cSandi        $this->in_seq = array();
258f3f0262cSandi        $ymids[0] = array();
259f3f0262cSandi
260f3f0262cSandi        $numer = $xlim - $xoff + $nchunks - 1;
261f3f0262cSandi        $x = $xoff;
262f3f0262cSandi        for ($chunk = 0; $chunk < $nchunks; $chunk++) {
263f3f0262cSandi            if ($chunk > 0)
264f3f0262cSandi                for ($i = 0; $i <= $this->lcs; $i++)
265f3f0262cSandi                    $ymids[$i][$chunk-1] = $this->seq[$i];
266f3f0262cSandi
267f3f0262cSandi            $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
268f3f0262cSandi            for ( ; $x < $x1; $x++) {
269f3f0262cSandi                $line = $flip ? $this->yv[$x] : $this->xv[$x];
270f3f0262cSandi                if (empty($ymatches[$line]))
271f3f0262cSandi                    continue;
272f3f0262cSandi                $matches = $ymatches[$line];
273dd5c3e2bSPhy                $switch = false;
274dd5c3e2bSPhy                foreach ($matches as $y) {
275d3b71b9dSPhy                    if ($switch && $y > $this->seq[$k-1]) {
276f3f0262cSandi                        USE_ASSERTS && assert($y < $this->seq[$k]);
277f3f0262cSandi                        // Optimization: this is a common case:
278f3f0262cSandi                        //  next match is just replacing previous match.
279f3f0262cSandi                        $this->in_seq[$this->seq[$k]] = false;
280f3f0262cSandi                        $this->seq[$k] = $y;
281f3f0262cSandi                        $this->in_seq[$y] = 1;
282f3f0262cSandi                    }
283f3f0262cSandi                    else if (empty($this->in_seq[$y])) {
284f3f0262cSandi                        $k = $this->_lcs_pos($y);
285f3f0262cSandi                        USE_ASSERTS && assert($k > 0);
286f3f0262cSandi                        $ymids[$k] = $ymids[$k-1];
287d3b71b9dSPhy                        $switch = true;
288f3f0262cSandi                    }
289f3f0262cSandi                }
290f3f0262cSandi            }
291dd5c3e2bSPhy        }
292f3f0262cSandi
293f3f0262cSandi        $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
294f3f0262cSandi        $ymid = $ymids[$this->lcs];
295f3f0262cSandi        for ($n = 0; $n < $nchunks - 1; $n++) {
296f3f0262cSandi            $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
297f3f0262cSandi            $y1 = $ymid[$n] + 1;
298f3f0262cSandi            $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
299f3f0262cSandi        }
300f3f0262cSandi        $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
301f3f0262cSandi
302f3f0262cSandi        return array($this->lcs, $seps);
303f3f0262cSandi    }
304f3f0262cSandi
305f3f0262cSandi    function _lcs_pos($ypos) {
306f3f0262cSandi        $end = $this->lcs;
307f3f0262cSandi        if ($end == 0 || $ypos > $this->seq[$end]) {
308f3f0262cSandi            $this->seq[++$this->lcs] = $ypos;
309f3f0262cSandi            $this->in_seq[$ypos] = 1;
310f3f0262cSandi            return $this->lcs;
311f3f0262cSandi        }
312f3f0262cSandi
313f3f0262cSandi        $beg = 1;
314f3f0262cSandi        while ($beg < $end) {
315f3f0262cSandi            $mid = (int)(($beg + $end) / 2);
316f3f0262cSandi            if ($ypos > $this->seq[$mid])
317f3f0262cSandi                $beg = $mid + 1;
318f3f0262cSandi            else
319f3f0262cSandi                $end = $mid;
320f3f0262cSandi        }
321f3f0262cSandi
322f3f0262cSandi        USE_ASSERTS && assert($ypos != $this->seq[$end]);
323f3f0262cSandi
324f3f0262cSandi        $this->in_seq[$this->seq[$end]] = false;
325f3f0262cSandi        $this->seq[$end] = $ypos;
326f3f0262cSandi        $this->in_seq[$ypos] = 1;
327f3f0262cSandi        return $end;
328f3f0262cSandi    }
329f3f0262cSandi
33015fae107Sandi    /**
33115fae107Sandi     * Find LCS of two sequences.
332f3f0262cSandi     *
333f3f0262cSandi     * The results are recorded in the vectors $this->{x,y}changed[], by
334f3f0262cSandi     * storing a 1 in the element for each line that is an insertion
335f3f0262cSandi     * or deletion (ie. is not in the LCS).
336f3f0262cSandi     *
337f3f0262cSandi     * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
338f3f0262cSandi     *
339f3f0262cSandi     * Note that XLIM, YLIM are exclusive bounds.
340f3f0262cSandi     * All line numbers are origin-0 and discarded lines are not counted.
341f50a239bSTakamura     *
342f50a239bSTakamura     * @param integer $xoff
343f50a239bSTakamura     * @param integer $xlim
344f50a239bSTakamura     * @param integer $yoff
345f50a239bSTakamura     * @param integer $ylim
346f3f0262cSandi     */
347f3f0262cSandi    function _compareseq($xoff, $xlim, $yoff, $ylim) {
348f3f0262cSandi        // Slide down the bottom initial diagonal.
3497deca91bSRobin Getz        while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) {
350f3f0262cSandi            ++$xoff;
351f3f0262cSandi            ++$yoff;
352f3f0262cSandi        }
353f3f0262cSandi
354f3f0262cSandi        // Slide up the top initial diagonal.
3557deca91bSRobin Getz        while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
356f3f0262cSandi            --$xlim;
357f3f0262cSandi            --$ylim;
358f3f0262cSandi        }
359f3f0262cSandi
360f3f0262cSandi        if ($xoff == $xlim || $yoff == $ylim)
361f3f0262cSandi            $lcs = 0;
362f3f0262cSandi        else {
363f3f0262cSandi            // This is ad hoc but seems to work well.
364f3f0262cSandi            //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
365f3f0262cSandi            //$nchunks = max(2,min(8,(int)$nchunks));
366f3f0262cSandi            $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
367f3f0262cSandi            list ($lcs, $seps)
368f3f0262cSandi                = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
369f3f0262cSandi        }
370f3f0262cSandi
371f3f0262cSandi        if ($lcs == 0) {
372f3f0262cSandi            // X and Y sequences have no common subsequence:
373f3f0262cSandi            // mark all changed.
374f3f0262cSandi            while ($yoff < $ylim)
375f3f0262cSandi                $this->ychanged[$this->yind[$yoff++]] = 1;
376f3f0262cSandi            while ($xoff < $xlim)
377f3f0262cSandi                $this->xchanged[$this->xind[$xoff++]] = 1;
378f3f0262cSandi        }
379f3f0262cSandi        else {
380f3f0262cSandi            // Use the partitions to split this problem into subproblems.
381f3f0262cSandi            reset($seps);
382f3f0262cSandi            $pt1 = $seps[0];
383f3f0262cSandi            while ($pt2 = next($seps)) {
384f3f0262cSandi                $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
385f3f0262cSandi                $pt1 = $pt2;
386f3f0262cSandi            }
387f3f0262cSandi        }
388f3f0262cSandi    }
389f3f0262cSandi
39015fae107Sandi    /**
39115fae107Sandi     * Adjust inserts/deletes of identical lines to join changes
392f3f0262cSandi     * as much as possible.
393f3f0262cSandi     *
394f3f0262cSandi     * We do something when a run of changed lines include a
395f3f0262cSandi     * line at one end and has an excluded, identical line at the other.
396f3f0262cSandi     * We are free to choose which identical line is included.
397f3f0262cSandi     * `compareseq' usually chooses the one at the beginning,
398f3f0262cSandi     * but usually it is cleaner to consider the following identical line
399f3f0262cSandi     * to be the "change".
400f3f0262cSandi     *
401f3f0262cSandi     * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
402f50a239bSTakamura     *
403f50a239bSTakamura     * @param array $lines
404f50a239bSTakamura     * @param array $changed
405f50a239bSTakamura     * @param array $other_changed
406f3f0262cSandi     */
407f3f0262cSandi    function _shift_boundaries($lines, &$changed, $other_changed) {
408f3f0262cSandi        $i = 0;
409f3f0262cSandi        $j = 0;
410f3f0262cSandi
411fc55d0b2SPhy        USE_ASSERTS && assert(count($lines) == count($changed));
412f5b78785SAndreas Gohr        $len = count($lines);
413f5b78785SAndreas Gohr        $other_len = count($other_changed);
414f3f0262cSandi
415f3f0262cSandi        while (1) {
416f3f0262cSandi            /*
417f3f0262cSandi             * Scan forwards to find beginning of another run of changes.
418f3f0262cSandi             * Also keep track of the corresponding point in the other file.
419f3f0262cSandi             *
420f3f0262cSandi             * Throughout this code, $i and $j are adjusted together so that
421f3f0262cSandi             * the first $i elements of $changed and the first $j elements
422f3f0262cSandi             * of $other_changed both contain the same number of zeros
423f3f0262cSandi             * (unchanged lines).
424f3f0262cSandi             * Furthermore, $j is always kept so that $j == $other_len or
425f3f0262cSandi             * $other_changed[$j] == false.
426f3f0262cSandi             */
427f3f0262cSandi            while ($j < $other_len && $other_changed[$j])
428f3f0262cSandi                $j++;
429f3f0262cSandi
430f3f0262cSandi            while ($i < $len && ! $changed[$i]) {
431fc55d0b2SPhy                USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]);
432f5b78785SAndreas Gohr                $i++;
433f5b78785SAndreas Gohr                $j++;
434f3f0262cSandi                while ($j < $other_len && $other_changed[$j])
435f3f0262cSandi                    $j++;
436f3f0262cSandi            }
437f3f0262cSandi
438f3f0262cSandi            if ($i == $len)
439f3f0262cSandi                break;
440f3f0262cSandi
441f3f0262cSandi            $start = $i;
442f3f0262cSandi
443f3f0262cSandi            // Find the end of this run of changes.
444f3f0262cSandi            while (++$i < $len && $changed[$i])
445f3f0262cSandi                continue;
446f3f0262cSandi
447f3f0262cSandi            do {
448f3f0262cSandi                /*
449f3f0262cSandi                 * Record the length of this run of changes, so that
450f3f0262cSandi                 * we can later determine whether the run has grown.
451f3f0262cSandi                 */
452f3f0262cSandi                $runlength = $i - $start;
453f3f0262cSandi
454f3f0262cSandi                /*
455f3f0262cSandi                 * Move the changed region back, so long as the
456f3f0262cSandi                 * previous unchanged line matches the last changed one.
457f3f0262cSandi                 * This merges with previous changed regions.
458f3f0262cSandi                 */
459f3f0262cSandi                while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
460f3f0262cSandi                    $changed[--$start] = 1;
461f3f0262cSandi                    $changed[--$i] = false;
462f3f0262cSandi                    while ($start > 0 && $changed[$start - 1])
463f3f0262cSandi                        $start--;
464fc55d0b2SPhy                    USE_ASSERTS && assert($j > 0);
465f3f0262cSandi                    while ($other_changed[--$j])
466f3f0262cSandi                        continue;
467fc55d0b2SPhy                    USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
468f3f0262cSandi                }
469f3f0262cSandi
470f3f0262cSandi                /*
471f3f0262cSandi                 * Set CORRESPONDING to the end of the changed run, at the last
472f3f0262cSandi                 * point where it corresponds to a changed run in the other file.
473f3f0262cSandi                 * CORRESPONDING == LEN means no such point has been found.
474f3f0262cSandi                 */
475f3f0262cSandi                $corresponding = $j < $other_len ? $i : $len;
476f3f0262cSandi
477f3f0262cSandi                /*
478f3f0262cSandi                 * Move the changed region forward, so long as the
479f3f0262cSandi                 * first changed line matches the following unchanged one.
480f3f0262cSandi                 * This merges with following changed regions.
481f3f0262cSandi                 * Do this second, so that if there are no merges,
482f3f0262cSandi                 * the changed region is moved forward as far as possible.
483f3f0262cSandi                 */
484f3f0262cSandi                while ($i < $len && $lines[$start] == $lines[$i]) {
485f3f0262cSandi                    $changed[$start++] = false;
486f3f0262cSandi                    $changed[$i++] = 1;
487f3f0262cSandi                    while ($i < $len && $changed[$i])
488f3f0262cSandi                        $i++;
489f3f0262cSandi
490fc55d0b2SPhy                    USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]);
491f3f0262cSandi                    $j++;
492f3f0262cSandi                    if ($j < $other_len && $other_changed[$j]) {
493f3f0262cSandi                        $corresponding = $i;
494f3f0262cSandi                        while ($j < $other_len && $other_changed[$j])
495f3f0262cSandi                            $j++;
496f3f0262cSandi                    }
497f3f0262cSandi                }
498f3f0262cSandi            } while ($runlength != $i - $start);
499f3f0262cSandi
500f3f0262cSandi            /*
501f3f0262cSandi             * If possible, move the fully-merged run of changes
502f3f0262cSandi             * back to a corresponding run in the other file.
503f3f0262cSandi             */
504f3f0262cSandi            while ($corresponding < $i) {
505f3f0262cSandi                $changed[--$start] = 1;
506f3f0262cSandi                $changed[--$i] = 0;
507fc55d0b2SPhy                USE_ASSERTS && assert($j > 0);
508f3f0262cSandi                while ($other_changed[--$j])
509f3f0262cSandi                    continue;
510fc55d0b2SPhy                USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
511f3f0262cSandi            }
512f3f0262cSandi        }
513f3f0262cSandi    }
514f3f0262cSandi}
515f3f0262cSandi
516f3f0262cSandi/**
517f3f0262cSandi * Class representing a 'diff' between two sequences of strings.
518f3f0262cSandi */
519f5b78785SAndreas Gohrclass Diff {
520f5b78785SAndreas Gohr
521f3f0262cSandi    var $edits;
522f3f0262cSandi
523f3f0262cSandi    /**
524f3f0262cSandi     * Constructor.
525f3f0262cSandi     * Computes diff between sequences of strings.
526f3f0262cSandi     *
52742ea7f44SGerrit Uitslag     * @param array $from_lines An array of strings.
528f3f0262cSandi     *                          (Typically these are lines from a file.)
52942ea7f44SGerrit Uitslag     * @param array $to_lines   An array of strings.
530f3f0262cSandi     */
531988c1340SPiyush Mishra    function __construct($from_lines, $to_lines) {
532f3f0262cSandi        $eng = new _DiffEngine;
533f3f0262cSandi        $this->edits = $eng->diff($from_lines, $to_lines);
534f3f0262cSandi        //$this->_check($from_lines, $to_lines);
535f3f0262cSandi    }
536f3f0262cSandi
537f3f0262cSandi    /**
538f3f0262cSandi     * Compute reversed Diff.
539f3f0262cSandi     *
540f3f0262cSandi     * SYNOPSIS:
541f3f0262cSandi     *
542f3f0262cSandi     *  $diff = new Diff($lines1, $lines2);
543f3f0262cSandi     *  $rev = $diff->reverse();
54442ea7f44SGerrit Uitslag     *
54542ea7f44SGerrit Uitslag     * @return Diff  A Diff object representing the inverse of the
546f3f0262cSandi     *               original diff.
547f3f0262cSandi     */
548f3f0262cSandi    function reverse() {
549f3f0262cSandi        $rev = $this;
550f3f0262cSandi        $rev->edits = array();
551f3f0262cSandi        foreach ($this->edits as $edit) {
552f3f0262cSandi            $rev->edits[] = $edit->reverse();
553f3f0262cSandi        }
554f3f0262cSandi        return $rev;
555f3f0262cSandi    }
556f3f0262cSandi
557f3f0262cSandi    /**
558f3f0262cSandi     * Check for empty diff.
559f3f0262cSandi     *
560f3f0262cSandi     * @return bool True iff two sequences were identical.
561f3f0262cSandi     */
562f3f0262cSandi    function isEmpty() {
563f3f0262cSandi        foreach ($this->edits as $edit) {
564f3f0262cSandi            if ($edit->type != 'copy')
565f3f0262cSandi                return false;
566f3f0262cSandi        }
567f3f0262cSandi        return true;
568f3f0262cSandi    }
569f3f0262cSandi
570f3f0262cSandi    /**
571f3f0262cSandi     * Compute the length of the Longest Common Subsequence (LCS).
572f3f0262cSandi     *
573f3f0262cSandi     * This is mostly for diagnostic purposed.
574f3f0262cSandi     *
575f3f0262cSandi     * @return int The length of the LCS.
576f3f0262cSandi     */
577f3f0262cSandi    function lcs() {
578f3f0262cSandi        $lcs = 0;
579f3f0262cSandi        foreach ($this->edits as $edit) {
580f3f0262cSandi            if ($edit->type == 'copy')
581f5b78785SAndreas Gohr                $lcs += count($edit->orig);
582f3f0262cSandi        }
583f3f0262cSandi        return $lcs;
584f3f0262cSandi    }
585f3f0262cSandi
586f3f0262cSandi    /**
587f3f0262cSandi     * Get the original set of lines.
588f3f0262cSandi     *
589f3f0262cSandi     * This reconstructs the $from_lines parameter passed to the
590f3f0262cSandi     * constructor.
591f3f0262cSandi     *
592f3f0262cSandi     * @return array The original sequence of strings.
593f3f0262cSandi     */
594f3f0262cSandi    function orig() {
595f3f0262cSandi        $lines = array();
596f3f0262cSandi
597f3f0262cSandi        foreach ($this->edits as $edit) {
598f3f0262cSandi            if ($edit->orig)
599f5b78785SAndreas Gohr                array_splice($lines, count($lines), 0, $edit->orig);
600f3f0262cSandi        }
601f3f0262cSandi        return $lines;
602f3f0262cSandi    }
603f3f0262cSandi
604f3f0262cSandi    /**
605f3f0262cSandi     * Get the closing set of lines.
606f3f0262cSandi     *
607f3f0262cSandi     * This reconstructs the $to_lines parameter passed to the
608f3f0262cSandi     * constructor.
609f3f0262cSandi     *
610f3f0262cSandi     * @return array The sequence of strings.
611f3f0262cSandi     */
612f3f0262cSandi    function closing() {
613f3f0262cSandi        $lines = array();
614f3f0262cSandi
615f3f0262cSandi        foreach ($this->edits as $edit) {
616f3f0262cSandi            if ($edit->closing)
617f5b78785SAndreas Gohr                array_splice($lines, count($lines), 0, $edit->closing);
618f3f0262cSandi        }
619f3f0262cSandi        return $lines;
620f3f0262cSandi    }
621f3f0262cSandi
622f3f0262cSandi    /**
623f3f0262cSandi     * Check a Diff for validity.
624f3f0262cSandi     *
625f3f0262cSandi     * This is here only for debugging purposes.
626f50a239bSTakamura     *
627f50a239bSTakamura     * @param mixed $from_lines
628f50a239bSTakamura     * @param mixed $to_lines
629f3f0262cSandi     */
630f3f0262cSandi    function _check($from_lines, $to_lines) {
631f3f0262cSandi        if (serialize($from_lines) != serialize($this->orig()))
632*0f7af8fcSAndreas Gohr            throw new \RuntimeException("Reconstructed original doesn't match");
633f3f0262cSandi        if (serialize($to_lines) != serialize($this->closing()))
634*0f7af8fcSAndreas Gohr            throw new \RuntimeException("Reconstructed closing doesn't match");
635f3f0262cSandi
636f3f0262cSandi        $rev = $this->reverse();
637f3f0262cSandi        if (serialize($to_lines) != serialize($rev->orig()))
638*0f7af8fcSAndreas Gohr            throw new \RuntimeException("Reversed original doesn't match");
639f3f0262cSandi        if (serialize($from_lines) != serialize($rev->closing()))
640*0f7af8fcSAndreas Gohr            throw new \RuntimeException("Reversed closing doesn't match");
641f3f0262cSandi
642f3f0262cSandi        $prevtype = 'none';
643f3f0262cSandi        foreach ($this->edits as $edit) {
644f3f0262cSandi            if ($prevtype == $edit->type)
645*0f7af8fcSAndreas Gohr                throw new \RuntimeException("Edit sequence is non-optimal");
646f3f0262cSandi            $prevtype = $edit->type;
647f3f0262cSandi        }
648f3f0262cSandi
649f3f0262cSandi        $lcs = $this->lcs();
650f3f0262cSandi        trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE);
651f3f0262cSandi    }
652f3f0262cSandi}
653f3f0262cSandi
654f3f0262cSandi/**
655f3f0262cSandi * FIXME: bad name.
656f3f0262cSandi */
657f5b78785SAndreas Gohrclass MappedDiff extends Diff {
658f3f0262cSandi    /**
659f3f0262cSandi     * Constructor.
660f3f0262cSandi     *
661f3f0262cSandi     * Computes diff between sequences of strings.
662f3f0262cSandi     *
663f3f0262cSandi     * This can be used to compute things like
664f3f0262cSandi     * case-insensitve diffs, or diffs which ignore
665f3f0262cSandi     * changes in white-space.
666f3f0262cSandi     *
66742ea7f44SGerrit Uitslag     * @param string[] $from_lines         An array of strings.
668f3f0262cSandi     *                                     (Typically these are lines from a file.)
669f3f0262cSandi     *
67042ea7f44SGerrit Uitslag     * @param string[] $to_lines           An array of strings.
671f3f0262cSandi     *
67242ea7f44SGerrit Uitslag     * @param string[] $mapped_from_lines  This array should
673f3f0262cSandi     *                                     have the same size number of elements as $from_lines.
674f3f0262cSandi     *                                     The elements in $mapped_from_lines and
675f3f0262cSandi     *                                     $mapped_to_lines are what is actually compared
676f3f0262cSandi     *                                     when computing the diff.
677f3f0262cSandi     *
67842ea7f44SGerrit Uitslag     * @param string[] $mapped_to_lines    This array should
679f3f0262cSandi     *                                     have the same number of elements as $to_lines.
680f3f0262cSandi     */
681988c1340SPiyush Mishra    function __construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) {
682f3f0262cSandi
683f5b78785SAndreas Gohr        assert(count($from_lines) == count($mapped_from_lines));
684f5b78785SAndreas Gohr        assert(count($to_lines) == count($mapped_to_lines));
685f3f0262cSandi
686988c1340SPiyush Mishra        parent::__construct($mapped_from_lines, $mapped_to_lines);
687f3f0262cSandi
688f3f0262cSandi        $xi = $yi = 0;
689f5b78785SAndreas Gohr        $ecnt = count($this->edits);
690f5b78785SAndreas Gohr        for ($i = 0; $i < $ecnt; $i++) {
691f3f0262cSandi            $orig = &$this->edits[$i]->orig;
692f3f0262cSandi            if (is_array($orig)) {
693f5b78785SAndreas Gohr                $orig = array_slice($from_lines, $xi, count($orig));
694f5b78785SAndreas Gohr                $xi += count($orig);
695f3f0262cSandi            }
696f3f0262cSandi
697f3f0262cSandi            $closing = &$this->edits[$i]->closing;
698f3f0262cSandi            if (is_array($closing)) {
699f5b78785SAndreas Gohr                $closing = array_slice($to_lines, $yi, count($closing));
700f5b78785SAndreas Gohr                $yi += count($closing);
701f3f0262cSandi            }
702f3f0262cSandi        }
703f3f0262cSandi    }
704f3f0262cSandi}
705f3f0262cSandi
706f3f0262cSandi/**
707f3f0262cSandi * A class to format Diffs
708f3f0262cSandi *
709f3f0262cSandi * This class formats the diff in classic diff format.
710f3f0262cSandi * It is intended that this class be customized via inheritance,
711f3f0262cSandi * to obtain fancier outputs.
712f3f0262cSandi */
713f5b78785SAndreas Gohrclass DiffFormatter {
714f3f0262cSandi    /**
715f3f0262cSandi     * Number of leading context "lines" to preserve.
716f3f0262cSandi     *
717f3f0262cSandi     * This should be left at zero for this class, but subclasses
718f3f0262cSandi     * may want to set this to other values.
719f3f0262cSandi     */
720f3f0262cSandi    var $leading_context_lines = 0;
721f3f0262cSandi
722f3f0262cSandi    /**
723f3f0262cSandi     * Number of trailing context "lines" to preserve.
724f3f0262cSandi     *
725f3f0262cSandi     * This should be left at zero for this class, but subclasses
726f3f0262cSandi     * may want to set this to other values.
727f3f0262cSandi     */
728f3f0262cSandi    var $trailing_context_lines = 0;
729f3f0262cSandi
730f3f0262cSandi    /**
731f3f0262cSandi     * Format a diff.
732f3f0262cSandi     *
73342ea7f44SGerrit Uitslag     * @param Diff $diff A Diff object.
734f3f0262cSandi     * @return string The formatted output.
735f3f0262cSandi     */
736f3f0262cSandi    function format($diff) {
737f3f0262cSandi
738f3f0262cSandi        $xi = $yi = 1;
73942ea7f44SGerrit Uitslag        $x0 = $y0 = 0;
740f3f0262cSandi        $block = false;
741f3f0262cSandi        $context = array();
742f3f0262cSandi
743f3f0262cSandi        $nlead = $this->leading_context_lines;
744f3f0262cSandi        $ntrail = $this->trailing_context_lines;
745f3f0262cSandi
746f3f0262cSandi        $this->_start_diff();
747f3f0262cSandi
748f3f0262cSandi        foreach ($diff->edits as $edit) {
749f3f0262cSandi            if ($edit->type == 'copy') {
750f3f0262cSandi                if (is_array($block)) {
751f5b78785SAndreas Gohr                    if (count($edit->orig) <= $nlead + $ntrail) {
752f3f0262cSandi                        $block[] = $edit;
753f3f0262cSandi                    }
754f3f0262cSandi                    else{
755f3f0262cSandi                        if ($ntrail) {
756f3f0262cSandi                            $context = array_slice($edit->orig, 0, $ntrail);
757f3f0262cSandi                            $block[] = new _DiffOp_Copy($context);
758f3f0262cSandi                        }
7597deca91bSRobin Getz                        $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block);
760f3f0262cSandi                        $block = false;
761f3f0262cSandi                    }
762f3f0262cSandi                }
763f3f0262cSandi                $context = $edit->orig;
764f3f0262cSandi            }
765f3f0262cSandi            else {
766f3f0262cSandi                if (! is_array($block)) {
767f5b78785SAndreas Gohr                    $context = array_slice($context, count($context) - $nlead);
768f5b78785SAndreas Gohr                    $x0 = $xi - count($context);
769f5b78785SAndreas Gohr                    $y0 = $yi - count($context);
770f3f0262cSandi                    $block = array();
771f3f0262cSandi                    if ($context)
772f3f0262cSandi                        $block[] = new _DiffOp_Copy($context);
773f3f0262cSandi                }
774f3f0262cSandi                $block[] = $edit;
775f3f0262cSandi            }
776f3f0262cSandi
777f3f0262cSandi            if ($edit->orig)
778f5b78785SAndreas Gohr                $xi += count($edit->orig);
779f3f0262cSandi            if ($edit->closing)
780f5b78785SAndreas Gohr                $yi += count($edit->closing);
781f3f0262cSandi        }
782f3f0262cSandi
783f3f0262cSandi        if (is_array($block))
7847deca91bSRobin Getz            $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block);
785f3f0262cSandi
786f3f0262cSandi        return $this->_end_diff();
787f3f0262cSandi    }
788f3f0262cSandi
78942ea7f44SGerrit Uitslag    /**
79042ea7f44SGerrit Uitslag     * @param int $xbeg
79142ea7f44SGerrit Uitslag     * @param int $xlen
79242ea7f44SGerrit Uitslag     * @param int $ybeg
79342ea7f44SGerrit Uitslag     * @param int $ylen
79442ea7f44SGerrit Uitslag     * @param array $edits
79542ea7f44SGerrit Uitslag     */
796f3f0262cSandi    function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
797f3f0262cSandi        $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
798f3f0262cSandi        foreach ($edits as $edit) {
799f3f0262cSandi            if ($edit->type == 'copy')
800f3f0262cSandi                $this->_context($edit->orig);
801f3f0262cSandi            elseif ($edit->type == 'add')
802f3f0262cSandi                $this->_added($edit->closing);
803f3f0262cSandi            elseif ($edit->type == 'delete')
804f3f0262cSandi                $this->_deleted($edit->orig);
805f3f0262cSandi            elseif ($edit->type == 'change')
806f3f0262cSandi                $this->_changed($edit->orig, $edit->closing);
807f3f0262cSandi            else
808*0f7af8fcSAndreas Gohr                throw new \RuntimeException("Unknown edit type");
809f3f0262cSandi        }
810f3f0262cSandi        $this->_end_block();
811f3f0262cSandi    }
812f3f0262cSandi
813f3f0262cSandi    function _start_diff() {
814f3f0262cSandi        ob_start();
815f3f0262cSandi    }
816f3f0262cSandi
817f3f0262cSandi    function _end_diff() {
818f3f0262cSandi        $val = ob_get_contents();
819f3f0262cSandi        ob_end_clean();
820f3f0262cSandi        return $val;
821f3f0262cSandi    }
822f3f0262cSandi
82342ea7f44SGerrit Uitslag    /**
82442ea7f44SGerrit Uitslag     * @param int $xbeg
82542ea7f44SGerrit Uitslag     * @param int $xlen
82642ea7f44SGerrit Uitslag     * @param int $ybeg
82742ea7f44SGerrit Uitslag     * @param int $ylen
82842ea7f44SGerrit Uitslag     * @return string
82942ea7f44SGerrit Uitslag     */
830f3f0262cSandi    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
831f3f0262cSandi        if ($xlen > 1)
832f3f0262cSandi            $xbeg .= "," . ($xbeg + $xlen - 1);
833f3f0262cSandi        if ($ylen > 1)
834f3f0262cSandi            $ybeg .= "," . ($ybeg + $ylen - 1);
835f3f0262cSandi
836f3f0262cSandi        return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
837f3f0262cSandi    }
838f3f0262cSandi
83942ea7f44SGerrit Uitslag    /**
84042ea7f44SGerrit Uitslag     * @param string $header
84142ea7f44SGerrit Uitslag     */
842f3f0262cSandi    function _start_block($header) {
843f3f0262cSandi        echo $header;
844f3f0262cSandi    }
845f3f0262cSandi
846f3f0262cSandi    function _end_block() {
847f3f0262cSandi    }
848f3f0262cSandi
849f3f0262cSandi    function _lines($lines, $prefix = ' ') {
850f3f0262cSandi        foreach ($lines as $line)
85160056e69SChristopher Smith            echo "$prefix ".$this->_escape($line)."\n";
852f3f0262cSandi    }
853f3f0262cSandi
854f3f0262cSandi    function _context($lines) {
855f3f0262cSandi        $this->_lines($lines);
856f3f0262cSandi    }
857f3f0262cSandi
858f3f0262cSandi    function _added($lines) {
859f3f0262cSandi        $this->_lines($lines, ">");
860f3f0262cSandi    }
861f3f0262cSandi    function _deleted($lines) {
862f3f0262cSandi        $this->_lines($lines, "<");
863f3f0262cSandi    }
864f3f0262cSandi
865f3f0262cSandi    function _changed($orig, $closing) {
866f3f0262cSandi        $this->_deleted($orig);
867f3f0262cSandi        echo "---\n";
868f3f0262cSandi        $this->_added($closing);
869f3f0262cSandi    }
87060056e69SChristopher Smith
871bfd197d2ShArpanet    /**
872bfd197d2ShArpanet     * Escape string
873bfd197d2ShArpanet     *
874bfd197d2ShArpanet     * Override this method within other formatters if escaping required.
875bfd197d2ShArpanet     * Base class requires $str to be returned WITHOUT escaping.
876bfd197d2ShArpanet     *
877bfd197d2ShArpanet     * @param $str string Text string to escape
878bfd197d2ShArpanet     * @return string The escaped string.
879bfd197d2ShArpanet     */
88060056e69SChristopher Smith    function _escape($str){
88160056e69SChristopher Smith        return $str;
88260056e69SChristopher Smith    }
883f3f0262cSandi}
884f3f0262cSandi
88547a906eaSAndreas Gohr/**
88647a906eaSAndreas Gohr * Utilityclass for styling HTML formatted diffs
88747a906eaSAndreas Gohr *
88847a906eaSAndreas Gohr * Depends on global var $DIFF_INLINESTYLES, if true some minimal predefined
88947a906eaSAndreas Gohr * inline styles are used. Useful for HTML mails and RSS feeds
89047a906eaSAndreas Gohr *
89147a906eaSAndreas Gohr * @author Andreas Gohr <[email protected]>
89247a906eaSAndreas Gohr */
89347a906eaSAndreas Gohrclass HTMLDiff {
89447a906eaSAndreas Gohr    /**
89547a906eaSAndreas Gohr     * Holds the style names and basic CSS
89647a906eaSAndreas Gohr     */
89747a906eaSAndreas Gohr    static public $styles = array(
89847a906eaSAndreas Gohr            'diff-addedline'    => 'background-color: #ddffdd;',
89947a906eaSAndreas Gohr            'diff-deletedline'  => 'background-color: #ffdddd;',
90047a906eaSAndreas Gohr            'diff-context'      => 'background-color: #f5f5f5;',
90147a906eaSAndreas Gohr            'diff-mark'         => 'color: #ff0000;',
90247a906eaSAndreas Gohr        );
90347a906eaSAndreas Gohr
90447a906eaSAndreas Gohr    /**
90547a906eaSAndreas Gohr     * Return a class or style parameter
906f50a239bSTakamura     *
907f50a239bSTakamura     * @param string $classname
908f50a239bSTakamura     *
909f50a239bSTakamura     * @return string
91047a906eaSAndreas Gohr     */
91147a906eaSAndreas Gohr    static function css($classname){
91247a906eaSAndreas Gohr        global $DIFF_INLINESTYLES;
91347a906eaSAndreas Gohr
91447a906eaSAndreas Gohr        if($DIFF_INLINESTYLES){
91547a906eaSAndreas Gohr            if(!isset(self::$styles[$classname])) return '';
91647a906eaSAndreas Gohr            return 'style="'.self::$styles[$classname].'"';
91747a906eaSAndreas Gohr        }else{
91847a906eaSAndreas Gohr            return 'class="'.$classname.'"';
91947a906eaSAndreas Gohr        }
92047a906eaSAndreas Gohr    }
92147a906eaSAndreas Gohr}
922f3f0262cSandi
923f3f0262cSandi/**
924f3f0262cSandi *  Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
925f3f0262cSandi *
926f3f0262cSandi */
927f3f0262cSandi
9289b3a5b24Schrisdefine('NBSP', "\xC2\xA0");     // utf-8 non-breaking space.
929f3f0262cSandi
930f3f0262cSandiclass _HWLDF_WordAccumulator {
931988c1340SPiyush Mishra
932ebba5e5dSDamien Regad    /** @var array */
933ebba5e5dSDamien Regad    protected $_lines;
934ebba5e5dSDamien Regad
935ebba5e5dSDamien Regad    /** @var string */
936ebba5e5dSDamien Regad    protected $_line;
937ebba5e5dSDamien Regad
938ebba5e5dSDamien Regad    /** @var string */
939ebba5e5dSDamien Regad    protected $_group;
940ebba5e5dSDamien Regad
941ebba5e5dSDamien Regad    /** @var string */
942ebba5e5dSDamien Regad    protected $_tag;
943ebba5e5dSDamien Regad
944988c1340SPiyush Mishra    function __construct() {
945f3f0262cSandi        $this->_lines = array();
946f3f0262cSandi        $this->_line = '';
947f3f0262cSandi        $this->_group = '';
948f3f0262cSandi        $this->_tag = '';
949f3f0262cSandi    }
950f3f0262cSandi
951f3f0262cSandi    function _flushGroup($new_tag) {
952f3f0262cSandi        if ($this->_group !== '') {
953f3f0262cSandi            if ($this->_tag == 'mark')
95460056e69SChristopher Smith                $this->_line .= '<strong '.HTMLDiff::css('diff-mark').'>'.$this->_escape($this->_group).'</strong>';
955812bb04eSRobin Getz            elseif ($this->_tag == 'add')
95660056e69SChristopher Smith                $this->_line .= '<span '.HTMLDiff::css('diff-addedline').'>'.$this->_escape($this->_group).'</span>';
957812bb04eSRobin Getz            elseif ($this->_tag == 'del')
95860056e69SChristopher Smith                $this->_line .= '<span '.HTMLDiff::css('diff-deletedline').'><del>'.$this->_escape($this->_group).'</del></span>';
959f3f0262cSandi            else
96060056e69SChristopher Smith                $this->_line .= $this->_escape($this->_group);
961f3f0262cSandi        }
962f3f0262cSandi        $this->_group = '';
963f3f0262cSandi        $this->_tag = $new_tag;
964f3f0262cSandi    }
965f3f0262cSandi
96642ea7f44SGerrit Uitslag    /**
96742ea7f44SGerrit Uitslag     * @param string $new_tag
96842ea7f44SGerrit Uitslag     */
969f3f0262cSandi    function _flushLine($new_tag) {
970f3f0262cSandi        $this->_flushGroup($new_tag);
971f3f0262cSandi        if ($this->_line != '')
972f3f0262cSandi            $this->_lines[] = $this->_line;
973f3f0262cSandi        $this->_line = '';
974f3f0262cSandi    }
975f3f0262cSandi
976f3f0262cSandi    function addWords($words, $tag = '') {
977f3f0262cSandi        if ($tag != $this->_tag)
978f3f0262cSandi            $this->_flushGroup($tag);
979f3f0262cSandi
980f3f0262cSandi        foreach ($words as $word) {
981f3f0262cSandi            // new-line should only come as first char of word.
982f3f0262cSandi            if ($word == '')
983f3f0262cSandi                continue;
984f3f0262cSandi            if ($word[0] == "\n") {
985f3f0262cSandi                $this->_group .= NBSP;
986f3f0262cSandi                $this->_flushLine($tag);
987f3f0262cSandi                $word = substr($word, 1);
988f3f0262cSandi            }
989f3f0262cSandi            assert(!strstr($word, "\n"));
990f3f0262cSandi            $this->_group .= $word;
991f3f0262cSandi        }
992f3f0262cSandi    }
993f3f0262cSandi
994f3f0262cSandi    function getLines() {
995f3f0262cSandi        $this->_flushLine('~done');
996f3f0262cSandi        return $this->_lines;
997f3f0262cSandi    }
99860056e69SChristopher Smith
99960056e69SChristopher Smith    function _escape($str){
100060056e69SChristopher Smith        return hsc($str);
100160056e69SChristopher Smith    }
1002f3f0262cSandi}
1003f3f0262cSandi
1004f5b78785SAndreas Gohrclass WordLevelDiff extends MappedDiff {
1005f5b78785SAndreas Gohr
1006988c1340SPiyush Mishra    function __construct($orig_lines, $closing_lines) {
1007f3f0262cSandi        list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
1008f3f0262cSandi        list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
1009f3f0262cSandi
1010988c1340SPiyush Mishra        parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped);
1011f3f0262cSandi    }
1012f3f0262cSandi
1013f3f0262cSandi    function _split($lines) {
10145e1ee188SXin LI        if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu',
10157deca91bSRobin Getz             implode("\n", $lines), $m)) {
1016f3f0262cSandi            return array(array(''), array(''));
1017f3f0262cSandi        }
1018f3f0262cSandi        return array($m[0], $m[1]);
1019f3f0262cSandi    }
1020f3f0262cSandi
1021f3f0262cSandi    function orig() {
1022f3f0262cSandi        $orig = new _HWLDF_WordAccumulator;
1023f3f0262cSandi
1024f3f0262cSandi        foreach ($this->edits as $edit) {
1025f3f0262cSandi            if ($edit->type == 'copy')
1026f3f0262cSandi                $orig->addWords($edit->orig);
1027f3f0262cSandi            elseif ($edit->orig)
1028f3f0262cSandi                $orig->addWords($edit->orig, 'mark');
1029f3f0262cSandi        }
1030f3f0262cSandi        return $orig->getLines();
1031f3f0262cSandi    }
1032f3f0262cSandi
1033f3f0262cSandi    function closing() {
1034f3f0262cSandi        $closing = new _HWLDF_WordAccumulator;
1035f3f0262cSandi
1036f3f0262cSandi        foreach ($this->edits as $edit) {
1037f3f0262cSandi            if ($edit->type == 'copy')
1038f3f0262cSandi                $closing->addWords($edit->closing);
1039f3f0262cSandi            elseif ($edit->closing)
1040f3f0262cSandi                $closing->addWords($edit->closing, 'mark');
1041f3f0262cSandi        }
1042f3f0262cSandi        return $closing->getLines();
1043f3f0262cSandi    }
1044f3f0262cSandi}
1045f3f0262cSandi
1046812bb04eSRobin Getzclass InlineWordLevelDiff extends MappedDiff {
1047812bb04eSRobin Getz
1048988c1340SPiyush Mishra    function __construct($orig_lines, $closing_lines) {
1049812bb04eSRobin Getz        list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
1050812bb04eSRobin Getz        list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
1051812bb04eSRobin Getz
1052988c1340SPiyush Mishra        parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped);
1053812bb04eSRobin Getz    }
1054812bb04eSRobin Getz
1055812bb04eSRobin Getz    function _split($lines) {
1056c5982caaSDanny Lin        if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu',
1057812bb04eSRobin Getz             implode("\n", $lines), $m)) {
1058812bb04eSRobin Getz            return array(array(''), array(''));
1059812bb04eSRobin Getz        }
1060812bb04eSRobin Getz        return array($m[0], $m[1]);
1061812bb04eSRobin Getz    }
1062812bb04eSRobin Getz
1063812bb04eSRobin Getz    function inline() {
1064812bb04eSRobin Getz        $orig = new _HWLDF_WordAccumulator;
1065812bb04eSRobin Getz        foreach ($this->edits as $edit) {
1066812bb04eSRobin Getz            if ($edit->type == 'copy')
10674f2305cbSAdrian Lang                $orig->addWords($edit->closing);
1068812bb04eSRobin Getz            elseif ($edit->type == 'change'){
1069812bb04eSRobin Getz                $orig->addWords($edit->orig, 'del');
1070812bb04eSRobin Getz                $orig->addWords($edit->closing, 'add');
1071812bb04eSRobin Getz            } elseif ($edit->type == 'delete')
1072812bb04eSRobin Getz                $orig->addWords($edit->orig, 'del');
1073812bb04eSRobin Getz            elseif ($edit->type == 'add')
1074812bb04eSRobin Getz                $orig->addWords($edit->closing, 'add');
1075812bb04eSRobin Getz            elseif ($edit->orig)
1076812bb04eSRobin Getz                $orig->addWords($edit->orig, 'del');
1077812bb04eSRobin Getz        }
1078812bb04eSRobin Getz        return $orig->getLines();
1079812bb04eSRobin Getz    }
1080812bb04eSRobin Getz}
1081812bb04eSRobin Getz
1082f3f0262cSandi/**
1083f3f0262cSandi * "Unified" diff formatter.
1084f3f0262cSandi *
1085f3f0262cSandi * This class formats the diff in classic "unified diff" format.
1086df9752e9SChristopher Smith *
1087df9752e9SChristopher Smith * NOTE: output is plain text and unsafe for use in HTML without escaping.
1088f3f0262cSandi */
1089f5b78785SAndreas Gohrclass UnifiedDiffFormatter extends DiffFormatter {
1090f5b78785SAndreas Gohr
1091988c1340SPiyush Mishra    function __construct($context_lines = 4) {
1092f3f0262cSandi        $this->leading_context_lines = $context_lines;
1093f3f0262cSandi        $this->trailing_context_lines = $context_lines;
1094f3f0262cSandi    }
1095f3f0262cSandi
1096f3f0262cSandi    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
1097f3f0262cSandi        if ($xlen != 1)
1098f3f0262cSandi            $xbeg .= "," . $xlen;
1099f3f0262cSandi        if ($ylen != 1)
1100f3f0262cSandi            $ybeg .= "," . $ylen;
1101f3f0262cSandi        return "@@ -$xbeg +$ybeg @@\n";
1102f3f0262cSandi    }
1103f3f0262cSandi
1104f3f0262cSandi    function _added($lines) {
1105f3f0262cSandi        $this->_lines($lines, "+");
1106f3f0262cSandi    }
1107f3f0262cSandi    function _deleted($lines) {
1108f3f0262cSandi        $this->_lines($lines, "-");
1109f3f0262cSandi    }
1110f3f0262cSandi    function _changed($orig, $final) {
1111f3f0262cSandi        $this->_deleted($orig);
1112f3f0262cSandi        $this->_added($final);
1113f3f0262cSandi    }
1114f3f0262cSandi}
1115f3f0262cSandi
1116f3f0262cSandi/**
1117f3f0262cSandi *  Wikipedia Table style diff formatter.
1118f3f0262cSandi *
1119f3f0262cSandi */
1120f5b78785SAndreas Gohrclass TableDiffFormatter extends DiffFormatter {
11213a4ea35cSChristopher Smith    var $colspan = 2;
1122f5b78785SAndreas Gohr
1123988c1340SPiyush Mishra    function __construct() {
1124f3f0262cSandi        $this->leading_context_lines = 2;
1125f3f0262cSandi        $this->trailing_context_lines = 2;
1126f3f0262cSandi    }
1127f3f0262cSandi
112842ea7f44SGerrit Uitslag    /**
112942ea7f44SGerrit Uitslag     * @param Diff $diff
113042ea7f44SGerrit Uitslag     * @return string
113142ea7f44SGerrit Uitslag     */
11322d880650SAdrian Lang    function format($diff) {
11332d880650SAdrian Lang        // Preserve whitespaces by converting some to non-breaking spaces.
11342d880650SAdrian Lang        // Do not convert all of them to allow word-wrap.
11352d880650SAdrian Lang        $val = parent::format($diff);
1136e260f93bSAnika Henke        $val = str_replace('  ','&#160; ', $val);
1137e260f93bSAnika Henke        $val = preg_replace('/ (?=<)|(?<=[ >]) /', '&#160;', $val);
11382d880650SAdrian Lang        return $val;
11392d880650SAdrian Lang    }
11402d880650SAdrian Lang
1141f3f0262cSandi    function _pre($text){
1142f3f0262cSandi        $text = htmlspecialchars($text);
1143f3f0262cSandi        return $text;
1144f3f0262cSandi    }
1145f3f0262cSandi
1146f3f0262cSandi    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
1147f3f0262cSandi        global $lang;
1148f3f0262cSandi        $l1 = $lang['line'].' '.$xbeg;
1149f3f0262cSandi        $l2 = $lang['line'].' '.$ybeg;
11503a4ea35cSChristopher Smith        $r = '<tr><td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l1.":</td>\n".
11513a4ea35cSChristopher Smith             '<td '.HTMLDiff::css('diff-blockheader').' colspan="'.$this->colspan.'">'.$l2.":</td>\n".
11525048c277SAnika Henke             "</tr>\n";
1153f3f0262cSandi        return $r;
1154f3f0262cSandi    }
1155f3f0262cSandi
1156f3f0262cSandi    function _start_block($header) {
1157f3f0262cSandi        print($header);
1158f3f0262cSandi    }
1159f3f0262cSandi
1160f3f0262cSandi    function _end_block() {
1161f3f0262cSandi    }
1162f3f0262cSandi
1163f3f0262cSandi    function _lines($lines, $prefix=' ', $color="white") {
1164f3f0262cSandi    }
1165f3f0262cSandi
116660056e69SChristopher Smith    function addedLine($line,$escaped=false) {
116760056e69SChristopher Smith        if (!$escaped){
116860056e69SChristopher Smith            $line = $this->_escape($line);
116960056e69SChristopher Smith        }
1170f76724a4STom N Harris        return '<td '.HTMLDiff::css('diff-lineheader').'>+</td>'.
1171f76724a4STom N Harris               '<td '.HTMLDiff::css('diff-addedline').'>' .  $line.'</td>';
1172f3f0262cSandi    }
1173f3f0262cSandi
117460056e69SChristopher Smith    function deletedLine($line,$escaped=false) {
117560056e69SChristopher Smith        if (!$escaped){
117660056e69SChristopher Smith            $line = $this->_escape($line);
117760056e69SChristopher Smith        }
1178f76724a4STom N Harris        return '<td '.HTMLDiff::css('diff-lineheader').'>-</td>'.
1179f76724a4STom N Harris               '<td '.HTMLDiff::css('diff-deletedline').'>' .  $line.'</td>';
1180f3f0262cSandi    }
1181f3f0262cSandi
1182f3f0262cSandi    function emptyLine() {
11833a4ea35cSChristopher Smith        return '<td colspan="'.$this->colspan.'">&#160;</td>';
1184f3f0262cSandi    }
1185f3f0262cSandi
1186f3f0262cSandi    function contextLine($line) {
1187f76724a4STom N Harris        return '<td '.HTMLDiff::css('diff-lineheader').'>&#160;</td>'.
1188333ef4f3SChristopher Smith               '<td '.HTMLDiff::css('diff-context').'>'.$this->_escape($line).'</td>';
1189f3f0262cSandi    }
1190f3f0262cSandi
1191f3f0262cSandi    function _added($lines) {
119260056e69SChristopher Smith        $this->_addedLines($lines,false);
119360056e69SChristopher Smith    }
119460056e69SChristopher Smith
119560056e69SChristopher Smith    function _addedLines($lines,$escaped=false){
1196f3f0262cSandi        foreach ($lines as $line) {
119760056e69SChristopher Smith            print('<tr>' . $this->emptyLine() . $this->addedLine($line,$escaped) . "</tr>\n");
1198f3f0262cSandi        }
1199f3f0262cSandi    }
1200f3f0262cSandi
1201f3f0262cSandi    function _deleted($lines) {
1202f3f0262cSandi        foreach ($lines as $line) {
12037deca91bSRobin Getz            print('<tr>' . $this->deletedLine($line) . $this->emptyLine() . "</tr>\n");
1204f3f0262cSandi        }
1205f3f0262cSandi    }
1206f3f0262cSandi
1207f3f0262cSandi    function _context($lines) {
1208f3f0262cSandi        foreach ($lines as $line) {
12097deca91bSRobin Getz            print('<tr>' . $this->contextLine($line) .  $this->contextLine($line) . "</tr>\n");
1210f3f0262cSandi        }
1211f3f0262cSandi    }
1212f3f0262cSandi
1213f3f0262cSandi    function _changed($orig, $closing) {
121460056e69SChristopher Smith        $diff = new WordLevelDiff($orig, $closing);  // this escapes the diff data
1215f3f0262cSandi        $del = $diff->orig();
1216f3f0262cSandi        $add = $diff->closing();
1217f3f0262cSandi
1218f3f0262cSandi        while ($line = array_shift($del)) {
1219f3f0262cSandi            $aline = array_shift($add);
122060056e69SChristopher Smith            print('<tr>' . $this->deletedLine($line,true) . $this->addedLine($aline,true) . "</tr>\n");
1221f3f0262cSandi        }
122260056e69SChristopher Smith        $this->_addedLines($add,true); # If any leftovers
122360056e69SChristopher Smith    }
122460056e69SChristopher Smith
122560056e69SChristopher Smith    function _escape($str) {
122660056e69SChristopher Smith        return hsc($str);
1227f3f0262cSandi    }
1228f3f0262cSandi}
1229f3f0262cSandi
1230812bb04eSRobin Getz/**
1231812bb04eSRobin Getz *  Inline style diff formatter.
1232812bb04eSRobin Getz *
1233812bb04eSRobin Getz */
1234812bb04eSRobin Getzclass InlineDiffFormatter extends DiffFormatter {
1235f76724a4STom N Harris    var $colspan = 2;
1236988c1340SPiyush Mishra
1237988c1340SPiyush Mishra    function __construct() {
1238812bb04eSRobin Getz        $this->leading_context_lines = 2;
1239812bb04eSRobin Getz        $this->trailing_context_lines = 2;
1240812bb04eSRobin Getz    }
1241812bb04eSRobin Getz
124242ea7f44SGerrit Uitslag    /**
124342ea7f44SGerrit Uitslag     * @param Diff $diff
124442ea7f44SGerrit Uitslag     * @return string
124542ea7f44SGerrit Uitslag     */
1246812bb04eSRobin Getz    function format($diff) {
1247812bb04eSRobin Getz        // Preserve whitespaces by converting some to non-breaking spaces.
1248812bb04eSRobin Getz        // Do not convert all of them to allow word-wrap.
1249812bb04eSRobin Getz        $val = parent::format($diff);
1250e260f93bSAnika Henke        $val = str_replace('  ','&#160; ', $val);
1251e260f93bSAnika Henke        $val = preg_replace('/ (?=<)|(?<=[ >]) /', '&#160;', $val);
1252812bb04eSRobin Getz        return $val;
1253812bb04eSRobin Getz    }
1254812bb04eSRobin Getz
1255812bb04eSRobin Getz    function _pre($text){
1256812bb04eSRobin Getz        $text = htmlspecialchars($text);
1257812bb04eSRobin Getz        return $text;
1258812bb04eSRobin Getz    }
1259812bb04eSRobin Getz
1260812bb04eSRobin Getz    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
1261812bb04eSRobin Getz        global $lang;
1262812bb04eSRobin Getz        if ($xlen != 1)
1263812bb04eSRobin Getz            $xbeg .= "," . $xlen;
1264812bb04eSRobin Getz        if ($ylen != 1)
1265812bb04eSRobin Getz            $ybeg .= "," . $ylen;
12663a4ea35cSChristopher Smith        $r = '<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-blockheader').'>@@ '.$lang['line']." -$xbeg +$ybeg @@";
126747a906eaSAndreas Gohr        $r .= ' <span '.HTMLDiff::css('diff-deletedline').'><del>'.$lang['deleted'].'</del></span>';
126847a906eaSAndreas Gohr        $r .= ' <span '.HTMLDiff::css('diff-addedline').'>'.$lang['created'].'</span>';
1269812bb04eSRobin Getz        $r .= "</td></tr>\n";
1270812bb04eSRobin Getz        return $r;
1271812bb04eSRobin Getz    }
1272812bb04eSRobin Getz
1273812bb04eSRobin Getz    function _start_block($header) {
1274812bb04eSRobin Getz        print($header."\n");
1275812bb04eSRobin Getz    }
1276812bb04eSRobin Getz
1277812bb04eSRobin Getz    function _end_block() {
1278812bb04eSRobin Getz    }
1279812bb04eSRobin Getz
1280812bb04eSRobin Getz    function _lines($lines, $prefix=' ', $color="white") {
1281812bb04eSRobin Getz    }
1282812bb04eSRobin Getz
1283812bb04eSRobin Getz    function _added($lines) {
1284812bb04eSRobin Getz        foreach ($lines as $line) {
1285333ef4f3SChristopher Smith            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td '.HTMLDiff::css('diff-addedline').'>'. $this->_escape($line) . "</td></tr>\n");
1286812bb04eSRobin Getz        }
1287812bb04eSRobin Getz    }
1288812bb04eSRobin Getz
1289812bb04eSRobin Getz    function _deleted($lines) {
1290812bb04eSRobin Getz        foreach ($lines as $line) {
1291333ef4f3SChristopher Smith            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td '.HTMLDiff::css('diff-deletedline').'><del>' . $this->_escape($line) . "</del></td></tr>\n");
1292812bb04eSRobin Getz        }
1293812bb04eSRobin Getz    }
1294812bb04eSRobin Getz
1295812bb04eSRobin Getz    function _context($lines) {
1296812bb04eSRobin Getz        foreach ($lines as $line) {
1297333ef4f3SChristopher Smith            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td '.HTMLDiff::css('diff-context').'>'. $this->_escape($line) ."</td></tr>\n");
1298812bb04eSRobin Getz        }
1299812bb04eSRobin Getz    }
1300812bb04eSRobin Getz
1301812bb04eSRobin Getz    function _changed($orig, $closing) {
130260056e69SChristopher Smith        $diff = new InlineWordLevelDiff($orig, $closing);  // this escapes the diff data
1303812bb04eSRobin Getz        $add = $diff->inline();
1304812bb04eSRobin Getz
1305812bb04eSRobin Getz        foreach ($add as $line)
1306a69506c5STom N Harris            print('<tr><td '.HTMLDiff::css('diff-lineheader').'>&#160;</td><td>'.$line."</td></tr>\n");
1307812bb04eSRobin Getz    }
130860056e69SChristopher Smith
130960056e69SChristopher Smith    function _escape($str) {
131060056e69SChristopher Smith        return hsc($str);
131160056e69SChristopher Smith    }
1312812bb04eSRobin Getz}
1313812bb04eSRobin Getz
1314a297e675SAndreas Gohr/**
1315a297e675SAndreas Gohr * A class for computing three way diffs.
1316a297e675SAndreas Gohr *
1317a297e675SAndreas Gohr * @author  Geoffrey T. Dairiki <[email protected]>
1318a297e675SAndreas Gohr */
1319a297e675SAndreas Gohrclass Diff3 extends Diff {
1320a297e675SAndreas Gohr
1321a297e675SAndreas Gohr    /**
1322a297e675SAndreas Gohr     * Conflict counter.
1323a297e675SAndreas Gohr     *
1324a297e675SAndreas Gohr     * @var integer
1325a297e675SAndreas Gohr     */
1326a297e675SAndreas Gohr    var $_conflictingBlocks = 0;
1327a297e675SAndreas Gohr
1328a297e675SAndreas Gohr    /**
1329a297e675SAndreas Gohr     * Computes diff between 3 sequences of strings.
1330a297e675SAndreas Gohr     *
1331a297e675SAndreas Gohr     * @param array $orig    The original lines to use.
1332a297e675SAndreas Gohr     * @param array $final1  The first version to compare to.
1333a297e675SAndreas Gohr     * @param array $final2  The second version to compare to.
1334a297e675SAndreas Gohr     */
1335a297e675SAndreas Gohr    function __construct($orig, $final1, $final2) {
1336a297e675SAndreas Gohr        $engine = new _DiffEngine();
1337a297e675SAndreas Gohr
1338a297e675SAndreas Gohr        $this->_edits = $this->_diff3($engine->diff($orig, $final1),
1339a297e675SAndreas Gohr                                      $engine->diff($orig, $final2));
1340a297e675SAndreas Gohr    }
1341a297e675SAndreas Gohr
1342a297e675SAndreas Gohr    /**
1343a297e675SAndreas Gohr     * Returns the merged lines
1344a297e675SAndreas Gohr     *
1345a297e675SAndreas Gohr     * @param string $label1  label for first version
1346a297e675SAndreas Gohr     * @param string $label2  label for second version
134734df7cb0SAndreas Gohr     * @param string $label3  separator between versions
1348a297e675SAndreas Gohr     * @return array          lines of the merged text
1349a297e675SAndreas Gohr     */
135034df7cb0SAndreas Gohr    function mergedOutput($label1='<<<<<<<',$label2='>>>>>>>',$label3='=======') {
1351a297e675SAndreas Gohr        $lines = array();
1352a297e675SAndreas Gohr        foreach ($this->_edits as $edit) {
1353a297e675SAndreas Gohr            if ($edit->isConflict()) {
1354a297e675SAndreas Gohr                /* FIXME: this should probably be moved somewhere else. */
1355a297e675SAndreas Gohr                $lines = array_merge($lines,
135634df7cb0SAndreas Gohr                                     array($label1),
1357a297e675SAndreas Gohr                                     $edit->final1,
135834df7cb0SAndreas Gohr                                     array($label3),
1359a297e675SAndreas Gohr                                     $edit->final2,
136034df7cb0SAndreas Gohr                                     array($label2));
1361a297e675SAndreas Gohr                $this->_conflictingBlocks++;
1362a297e675SAndreas Gohr            } else {
1363a297e675SAndreas Gohr                $lines = array_merge($lines, $edit->merged());
1364a297e675SAndreas Gohr            }
1365a297e675SAndreas Gohr        }
1366a297e675SAndreas Gohr
1367a297e675SAndreas Gohr        return $lines;
1368a297e675SAndreas Gohr    }
1369a297e675SAndreas Gohr
1370a297e675SAndreas Gohr    /**
1371a297e675SAndreas Gohr     * @access private
1372f50a239bSTakamura     *
1373f50a239bSTakamura     * @param array $edits1
1374f50a239bSTakamura     * @param array $edits2
1375f50a239bSTakamura     *
1376f50a239bSTakamura     * @return array
1377a297e675SAndreas Gohr     */
1378a297e675SAndreas Gohr    function _diff3($edits1, $edits2) {
1379a297e675SAndreas Gohr        $edits = array();
1380a297e675SAndreas Gohr        $bb = new _Diff3_BlockBuilder();
1381a297e675SAndreas Gohr
1382a297e675SAndreas Gohr        $e1 = current($edits1);
1383a297e675SAndreas Gohr        $e2 = current($edits2);
1384a297e675SAndreas Gohr        while ($e1 || $e2) {
1385a297e675SAndreas Gohr            if ($e1 && $e2 && is_a($e1, '_DiffOp_copy') && is_a($e2, '_DiffOp_copy')) {
1386a297e675SAndreas Gohr                /* We have copy blocks from both diffs. This is the (only)
1387a297e675SAndreas Gohr                 * time we want to emit a diff3 copy block.  Flush current
1388a297e675SAndreas Gohr                 * diff3 diff block, if any. */
1389a297e675SAndreas Gohr                if ($edit = $bb->finish()) {
1390a297e675SAndreas Gohr                    $edits[] = $edit;
1391a297e675SAndreas Gohr                }
1392a297e675SAndreas Gohr
1393a297e675SAndreas Gohr                $ncopy = min($e1->norig(), $e2->norig());
1394a297e675SAndreas Gohr                assert($ncopy > 0);
1395a297e675SAndreas Gohr                $edits[] = new _Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy));
1396a297e675SAndreas Gohr
1397a297e675SAndreas Gohr                if ($e1->norig() > $ncopy) {
1398a297e675SAndreas Gohr                    array_splice($e1->orig, 0, $ncopy);
1399a297e675SAndreas Gohr                    array_splice($e1->closing, 0, $ncopy);
1400a297e675SAndreas Gohr                } else {
1401a297e675SAndreas Gohr                    $e1 = next($edits1);
1402a297e675SAndreas Gohr                }
1403a297e675SAndreas Gohr
1404a297e675SAndreas Gohr                if ($e2->norig() > $ncopy) {
1405a297e675SAndreas Gohr                    array_splice($e2->orig, 0, $ncopy);
1406a297e675SAndreas Gohr                    array_splice($e2->closing, 0, $ncopy);
1407a297e675SAndreas Gohr                } else {
1408a297e675SAndreas Gohr                    $e2 = next($edits2);
1409a297e675SAndreas Gohr                }
1410a297e675SAndreas Gohr            } else {
1411a297e675SAndreas Gohr                if ($e1 && $e2) {
1412a297e675SAndreas Gohr                    if ($e1->orig && $e2->orig) {
1413a297e675SAndreas Gohr                        $norig = min($e1->norig(), $e2->norig());
1414a297e675SAndreas Gohr                        $orig = array_splice($e1->orig, 0, $norig);
1415a297e675SAndreas Gohr                        array_splice($e2->orig, 0, $norig);
1416a297e675SAndreas Gohr                        $bb->input($orig);
1417a297e675SAndreas Gohr                    }
1418a297e675SAndreas Gohr
1419a297e675SAndreas Gohr                    if (is_a($e1, '_DiffOp_copy')) {
1420a297e675SAndreas Gohr                        $bb->out1(array_splice($e1->closing, 0, $norig));
1421a297e675SAndreas Gohr                    }
1422a297e675SAndreas Gohr
1423a297e675SAndreas Gohr                    if (is_a($e2, '_DiffOp_copy')) {
1424a297e675SAndreas Gohr                        $bb->out2(array_splice($e2->closing, 0, $norig));
1425a297e675SAndreas Gohr                    }
1426a297e675SAndreas Gohr                }
1427a297e675SAndreas Gohr
1428a297e675SAndreas Gohr                if ($e1 && ! $e1->orig) {
1429a297e675SAndreas Gohr                    $bb->out1($e1->closing);
1430a297e675SAndreas Gohr                    $e1 = next($edits1);
1431a297e675SAndreas Gohr                }
1432a297e675SAndreas Gohr                if ($e2 && ! $e2->orig) {
1433a297e675SAndreas Gohr                    $bb->out2($e2->closing);
1434a297e675SAndreas Gohr                    $e2 = next($edits2);
1435a297e675SAndreas Gohr                }
1436a297e675SAndreas Gohr            }
1437a297e675SAndreas Gohr        }
1438a297e675SAndreas Gohr
1439a297e675SAndreas Gohr        if ($edit = $bb->finish()) {
1440a297e675SAndreas Gohr            $edits[] = $edit;
1441a297e675SAndreas Gohr        }
1442a297e675SAndreas Gohr
1443a297e675SAndreas Gohr        return $edits;
1444a297e675SAndreas Gohr    }
1445a297e675SAndreas Gohr}
1446a297e675SAndreas Gohr
1447a297e675SAndreas Gohr/**
1448a297e675SAndreas Gohr * @author  Geoffrey T. Dairiki <[email protected]>
1449a297e675SAndreas Gohr *
1450a297e675SAndreas Gohr * @access private
1451a297e675SAndreas Gohr */
1452a297e675SAndreas Gohrclass _Diff3_Op {
1453a297e675SAndreas Gohr
1454ebba5e5dSDamien Regad    /** @var array|mixed */
1455ebba5e5dSDamien Regad    protected $orig;
1456ebba5e5dSDamien Regad
1457ebba5e5dSDamien Regad    /** @var array|mixed */
1458ebba5e5dSDamien Regad    protected $final1;
1459ebba5e5dSDamien Regad
1460ebba5e5dSDamien Regad    /** @var array|mixed */
1461ebba5e5dSDamien Regad    protected $final2;
1462ebba5e5dSDamien Regad
1463ebba5e5dSDamien Regad    /** @var array|mixed|false */
1464ebba5e5dSDamien Regad    protected $_merged;
1465ebba5e5dSDamien Regad
1466a297e675SAndreas Gohr    function __construct($orig = false, $final1 = false, $final2 = false) {
1467a297e675SAndreas Gohr        $this->orig = $orig ? $orig : array();
1468a297e675SAndreas Gohr        $this->final1 = $final1 ? $final1 : array();
1469a297e675SAndreas Gohr        $this->final2 = $final2 ? $final2 : array();
1470a297e675SAndreas Gohr    }
1471a297e675SAndreas Gohr
1472a297e675SAndreas Gohr    function merged() {
1473a297e675SAndreas Gohr        if (!isset($this->_merged)) {
1474a297e675SAndreas Gohr            if ($this->final1 === $this->final2) {
1475a297e675SAndreas Gohr                $this->_merged = &$this->final1;
1476a297e675SAndreas Gohr            } elseif ($this->final1 === $this->orig) {
1477a297e675SAndreas Gohr                $this->_merged = &$this->final2;
1478a297e675SAndreas Gohr            } elseif ($this->final2 === $this->orig) {
1479a297e675SAndreas Gohr                $this->_merged = &$this->final1;
1480a297e675SAndreas Gohr            } else {
1481a297e675SAndreas Gohr                $this->_merged = false;
1482a297e675SAndreas Gohr            }
1483a297e675SAndreas Gohr        }
1484a297e675SAndreas Gohr
1485a297e675SAndreas Gohr        return $this->_merged;
1486a297e675SAndreas Gohr    }
1487a297e675SAndreas Gohr
1488a297e675SAndreas Gohr    function isConflict() {
1489a297e675SAndreas Gohr        return $this->merged() === false;
1490a297e675SAndreas Gohr    }
1491a297e675SAndreas Gohr
1492a297e675SAndreas Gohr}
1493a297e675SAndreas Gohr
1494a297e675SAndreas Gohr/**
1495a297e675SAndreas Gohr * @author  Geoffrey T. Dairiki <[email protected]>
1496a297e675SAndreas Gohr *
1497a297e675SAndreas Gohr * @access private
1498a297e675SAndreas Gohr */
1499a297e675SAndreas Gohrclass _Diff3_Op_copy extends _Diff3_Op {
1500a297e675SAndreas Gohr
1501a297e675SAndreas Gohr    function __construct($lines = false) {
1502a297e675SAndreas Gohr        $this->orig = $lines ? $lines : array();
1503a297e675SAndreas Gohr        $this->final1 = &$this->orig;
1504a297e675SAndreas Gohr        $this->final2 = &$this->orig;
1505a297e675SAndreas Gohr    }
1506a297e675SAndreas Gohr
1507a297e675SAndreas Gohr    function merged() {
1508a297e675SAndreas Gohr        return $this->orig;
1509a297e675SAndreas Gohr    }
1510a297e675SAndreas Gohr
1511a297e675SAndreas Gohr    function isConflict() {
1512a297e675SAndreas Gohr        return false;
1513a297e675SAndreas Gohr    }
1514a297e675SAndreas Gohr}
1515a297e675SAndreas Gohr
1516a297e675SAndreas Gohr/**
1517a297e675SAndreas Gohr * @author  Geoffrey T. Dairiki <[email protected]>
1518a297e675SAndreas Gohr *
1519a297e675SAndreas Gohr * @access private
1520a297e675SAndreas Gohr */
1521a297e675SAndreas Gohrclass _Diff3_BlockBuilder {
1522a297e675SAndreas Gohr
1523a297e675SAndreas Gohr    function __construct() {
1524a297e675SAndreas Gohr        $this->_init();
1525a297e675SAndreas Gohr    }
1526a297e675SAndreas Gohr
1527a297e675SAndreas Gohr    function input($lines) {
1528a297e675SAndreas Gohr        if ($lines) {
1529a297e675SAndreas Gohr            $this->_append($this->orig, $lines);
1530a297e675SAndreas Gohr        }
1531a297e675SAndreas Gohr    }
1532a297e675SAndreas Gohr
1533a297e675SAndreas Gohr    function out1($lines) {
1534a297e675SAndreas Gohr        if ($lines) {
1535a297e675SAndreas Gohr            $this->_append($this->final1, $lines);
1536a297e675SAndreas Gohr        }
1537a297e675SAndreas Gohr    }
1538a297e675SAndreas Gohr
1539a297e675SAndreas Gohr    function out2($lines) {
1540a297e675SAndreas Gohr        if ($lines) {
1541a297e675SAndreas Gohr            $this->_append($this->final2, $lines);
1542a297e675SAndreas Gohr        }
1543a297e675SAndreas Gohr    }
1544a297e675SAndreas Gohr
1545a297e675SAndreas Gohr    function isEmpty() {
1546a297e675SAndreas Gohr        return !$this->orig && !$this->final1 && !$this->final2;
1547a297e675SAndreas Gohr    }
1548a297e675SAndreas Gohr
1549a297e675SAndreas Gohr    function finish() {
1550a297e675SAndreas Gohr        if ($this->isEmpty()) {
1551a297e675SAndreas Gohr            return false;
1552a297e675SAndreas Gohr        } else {
1553a297e675SAndreas Gohr            $edit = new _Diff3_Op($this->orig, $this->final1, $this->final2);
1554a297e675SAndreas Gohr            $this->_init();
1555a297e675SAndreas Gohr            return $edit;
1556a297e675SAndreas Gohr        }
1557a297e675SAndreas Gohr    }
1558a297e675SAndreas Gohr
1559a297e675SAndreas Gohr    function _init() {
1560a297e675SAndreas Gohr        $this->orig = $this->final1 = $this->final2 = array();
1561a297e675SAndreas Gohr    }
1562a297e675SAndreas Gohr
1563a297e675SAndreas Gohr    function _append(&$array, $lines) {
1564a297e675SAndreas Gohr        array_splice($array, sizeof($array), 0, $lines);
1565a297e675SAndreas Gohr    }
1566a297e675SAndreas Gohr}
1567340756e4Sandi
1568e3776c06SMichael Hamann//Setup VIM: ex: et ts=4 :
1569