aboutsummaryrefslogtreecommitdiffstats
path: root/build/diff_class.php
diff options
context:
space:
mode:
authorNils Adermann <naderman@naderman.de>2010-02-01 00:03:26 +0000
committerNils Adermann <naderman@naderman.de>2010-02-01 00:03:26 +0000
commit9487673f9c218c776f36605a0c2c9fcd4d55b50c (patch)
treec6a07f05d1b9f3998d8d849afd86870e207909cd /build/diff_class.php
parent926d0f980ee2e064a2778da3fdca7b6be2e95e3d (diff)
downloadforums-9487673f9c218c776f36605a0c2c9fcd4d55b50c.tar
forums-9487673f9c218c776f36605a0c2c9fcd4d55b50c.tar.gz
forums-9487673f9c218c776f36605a0c2c9fcd4d55b50c.tar.bz2
forums-9487673f9c218c776f36605a0c2c9fcd4d55b50c.tar.xz
forums-9487673f9c218c776f36605a0c2c9fcd4d55b50c.zip
Adding the phpBB build script. This is the latest version of the script Meik has been using for releases of phpBB. I've made a few smaller changes to make its output work on the CLI and for it to work when located inside a working directory. Various notices/warnings etc. have been fixed, too. We probably want to make some more major changes to the script, at the moment it seems a little difficult to follow exactly what it does.
git-svn-id: file:///svn/phpbb/branches/phpBB-3_0_0@10465 89ea8834-ac86-4346-8a33-228a782c2dd0
Diffstat (limited to 'build/diff_class.php')
-rw-r--r--build/diff_class.php1677
1 files changed, 1677 insertions, 0 deletions
diff --git a/build/diff_class.php b/build/diff_class.php
new file mode 100644
index 0000000000..0d7c2dcd3a
--- /dev/null
+++ b/build/diff_class.php
@@ -0,0 +1,1677 @@
+<?php
+/**
+*
+* @package build
+* @version $Id$
+* @copyright (c) 2000 Geoffrey T. Dairiki <dairiki@dairiki.org>
+* @copyright (c) 2010 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* Class used internally by WikiDiff to actually compute the diffs.
+*
+* The algorithm used here is mostly lifted from the perl module
+* Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
+* http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
+*
+* More ideas are taken from:
+* http://www.ics.uci.edu/~eppstein/161/960229.html
+*
+* Some ideas are (and a bit of code) are from from analyze.c, from GNU
+* diffutils-2.7, which can be found at:
+* ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
+*
+* Finally, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
+* are my own.
+*/
+class _WikiDiffEngine
+{
+ var $edits; // List of editing operation to convert XV to YV.
+ var $xv = array(), $yv = array();
+
+ function _WikiDiffEngine($from_lines, $to_lines)
+ {
+ $n_from = sizeof($from_lines);
+ $n_to = sizeof($to_lines);
+
+ $endskip = 0;
+ // Ignore trailing and leading matching lines.
+ while ($n_from > 0 && $n_to > 0)
+ {
+ if ($from_lines[$n_from - 1] != $to_lines[$n_to - 1])
+ {
+ break;
+ }
+
+ $n_from--;
+ $n_to--;
+ $endskip++;
+ }
+
+ for ($skip = 0; $skip < min($n_from, $n_to); $skip++)
+ {
+ if ($from_lines[$skip] != $to_lines[$skip])
+ {
+ break;
+ }
+ }
+ $n_from -= $skip;
+ $n_to -= $skip;
+
+ $xlines = array();
+ $ylines = array();
+
+ // Ignore lines which do not exist in both files.
+ for ($x = 0; $x < $n_from; $x++)
+ {
+ $xhash[$from_lines[$x + $skip]] = 1;
+ }
+
+ for ($y = 0; $y < $n_to; $y++)
+ {
+ $line = $to_lines[$y + $skip];
+ $ylines[] = $line;
+
+ if (($this->ychanged[$y] = empty($xhash[$line])))
+ {
+ continue;
+ }
+ $yhash[$line] = 1;
+ $this->yv[] = $line;
+ $this->yind[] = $y;
+ }
+
+ for ($x = 0; $x < $n_from; $x++)
+ {
+ $line = $from_lines[$x + $skip];
+ $xlines[] = $line;
+
+ if (($this->xchanged[$x] = empty($yhash[$line])))
+ {
+ continue; // fixme? what happens to yhash/xhash when
+ // there are two identical lines??
+ }
+ $this->xv[] = $line;
+ $this->xind[] = $x;
+ }
+
+ // Find the LCS.
+ $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv));
+
+ // Merge edits when possible
+ $this->_shift_boundaries($xlines, $this->xchanged, $this->ychanged);
+ $this->_shift_boundaries($ylines, $this->ychanged, $this->xchanged);
+
+ // Compute the edit operations.
+ $this->edits = array();
+
+ if ($skip)
+ {
+ $this->edits[] = $skip;
+ }
+
+ $x = 0;
+ $y = 0;
+
+ while ($x < $n_from || $y < $n_to)
+ {
+ // Skip matching "snake".
+ $x0 = $x;
+ $ncopy = 0;
+ while ($x < $n_from && $y < $n_to && !$this->xchanged[$x] && !$this->ychanged[$y])
+ {
+ ++$x;
+ ++$y;
+ ++$ncopy;
+ }
+
+ if ($x > $x0)
+ {
+ $this->edits[] = $x - $x0;
+ }
+
+ // Find deletes.
+ $x0 = $x;
+ $ndelete = 0;
+
+ while ($x < $n_from && $this->xchanged[$x])
+ {
+ ++$x;
+ ++$ndelete;
+ }
+
+ if ($x > $x0)
+ {
+ $this->edits[] = -($x - $x0);
+ }
+
+ // Find adds.
+ if (isset($this->ychanged[$y]) && $this->ychanged[$y])
+ {
+ $adds = array();
+ while ($y < $n_to && $this->ychanged[$y])
+ {
+ $adds[] = $ylines[$y++];
+ }
+ $this->edits[] = $adds;
+ }
+ }
+
+ if (!empty($endskip))
+ {
+ $this->edits[] = $endskip;
+ }
+ }
+
+ /* Divide the Largest Common Subsequence (LCS) of the sequences
+ * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
+ * sized segments.
+ *
+ * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an
+ * array of NCHUNKS+1 (X, Y) indexes giving the diving points between
+ * sub sequences. The first sub-sequence is contained in [X0, X1),
+ * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note
+ * that (X0, Y0) == (XOFF, YOFF) and
+ * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
+ *
+ * This function assumes that the first lines of the specified portions
+ * of the two files do not match, and likewise that the last lines do not
+ * match. The caller must trim matching lines from the beginning and end
+ * of the portions it is going to specify.
+ */
+ function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks)
+ {
+ $flip = false;
+
+ if ($xlim - $xoff > $ylim - $yoff)
+ {
+ // Things seems faster (I'm not sure I understand why)
+ // when the shortest sequence in X.
+ $flip = true;
+ list ($xoff, $xlim, $yoff, $ylim) = array( $yoff, $ylim, $xoff, $xlim);
+ }
+
+ if ($flip)
+ {
+ for ($i = $ylim - 1; $i >= $yoff; $i--)
+ {
+ $ymatches[$this->xv[$i]][] = $i;
+ }
+ }
+ else
+ {
+ for ($i = $ylim - 1; $i >= $yoff; $i--)
+ {
+ $ymatches[$this->yv[$i]][] = $i;
+ }
+ }
+
+ $this->lcs = 0;
+ $this->seq[0]= $yoff - 1;
+ $this->in_seq = array();
+ $ymids[0] = array();
+
+ $numer = $xlim - $xoff + $nchunks - 1;
+ $x = $xoff;
+
+ for ($chunk = 0; $chunk < $nchunks; $chunk++)
+ {
+ if ($chunk > 0)
+ {
+ for ($i = 0; $i <= $this->lcs; $i++)
+ {
+ $ymids[$i][$chunk-1] = $this->seq[$i];
+ }
+ }
+
+ $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
+
+ for ( ; $x < $x1; $x++)
+ {
+ $_index = $flip ? $this->yv[$x] : $this->xv[$x];
+ $matches = (isset($ymatches[$_index])) ? $ymatches[$_index] : array();
+
+ if (!$matches)
+ {
+ continue;
+ }
+ reset($matches);
+
+ while (list ($junk, $y) = each($matches))
+ {
+ if (!isset($this->in_seq[$y]) || !$this->in_seq[$y])
+ {
+ $k = $this->_lcs_pos($y);
+ //if (!$k) die('assertion "!$k" failed');
+ $ymids[$k] = $ymids[$k-1];
+ break;
+ }
+ }
+
+ while (list ($junk, $y) = each($matches))
+ {
+ if ($y > $this->seq[$k-1])
+ {
+ //if ($y >= $this->seq[$k]) die('assertion failed');
+ // Optimization: this is a common case:
+ // next match is just replacing previous match.
+ $this->in_seq[$this->seq[$k]] = false;
+ $this->seq[$k] = $y;
+ $this->in_seq[$y] = 1;
+ }
+ else if (!isset($this->in_seq[$y]) || !$this->in_seq[$y])
+ {
+ $k = $this->_lcs_pos($y);
+ //if (!$k) die('assertion "!$k" failed');
+ $ymids[$k] = $ymids[$k-1];
+ }
+ }
+ }
+ }
+
+ $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
+ $ymid = $ymids[$this->lcs];
+
+ for ($n = 0; $n < $nchunks - 1; $n++)
+ {
+ $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
+ $y1 = $ymid[$n] + 1;
+ $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
+ }
+ $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
+
+ return array($this->lcs, $seps);
+ }
+
+ function _lcs_pos ($ypos)
+ {
+ $end = $this->lcs;
+ if ($end == 0 || $ypos > $this->seq[$end])
+ {
+ $this->seq[++$this->lcs] = $ypos;
+ $this->in_seq[$ypos] = 1;
+ return $this->lcs;
+ }
+
+ $beg = 1;
+ while ($beg < $end)
+ {
+ $mid = (int)(($beg + $end) / 2);
+
+ if ($ypos > $this->seq[$mid])
+ {
+ $beg = $mid + 1;
+ }
+ else
+ {
+ $end = $mid;
+ }
+ }
+
+ //if ($ypos == $this->seq[$end]) die("assertion failure");
+
+ $this->in_seq[$this->seq[$end]] = false;
+ $this->seq[$end] = $ypos;
+ $this->in_seq[$ypos] = 1;
+ return $end;
+ }
+
+ /* Find LCS of two sequences.
+ *
+ * The results are recorded in the vectors $this->{x,y}changed[], by
+ * storing a 1 in the element for each line that is an insertion
+ * or deletion (ie. is not in the LCS).
+ *
+ * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
+ *
+ * Note that XLIM, YLIM are exclusive bounds.
+ * All line numbers are origin-0 and discarded lines are not counted.
+ */
+ function _compareseq ($xoff, $xlim, $yoff, $ylim)
+ {
+ // Slide down the bottom initial diagonal.
+ while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff])
+ {
+ ++$xoff;
+ ++$yoff;
+ }
+
+ // Slide up the top initial diagonal.
+ while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1])
+ {
+ --$xlim;
+ --$ylim;
+ }
+
+ if ($xoff == $xlim || $yoff == $ylim)
+ {
+ $lcs = 0;
+ }
+ else
+ {
+ // This is ad hoc but seems to work well.
+ //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
+ //$nchunks = max(2,min(8,(int)$nchunks));
+ $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
+ list ($lcs, $seps) = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
+ }
+
+ if ($lcs == 0)
+ {
+ // X and Y sequences have no common subsequence:
+ // mark all changed.
+ while ($yoff < $ylim)
+ {
+ $this->ychanged[$this->yind[$yoff++]] = 1;
+ }
+
+ while ($xoff < $xlim)
+ {
+ $this->xchanged[$this->xind[$xoff++]] = 1;
+ }
+ }
+ else
+ {
+ // Use the partitions to split this problem into subproblems.
+ reset($seps);
+
+ $pt1 = $seps[0];
+
+ while ($pt2 = next($seps))
+ {
+ $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
+ $pt1 = $pt2;
+ }
+ }
+ }
+
+ /* Adjust inserts/deletes of identical lines to join changes
+ * as much as possible.
+ *
+ * We do something when a run of changed lines include a
+ * line at one end and have an excluded, identical line at the other.
+ * We are free to choose which identical line is included.
+ * `compareseq' usually chooses the one at the beginning,
+ * but usually it is cleaner to consider the following identical line
+ * to be the "change".
+ *
+ * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
+ */
+ function _shift_boundaries ($lines, &$changed, $other_changed)
+ {
+ $i = 0;
+ $j = 0;
+ $len = sizeof($lines);
+
+ while (1)
+ {
+ /*
+ * Scan forwards to find beginning of another run of changes.
+ * Also keep track of the corresponding point in the other file.
+ */
+
+ while ($i < $len && $changed[$i] == 0)
+ {
+ while ($other_changed[$j++])
+ {
+ continue;
+ }
+ $i++;
+ }
+
+ if ($i == $len)
+ {
+ break;
+ }
+
+ $start = $i;
+
+ // Find the end of this run of changes.
+ while (isset($changed[++$i]))
+ {
+ continue;
+ }
+
+ while (isset($other_changed[$j]) && $other_changed[$j])
+ {
+ $j++;
+ }
+
+ do
+ {
+ /*
+ * Record the length of this run of changes, so that
+ * we can later determine whether the run has grown.
+ */
+ $runlength = $i - $start;
+
+ /*
+ * Move the changed region back, so long as the
+ * previous unchanged line matches the last changed one.
+ * This merges with previous changed regions.
+ */
+ while ($start && $lines[$start - 1] == $lines[$i - 1])
+ {
+ $changed[--$start] = 1;
+ $changed[--$i] = false;
+
+ while ($changed[$start - 1])
+ {
+ $start--;
+ }
+
+ while ($other_changed[--$j])
+ {
+ continue;
+ }
+ }
+
+ /*
+ * Set CORRESPONDING to the end of the changed run, at the last
+ * point where it corresponds to a changed run in the other file.
+ * CORRESPONDING == LEN means no such point has been found.
+ */
+ $corresponding = empty($other_changed[$j - 1]) ? $len : $i;
+
+ /*
+ * Move the changed region forward, so long as the
+ * first changed line matches the following unchanged one.
+ * This merges with following changed regions.
+ * Do this second, so that if there are no merges,
+ * the changed region is moved forward as far as possible.
+ */
+ while ($i != $len && $lines[$start] == $lines[$i])
+ {
+ $changed[$start++] = false;
+ $changed[$i++] = 1;
+
+ while ($changed[$i])
+ {
+ $i++;
+ }
+
+ while ($other_changed[++$j])
+ {
+ $corresponding = $i;
+ }
+ }
+ } while ($runlength != $i - $start);
+
+ /*
+ * If possible, move the fully-merged run of changes
+ * back to a corresponding run in the other file.
+ */
+ while ($corresponding < $i)
+ {
+ $changed[--$start] = 1;
+ $changed[--$i] = 0;
+
+ while ($other_changed[--$j])
+ {
+ continue;
+ }
+ }
+ }
+ }
+}
+
+/**
+* Class representing a diff between two files.
+*/
+class Diff
+{
+ var $edits;
+
+ /**
+ * Compute diff between files (or deserialize serialized WikiDiff.)
+ */
+ function Diff($from_lines = false, $to_lines = false)
+ {
+ if ($from_lines && $to_lines)
+ {
+ $compute = new _WikiDiffEngine($from_lines, $to_lines);
+ $this->edits = $compute->edits;
+ }
+ else if ($from_lines)
+ {
+ // $from_lines is not really from_lines, but rather
+ // a serialized Diff.
+ $this->edits = unserialize($from_lines);
+ }
+ else
+ {
+ $this->edits = array();
+ }
+ }
+
+ /**
+ * Compute reversed Diff.
+ *
+ * SYNOPSIS:
+ *
+ * $diff = new Diff($lines1, $lines2);
+ * $rev = $diff->reverse($lines1);
+ *
+ * // reconstruct $lines1 from $lines2:
+ * $out = $rev->apply($lines2);
+ */
+ function reverse ($from_lines)
+ {
+ $x = 0;
+ $rev = new Diff;
+
+ for (reset($this->edits); $edit = current($this->edits); next($this->edits))
+ {
+ if (is_array($edit))
+ { // Was an add, turn it into a delete.
+ $nadd = sizeof($edit);
+ if ($nadd == 0)
+ {
+ die("assertion error");
+ }
+ $edit = -$nadd;
+ }
+ else if ($edit > 0)
+ {
+ // Was a copy --- just pass it through. }
+ $x += $edit;
+ }
+ else if ($edit < 0)
+ { // Was a delete, turn it into an add.
+ $ndelete = -$edit;
+ $edit = array();
+
+ while ($ndelete-- > 0)
+ {
+ $edit[] = $from_lines[$x++];
+ }
+ }
+ else
+ {
+ die("assertion error");
+ }
+
+ $rev->edits[] = $edit;
+ }
+
+ return $rev;
+ }
+
+ /**
+ * Compose (concatenate) Diffs.
+ *
+ * SYNOPSIS:
+ *
+ * $diff1 = new Diff($lines1, $lines2);
+ * $diff2 = new Diff($lines2, $lines3);
+ * $comp = $diff1->compose($diff2);
+ *
+ * // reconstruct $lines3 from $lines1:
+ * $out = $comp->apply($lines1);
+ */
+ function compose ($that)
+ {
+ reset($this->edits);
+ reset($that->edits);
+
+ $comp = new Diff;
+ $left = current($this->edits);
+ $right = current($that->edits);
+
+ while ($left || $right)
+ {
+ if (!is_array($left) && $left < 0)
+ { // Left op is a delete.
+ $newop = $left;
+ $left = next($this->edits);
+ }
+ else if (is_array($right))
+ { // Right op is an add.
+ $newop = $right;
+ $right = next($that->edits);
+ }
+ else if (!$left || !$right)
+ {
+ die ("assertion error");
+ }
+ else if (!is_array($left) && $left > 0)
+ { // Left op is a copy.
+ if ($left <= abs($right))
+ {
+ $newop = $right > 0 ? $left : -$left;
+ $right -= $newop;
+
+ if ($right == 0)
+ {
+ $right = next($that->edits);
+ }
+ $left = next($this->edits);
+ }
+ else
+ {
+ $newop = $right;
+ $left -= abs($right);
+ $right = next($that->edits);
+ }
+ }
+ else
+ { // Left op is an add.
+ if (!is_array($left))
+ {
+ die('assertion error');
+ }
+ $nleft = sizeof($left);
+
+ if ($nleft <= abs($right))
+ {
+ if ($right > 0)
+ { // Right op is copy
+ $newop = $left;
+ $right -= $nleft;
+ }
+ else // Right op is delete
+ {
+ $newop = false;
+ $right += $nleft;
+ }
+
+ if ($right == 0)
+ {
+ $right = next($that->edits);
+ }
+ $left = next($this->edits);
+ }
+ else
+ {
+ unset($newop);
+
+ if ($right > 0)
+ {
+ for ($i = 0; $i < $right; $i++)
+ {
+ $newop[] = $left[$i];
+ }
+ }
+
+ $tmp = array();
+ for ($i = abs($right); $i < $nleft; $i++)
+ {
+ $tmp[] = $left[$i];
+ }
+ $left = $tmp;
+ $right = next($that->edits);
+ }
+ }
+
+ if (!$op)
+ {
+ $op = $newop;
+ continue;
+ }
+
+ if (!$newop)
+ {
+ continue;
+ }
+
+ if (is_array($op) && is_array($newop))
+ {
+ // Both $op and $newop are adds.
+ for ($i = 0; $i < sizeof($newop); $i++)
+ {
+ $op[] = $newop[$i];
+ }
+ }
+ else if (($op > 0 && $newop > 0) || ($op < 0 && $newop < 0))
+ { // $op and $newop are both either deletes or copies.
+ $op += $newop;
+ }
+ else
+ {
+ $comp->edits[] = $op;
+ $op = $newop;
+ }
+ }
+
+ if ($op)
+ {
+ $comp->edits[] = $op;
+ }
+
+ return $comp;
+ }
+
+ /* Debugging only:
+ function _dump ()
+ {
+ echo "<ol>";
+ for (reset($this->edits);
+ {
+ $edit = current($this->edits);
+ }
+ next($this->edits))
+ {
+ echo "<li>";
+ if ($edit > 0)
+ echo "Copy $edit";
+ else if ($edit < 0)
+ echo "Delete " . -$edit;
+ else if (is_array($edit))
+ {
+ echo "Add " . sizeof($edit) . "<ul>";
+ for ($i = 0; $i < sizeof($edit); $i++)
+ echo "<li>" . htmlspecialchars($edit[$i]);
+ echo "</ul>";
+ }
+ else
+ die("assertion error");
+ }
+ echo "</ol>";
+ }
+ */
+
+ /**
+ * Apply a Diff to a set of lines.
+ *
+ * SYNOPSIS:
+ *
+ * $diff = new Diff($lines1, $lines2);
+ *
+ * // reconstruct $lines2 from $lines1:
+ * $out = $diff->apply($lines1);
+ */
+ function apply ($from_lines)
+ {
+ $x = 0;
+ $xlim = sizeof($from_lines);
+
+ for (reset($this->edits); $edit = current($this->edits); next($this->edits))
+ {
+ if (is_array($edit))
+ {
+ reset($edit);
+ while (list ($junk, $line) = each($edit))
+ {
+ $output[] = $line;
+ }
+ }
+ else if ($edit > 0)
+ {
+ while ($edit--)
+ {
+ $output[] = $from_lines[$x++];
+ }
+ }
+ else
+ {
+ $x += -$edit;
+ }
+ }
+
+ if ($x != $xlim)
+ {
+ die(sprintf("Diff::apply: line count mismatch: %s != %s", $x, $xlim));
+ }
+
+ return $output;
+ }
+
+ /**
+ * Serialize a Diff.
+ *
+ * SYNOPSIS:
+ *
+ * $diff = new Diff($lines1, $lines2);
+ * $string = $diff->serialize;
+ *
+ * // recover Diff from serialized version:
+ * $diff2 = new Diff($string);
+ */
+ function serialize ()
+ {
+ return serialize($this->edits);
+ }
+
+ /**
+ * Return true if two files were equal.
+ */
+ function isEmpty()
+ {
+ if (sizeof($this->edits) > 1)
+ {
+ return false;
+ }
+
+ if (sizeof($this->edits) == 0)
+ {
+ return true;
+ }
+
+ // Test for: only edit is a copy.
+ return !is_array($this->edits[0]) && $this->edits[0] > 0;
+ }
+
+ /**
+ * Compute the length of the Longest Common Subsequence (LCS).
+ *
+ * This is mostly for diagnostic purposed.
+ */
+ function lcs()
+ {
+ $lcs = 0;
+ for (reset($this->edits); $edit = current($this->edits); next($this->edits))
+ {
+ if (!is_array($edit) && $edit > 0)
+ {
+ $lcs += $edit;
+ }
+ }
+
+ return $lcs;
+ }
+
+ /**
+ * Check a Diff for validity.
+ *
+ * This is here only for debugging purposes.
+ */
+ function _check ($from_lines, $to_lines)
+ {
+ $test = $this->apply($from_lines);
+ if (serialize($test) != serialize($to_lines))
+ {
+ die("Diff::_check: failed");
+ }
+
+ reset($this->edits);
+ $prev = current($this->edits);
+ $prevtype = is_array($prev) ? 'a' : ($prev > 0 ? 'c' : 'd');
+
+ while ($edit = next($this->edits))
+ {
+ $type = is_array($edit) ? 'a' : ($edit > 0 ? 'c' : 'd');
+ if ($prevtype == $type)
+ {
+ die("Diff::_check: edit sequence is non-optimal");
+ }
+ $prevtype = $type;
+ }
+ $lcs = $this->lcs();
+ echo "<strong>Diff Okay: LCS = $lcs</strong>\n";
+ }
+}
+
+/**
+* A class to format a Diff as HTML.
+*
+* Usage:
+*
+* $diff = new Diff($lines1, $lines2); // compute diff.
+*
+* $fmt = new DiffFormatter;
+* echo $fmt->format($diff, $lines1); // Output HTMLified standard diff.
+*
+* or to output reverse diff (diff's that would take $lines2 to $lines1):
+*
+* $fmt = new DiffFormatter(true);
+* echo $fmt->format($diff, $lines1);
+*/
+class DiffFormatter
+{
+ var $context_lines;
+ var $do_reverse_diff;
+ var $context_prefix, $deletes_prefix, $adds_prefix;
+
+ function DiffFormatter ($reverse = false)
+ {
+ $this->do_reverse_diff = $reverse;
+ $this->context_lines = 0;
+ $this->context_prefix = '&nbsp;&nbsp;';
+ $this->deletes_prefix = '&lt;&nbsp;';
+ $this->adds_prefix = '&gt;&nbsp;';
+ }
+
+ function format ($diff, $from_lines)
+ {
+ $html = '<table width="100%" bgcolor="black" cellspacing=2 cellpadding=2 border=0>' . "\n";
+ $html .= $this->_format($diff->edits, $from_lines);
+ $html .= "</table>\n";
+
+ return $html;
+ }
+
+ function _format ($edits, $from_lines)
+ {
+ $html = '';
+ $x = 0; $y = 0;
+ $xlim = sizeof($from_lines);
+
+ reset($edits);
+ while ($edit = current($edits))
+ {
+ if (!is_array($edit) && $edit >= 0)
+ { // Edit op is a copy.
+ $ncopy = $edit;
+ }
+ else
+ {
+ $ncopy = 0;
+
+ if (empty($hunk))
+ {
+ // Start of an output hunk.
+ $xoff = max(0, $x - $this->context_lines);
+ $yoff = $xoff + $y - $x;
+
+ if ($xoff < $x)
+ {
+ // Get leading context.
+ $context = array();
+
+ for ($i = $xoff; $i < $x; $i++)
+ {
+ $context[] = $from_lines[$i];
+ }
+ $hunk['c'] = $context;
+ }
+ }
+
+ if (is_array($edit))
+ {
+ // Edit op is an add.
+ $y += sizeof($edit);
+ $hunk[$this->do_reverse_diff ? 'd' : 'a'] = $edit;
+ }
+ else
+ {
+ // Edit op is a delete
+ $deletes = array();
+
+ while ($edit++ < 0)
+ {
+ $deletes[] = $from_lines[$x++];
+ }
+
+ $hunk[$this->do_reverse_diff ? 'a' : 'd'] = $deletes;
+ }
+ }
+
+ $next = next($edits);
+
+ if (!empty($hunk))
+ {
+ // Originally $ncopy > 2 * $this->context_lines, but we need to split as early as we can for creating MOD Text Templates. ;)
+ if (!$next || $ncopy > $this->context_lines)
+ {
+ // End of an output hunk.
+ $hunks[] = $hunk;
+ unset($hunk);
+
+ $xend = min($x + $this->context_lines, $xlim);
+
+ if ($x < $xend)
+ {
+ // Get trailing context.
+ $context = array();
+ for ($i = $x; $i < $xend; $i++)
+ {
+ $context[] = $from_lines[$i];
+ }
+ $hunks[] = array('c' => $context);
+ }
+
+ $xlen = $xend - $xoff;
+ $ylen = $xend + $y - $x - $yoff;
+ $xbeg = $xlen ? $xoff + 1 : $xoff;
+ $ybeg = $ylen ? $yoff + 1 : $yoff;
+
+ if ($this->do_reverse_diff)
+ {
+ list ($xbeg, $xlen, $ybeg, $ylen) = array($ybeg, $ylen, $xbeg, $xlen);
+ }
+
+ $html .= $this->_emit_diff($xbeg, $xlen, $ybeg, $ylen, $hunks);
+ unset($hunks);
+ }
+ else if ($ncopy)
+ {
+ $hunks[] = $hunk;
+
+ // Copy context.
+ $context = array();
+ for ($i = $x; $i < $x + $ncopy; $i++)
+ {
+ $context[] = $from_lines[$i];
+ }
+ $hunk = array('c' => $context);
+ }
+ }
+
+ $x += $ncopy;
+ $y += $ncopy;
+ }
+
+ return $html;
+ }
+
+ function _emit_lines($lines, $prefix, $color)
+ {
+ $html = '';
+ reset($lines);
+ while (list ($junk, $line) = each($lines))
+ {
+ $html .= "<tr bgcolor=\"$color\"><td><tt>$prefix</tt>";
+ $html .= "<tt>" . htmlspecialchars($line) . "</tt></td></tr>\n";
+ }
+ return $html;
+ }
+
+ function _emit_diff ($xbeg,$xlen,$ybeg,$ylen,$hunks)
+ {
+ // Save hunk...
+ $this->diff_hunks[] = $hunks;
+
+ $html = '<tr><td><table width="100%" bgcolor="white" cellspacing="0" border="0" cellpadding="4">
+ <tr bgcolor="#cccccc"><td><tt>' .
+ $this->_diff_header($xbeg, $xlen, $ybeg, $ylen) . '
+ </tt></td></tr>\n<tr><td>
+ <table width="100%" cellspacing="0" border="0" cellpadding="2">
+ ';
+
+ $prefix = array('c' => $this->context_prefix, 'a' => $this->adds_prefix, 'd' => $this->deletes_prefix);
+ $color = array('c' => '#ffffff', 'a' => '#ffcccc', 'd' => '#ccffcc');
+
+ for (reset($hunks); $hunk = current($hunks); next($hunks))
+ {
+ if (!empty($hunk['c']))
+ {
+ $html .= $this->_emit_lines($hunk['c'], $this->context_prefix, '#ffffff');
+ }
+
+ if (!empty($hunk['d']))
+ {
+ $html .= $this->_emit_lines($hunk['d'], $this->deletes_prefix, '#ccffcc');
+ }
+
+ if (!empty($hunk['a']))
+ {
+ $html .= $this->_emit_lines($hunk['a'], $this->adds_prefix, '#ffcccc');
+ }
+ }
+
+ $html .= "</table></td></tr></table></td></tr>\n";
+ return $html;
+ }
+
+ function _diff_header ($xbeg,$xlen,$ybeg,$ylen)
+ {
+ $what = $xlen ? ($ylen ? 'c' : 'd') : 'a';
+ $xlen = $xlen > 1 ? "," . ($xbeg + $xlen - 1) : '';
+ $ylen = $ylen > 1 ? "," . ($ybeg + $ylen - 1) : '';
+
+ return "$xbeg$xlen$what$ybeg$ylen";
+ }
+}
+
+/**
+* A class to format a Diff as a pretty HTML unified diff.
+*
+* Usage:
+*
+* $diff = new Diff($lines1, $lines2); // compute diff.
+*
+* $fmt = new UnifiedDiffFormatter;
+* echo $fmt->format($diff, $lines1); // Output HTMLified unified diff.
+*/
+class UnifiedDiffFormatter extends DiffFormatter
+{
+ function UnifiedDiffFormatter ($reverse = false, $context_lines = 3)
+ {
+ $this->do_reverse_diff = $reverse;
+ $this->context_lines = $context_lines;
+ $this->context_prefix = '&nbsp;';
+ $this->deletes_prefix = '-';
+ $this->adds_prefix = '+';
+ }
+
+ function _diff_header ($xbeg,$xlen,$ybeg,$ylen)
+ {
+ $xlen = $xlen == 1 ? '' : ",$xlen";
+ $ylen = $ylen == 1 ? '' : ",$ylen";
+
+ return "@@ -$xbeg$xlen +$ybeg$ylen @@";
+ }
+}
+
+/**
+* A class to format a Diff as MOD Template instuctions.
+*
+* Usage:
+*
+* $diff = new Diff($lines1, $lines2); // compute diff.
+*
+* $fmt = new BBCodeDiffFormatter;
+* echo $fmt->format($diff, $lines1); // Output MOD Actions.
+*/
+class BBCodeDiffFormatter extends DiffFormatter
+{
+ function BBCodeDiffFormatter ($reverse = false, $context_lines = 3, $debug = false)
+ {
+ $this->do_reverse_diff = $reverse;
+ $this->context_lines = $context_lines;
+ $this->context_prefix = '&nbsp;';
+ $this->deletes_prefix = '-';
+ $this->adds_prefix = '+';
+ $this->debug = $debug;
+ }
+
+ function format ($diff, $from_lines)
+ {
+ $html = $this->_format($diff->edits, $from_lines);
+
+ return $html;
+ }
+
+ function skip_lines(&$order_array, &$ordering)
+ {
+ if (sizeof($order_array['find_c']))
+ {
+ $text = implode('', $order_array['find_c']);
+ if ($text === "\n" || $text === "\t" || $text === '')
+ {
+ return true;
+ }
+ }
+
+ if (isset($order_array['add']) && sizeof($order_array['add']))
+ {
+ $text = implode('', $order_array['add']);
+ if ($text === "\n" || $text === "\t" || $text === '')
+ {
+ return true;
+ }
+ }
+
+ if (isset($order_array['replace']) && sizeof($order_array['replace']))
+ {
+ $text = implode('', $order_array['replace']);
+ if ($text === "\n" || $text === "\t" || $text === '')
+ {
+ return true;
+ }
+ }
+ }
+
+ function _emit_lines_bb($ybeg, &$ordering)
+ {
+ $html = '';
+
+ // Now adjust for bbcode display...
+ foreach ($ordering as $order_array)
+ {
+ // Skip useless empty lines...
+ if ($this->skip_lines($order_array, $ordering))
+ {
+ continue;
+ }
+
+ // Only include double-finds if the last find has very few code location indications...
+ if (isset($order_array['first_find_c']) && sizeof($order_array['first_find_c']))
+ {
+ $text = implode('', $order_array['find_c']);
+ if ($text === "\n" || $text === "\t" || $text === '')
+ {
+ continue;
+ }
+
+ if (strlen(implode('', $order_array['find_c'])) < 50 && is_array($order_array['first_find_c'][0]))
+ {
+ $html .= "#\n#-----[ FIND ]---------------------------------------------\n# Around Line {$ybeg}\n";
+ $html .= implode("", $order_array['first_find_c'][0]);
+ $html .= "\n";
+ $ybeg += sizeof($order_array['first_find_c'][0]);
+ }
+ }
+
+ if (sizeof($order_array['find_c']))
+ {
+ $html .= "#\n#-----[ FIND ]---------------------------------------------\n# Around Line {$ybeg}\n";
+ $html .= implode("", $order_array['find_c']);
+ $html .= "\n";
+ }
+
+ if (isset($order_array['replace']))
+ {
+ $html .= "#\n#-----[ REPLACE WITH ]---------------------------------------------\n#\n";
+ $html .= implode("", $order_array['replace']);
+ $html .= "\n";
+ }
+
+ if (isset($order_array['add']))
+ {
+ $html .= "#\n#-----[ AFTER, ADD ]---------------------------------------------\n#\n";
+ $html .= implode("", $order_array['add']);
+ $html .= "\n";
+ }
+
+ // There is no DELETE. :o
+ // Let's try to adjust it then...
+ if (isset($order_array['delete']))
+ {
+ $text = implode('', $order_array['delete']);
+ if ($text === "\n" || $text === "\t" || $text === '')
+ {
+ continue;
+ }
+
+ $ybeg += sizeof($order_array['find_c']);
+
+ $html .= "#\n#-----[ FIND ]---------------------------------------------\n# Around Line {$ybeg}\n";
+ $html .= implode("", $order_array['delete']);
+ $html .= "\n";
+
+ $html .= "#\n#-----[ REPLACE WITH ]---------------------------------------------\n# Just remove/delete the lines (replacing with an empty line)\n";
+ $html .= "\n";
+ $html .= "\n";
+ }
+ }
+
+ return $html;
+ }
+
+ function format_open($filename)
+ {
+ $html = '';
+ $html .= "#\n#-----[ OPEN ]--------------------------------------------- \n#\n{$filename}\n\n";
+
+ return $html;
+ }
+
+ function format_close($filename)
+ {
+ return '';
+ }
+
+ function _emit_diff ($xbeg, $xlen, $ybeg, $ylen, $hunks)
+ {
+
+ // Go through the hunks to determine which method we are using (AFTER, ADD; REPLACE WITH or DELETE)
+
+ // Remove the header...
+ if (sizeof($hunks) <= 2 && !isset($hunks[1]['a']) && !isset($hunks[1]['d']))
+ {
+ $reorder = false;
+ $orig_hunks = $hunks;
+
+ foreach ($hunks as $key => $hunk)
+ {
+ if (isset($hunk['a']) && isset($hunk['d']))
+ {
+ /* if (sizeof($hunk['a']) == 1 && sizeof($hunk['d']) == 1)
+ {
+ if (preg_match('/\* @version \$Id:.+\$$/', $hunk['a'][0]) && preg_match('/\* @version \$Id:.+\$$/', $hunk['d'][0]))
+ {
+ // Only remove this sole hunk...
+ unset($hunks[$key]);
+ $reorder = true;
+ continue;
+ }
+ }*/
+
+ // Compare the add and replace one...
+ $string_1 = rtrim(trim(implode('', $hunk['a'])));
+ $string_2 = rtrim(trim(implode('', $hunk['d'])));
+
+ if (strcmp($string_1, $string_2) === 0)
+ {
+ // Only remove this sole hunk...
+ unset($hunks[$key]);
+ $reorder = true;
+ continue;
+ }
+ }
+ }
+
+ if ($reorder)
+ {
+ // Now check if we have no more 'a' and 'd's
+ $hunks = array_merge($hunks, array());
+ }
+ }
+ else
+ {
+ $reorder = false;
+ $orig_hunks = $hunks;
+
+ foreach ($hunks as $key => $hunk)
+ {
+ if (isset($hunk['a']) && isset($hunk['d']))
+ {
+ /* if (sizeof($hunk['a']) == 1 && sizeof($hunk['d']) == 1)
+ {
+ if (preg_match('/\* @version \$Id:.+\$$/', $hunk['a'][0]) && preg_match('/\* @version \$Id:.+\$$/', $hunk['d'][0]))
+ {
+ // Only remove this sole hunk...
+ unset($hunks[$key]);
+ $reorder = true;
+ continue;
+ }
+ }*/
+
+ // Compare the add and replace one...
+ $string_1 = rtrim(trim(implode('', $hunk['a'])));
+ $string_2 = rtrim(trim(implode('', $hunk['d'])));
+
+ if (strcmp($string_1, $string_2) === 0)
+ {
+ // Only remove this sole hunk...
+ unset($hunks[$key]);
+ $reorder = true;
+ continue;
+ }
+ }
+ }
+
+ if ($reorder)
+ {
+ $hunks = array_merge($hunks, array());
+
+ if (sizeof($hunks) == 1 && sizeof($hunks[0]) == 1 && isset($hunks[0]['c']))
+ {
+ return;
+ }
+ else
+ {
+ $hunks = $orig_hunks;
+ }
+ }
+ }
+
+ if (sizeof($hunks) == 1 && sizeof($hunks[0]) == 1 && isset($hunks[0]['c']))
+ {
+ return;
+ }
+
+ $replace = false;
+ foreach ($hunks as $key => $hunk)
+ {
+ if (isset($hunk['d']) && isset($hunk['a']))
+ {
+ $replace = true;
+ break;
+ }
+ }
+
+ $ordering = array();
+ $cur_pos = 0;
+
+ // Replace-block
+ if ($replace)
+ {
+ foreach ($hunks as $hunk)
+ {
+ if (!isset($hunk['a']) && !isset($hunk['d']))
+ {
+ continue;
+ }
+
+ if (!empty($hunk['c']))
+ {
+ if (!isset($ordering[$cur_pos]['find_c']))
+ {
+ $ordering[$cur_pos]['find_c'] = $hunk['c'];
+ }
+ else
+ {
+ $ordering[$cur_pos]['end_c'] = $hunk['c'];
+ }
+ }
+
+ // Make sure we begin fresh...
+ if (!isset($ordering[$cur_pos]['replace']))
+ {
+ $ordering[$cur_pos]['first_find_c'][] = $ordering[$cur_pos]['find_c'];
+ $ordering[$cur_pos]['find_c'] = array();
+ $ordering[$cur_pos]['replace'] = array();
+ }
+
+ // Add the middle part if one exist...
+ if (isset($ordering[$cur_pos]['end_c']))
+ {
+ $ordering[$cur_pos]['find_c'] = array_merge($ordering[$cur_pos]['find_c'], $ordering[$cur_pos]['end_c']);
+ $ordering[$cur_pos]['replace'] = array_merge($ordering[$cur_pos]['replace'], $ordering[$cur_pos]['end_c']);
+ unset($ordering[$cur_pos]['end_c']);
+ }
+
+ if (isset($hunk['d']))
+ {
+ $ordering[$cur_pos]['find_c'] = array_merge($ordering[$cur_pos]['find_c'], $hunk['d']);
+ }
+
+ if (isset($hunk['a']))
+ {
+ $ordering[$cur_pos]['replace'] = array_merge($ordering[$cur_pos]['replace'], $hunk['a']);
+ }
+ }
+ }
+ else
+ {
+ foreach ($hunks as $hunk)
+ {
+ if (!empty($hunk['c']))
+ {
+ if (!isset($ordering[$cur_pos]['find_c']))
+ {
+ $ordering[$cur_pos]['find_c'] = $hunk['c'];
+ }
+ else
+ {
+ $ordering[$cur_pos]['end_c'] = $hunk['c'];
+ }
+ }
+
+ if (!empty($hunk['a']))
+ {
+ if (isset($ordering[$cur_pos]['delete']))
+ {
+ // If ordering is set with an delete entry, we will actually begin a new ordering array (to seperate delete from add)
+ $cur_pos++;
+ $ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c'];
+ $ordering[$cur_pos]['add'] = $hunk['a'];
+ }
+ else
+ {
+ if (isset($ordering[$cur_pos]['add']))
+ {
+ // Now, we really need to be quite careful here...
+ if (isset($ordering[$cur_pos]['end_c']) && isset($hunk['c']) && isset($hunk['a']) && sizeof($hunk) == 2)
+ {
+ // There is a new find/add entry we did not catch... let's try to add a new entry then... but first check the hunk[a] contents...
+ $text = trim(implode("\n", $hunk['c']));
+ if ($text == "\n" || !$text)
+ {
+ $ordering[$cur_pos]['add'] = array_merge($ordering[$cur_pos]['add'], array("\n"), $hunk['a']);
+ }
+ else if (sizeof($hunk['c']) > 2 || strlen(implode('', $hunk['c'])) > 20)
+ {
+ $cur_pos++;
+ $ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c'];
+ $ordering[$cur_pos]['add'] = $hunk['a'];
+ }
+ else
+ {
+ $cur_pos++;
+ $ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c'];
+ $ordering[$cur_pos]['add'] = $hunk['a'];
+/* echo 'FIND STATEMENT TOO TINY';
+ echo ";".rawurlencode($text).";";
+ var_dump($hunk);
+ exit;*/
+ }
+ }
+ else
+ {
+ echo 'UNCATCHED ENTRY';
+ var_dump($hunks);
+ exit;
+ }
+ }
+ else
+ {
+ $ordering[$cur_pos]['add'] = $hunk['a'];
+ }
+ }
+ }
+ else if (!empty($hunk['d']))
+ {
+ if (isset($ordering[$cur_pos]['add']))
+ {
+ // If ordering is set with an add entry, we will actually begin a new ordering array (to seperate delete from add)
+ $cur_pos++;
+ $ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c'];
+ $ordering[$cur_pos]['delete'] = $hunk['d'];
+ }
+ else
+ {
+ $ordering[$cur_pos]['delete'] = $hunk['d'];
+ }
+ }
+ }
+ }
+
+ $html = '';
+
+ return $this->_emit_lines_bb($ybeg, $ordering);
+ }
+
+ function _diff_header($xbeg, $xlen, $ybeg, $ylen)
+ {
+ }
+}
+
+
+/**
+* A class to format a Diff as MOD Template instuctions.
+*
+* Usage:
+*
+* $diff = new Diff($lines1, $lines2); // compute diff.
+*
+* $fmt = new BBCodeDiffFormatter;
+* echo $fmt->format($diff, $lines1); // Output MOD Actions.
+*/
+class MODXDiffFormatter extends BBCodeDiffFormatter
+{
+ function MODXDiffFormatter ($reverse = false, $context_lines = 3, $debug = false)
+ {
+ $this->do_reverse_diff = $reverse;
+ $this->context_lines = $context_lines;
+ $this->context_prefix = '&nbsp;';
+ $this->deletes_prefix = '-';
+ $this->adds_prefix = '+';
+ $this->debug = $debug;
+ }
+
+ function _emit_lines_bb($ybeg, &$ordering)
+ {
+ $html = '';
+
+ // Now adjust for bbcode display...
+ foreach ($ordering as $order_array)
+ {
+ // Skip useless empty lines...
+ if ($this->skip_lines($order_array, $ordering))
+ {
+ continue;
+ }
+
+ // Only include double-finds if the last find has very few code location indications...
+ if (sizeof($order_array['first_find_c']))
+ {
+ $text = implode('', $order_array['find_c']);
+ if ($text === "\n" || $text === "\t" || $text === '')
+ {
+ continue;
+ }
+
+ if (strlen(implode('', $order_array['find_c'])) < 50)
+ {
+ if (substr($html, -8) !== '</find>' . "\n")
+ {
+ $html .= ' <edit>' . "\n";
+ }
+
+ $html .= ' <comment lang="en">Around Line ' . $ybeg . '</comment>' . "\n";
+ $html .= ' <find>' . htmlspecialchars(implode('', $order_array['first_find_c'][0])) . '</find>' . "\n";
+ $ybeg += sizeof($order_array['first_find_c'][0]);
+ }
+ }
+
+ if (sizeof($order_array['find_c']))
+ {
+ if (substr($html, -8) !== '</find>' . "\n")
+ {
+ $html .= ' <edit>' . "\n";
+ }
+
+// $html .= ' <edit>' . "\n";
+ $html .= ' <comment lang="en">Around Line ' . $ybeg . '</comment>' . "\n";
+ $html .= ' <find>' . htmlspecialchars(implode('', $order_array['find_c'])) . '</find>' . "\n";
+ }
+
+ if (isset($order_array['replace']))
+ {
+ $html .= ' <action type="replace-with">' . htmlspecialchars(implode('', $order_array['replace'])) . '</action>' . "\n";
+ $html .= ' </edit>' . "\n";
+ }
+
+ if (isset($order_array['add']))
+ {
+ $html .= ' <action type="after-add">' . htmlspecialchars(implode('', $order_array['add'])) . '</action>' . "\n";
+ $html .= ' </edit>' . "\n";
+ }
+
+ // There is no DELETE. :o
+ // Let's try to adjust it then...
+ if (isset($order_array['delete']))
+ {
+ $text = implode('', $order_array['delete']);
+ if ($text === "\n" || $text === "\t" || $text === '')
+ {
+ continue;
+ }
+
+ $ybeg += sizeof($order_array['find_c']);
+
+ if (substr($html, -8) !== '</find>' . "\n")
+ {
+ $html .= ' <edit>' . "\n";
+ }
+ $html .= ' <comment lang="en">Around Line ' . $ybeg . ' / Just remove/delete the lines (replacing with an empty line)</comment>' . "\n";
+ $html .= ' <find>' . htmlspecialchars(implode('', $order_array['delete'])) . '</find>' . "\n";
+ $html .= ' <action type="replace-with"></action>' . "\n";
+ $html .= ' </edit>';
+ }
+ }
+
+ return $html;
+ }
+
+ function format_open($filename)
+ {
+ return '<open src="' . $filename . '">' . "\n";
+ }
+
+ function format_close($filename)
+ {
+ return '</open>' . "\n";
+ }
+
+ function _diff_header($xbeg, $xlen, $ybeg, $ylen)
+ {
+ }
+}
+
+?> \ No newline at end of file