﻿XS.pm                                                                                               0000644                 00000006752 00000000000 0005407 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       package Algorithm::Diff::XS;
use 5.006;
use strict;
use warnings;
use vars '$VERSION';
use Algorithm::Diff;

BEGIN {
    $VERSION = '0.04';
    require XSLoader;
    XSLoader::load( __PACKAGE__, $VERSION );

    my $code = do {
        open my $fh, '<', $INC{'Algorithm/Diff.pm'}
          or die "Cannot read $INC{'Algorithm/Diff.pm'}: $!";
        local $/;
        <$fh>;
    };

    {
        no warnings;
        local $@;
        $code =~ s/Algorithm::Diff/Algorithm::Diff::XS/g;
        $code =~ s/sub LCSidx/sub LCSidx_old/g;
        $code = "#line 1 " . __FILE__ . "\n$code";
        eval $code;
        die $@ if $@;
    }

    no warnings 'redefine';

    sub LCSidx {
        my $lcs = Algorithm::Diff::XS->_CREATE_;
        my ( @l, @r );
        for my $chunk ( $lcs->_LCS_(@_) ) {
            push @l, $chunk->[0];
            push @r, $chunk->[1];
        }
        return ( \@l, \@r );
    }
}

sub _line_map_ {
    my $ctx = shift;
    my %lines;
    push @{ $lines{ $_[$_] } }, $_ for 0 .. $#_;    # values MUST be SvIOK
    \%lines;
}

sub _LCS_ {
    my ( $ctx, $a, $b ) = @_;
    my ( $amin, $amax, $bmin, $bmax ) = ( 0, $#$a, 0, $#$b );

    while ( $amin <= $amax and $bmin <= $bmax and $a->[$amin] eq $b->[$bmin] ) {
        $amin++;
        $bmin++;
    }
    while ( $amin <= $amax and $bmin <= $bmax and $a->[$amax] eq $b->[$bmax] ) {
        $amax--;
        $bmax--;
    }

    my $h =
      $ctx->_line_map_( @$b[ $bmin .. $bmax ] ); # line numbers are off by $bmin

    return $amin + _core_loop_( $ctx, $a, $amin, $amax, $h ) + ( $#$a - $amax )
      unless wantarray;

    my @lcs = _core_loop_( $ctx, $a, $amin, $amax, $h );
    if ( $bmin > 0 ) {
        $_->[1] += $bmin for @lcs;               # correct line numbers
    }

    map( [ $_ => $_ ], 0 .. ( $amin - 1 ) ),
      @lcs,
      map( [ $_ => ++$bmax ], ( $amax + 1 ) .. $#$a );
}

1;

__END__

=head1 NAME

Algorithm::Diff::XS - Algorithm::Diff with XS core loop

=head1 SYNOPSIS

    # Drop-in replacement to Algorithm::Diff, but "compact_diff"
    # and C<LCSidx> will run much faster for large data sets.
    use Algorithm::Diff::XS qw( compact_diff LCSidx );

=head1 DESCRIPTION

This module is a simple re-packaging of Joe Schaefer's excellent
but not very well-known L<Algorithm::LCS> with a drop-in interface
that simply re-uses the installed version of the L<Algorithm::Diff>
module.

Note that only the C<LCSidx> function is optimized in XS at the
moment, which means only C<compact_diff> will get significantly
faster for large data sets, while C<diff> and C<sdiff> will run
in identical speed as C<Algorithm::Diff>.

=head1 BENCHMARK

                      Rate     Algorithm::Diff Algorithm::Diff::XS
Algorithm::Diff     14.7/s                  --                -98%
Algorithm::Diff::XS  806/s               5402%                  --

The benchmarking script is as below:

    my @data = ([qw/a b d/ x 50], [qw/b a d c/ x 50]);
    cmpthese( 500, {
        'Algorithm::Diff' => sub {
            Algorithm::Diff::compact_diff(@data)
        },
        'Algorithm::Diff::XS' => sub {
            Algorithm::Diff::XS::compact_diff(@data)
        },
    });

=head1 SEE ALSO

L<Algorithm::Diff>, L<Algorithm::LCS>.

=head1 AUTHORS

Audrey Tang E<lt>cpan@audreyt.orgE<gt>

=head1 COPYRIGHT

Copyright 2008 by Audrey Tang E<lt>cpan@audreyt.orgE<gt>.

Contains derived code copyrighted 2003 by Joe Schaefer,
E<lt>joe+cpan@sunstarsys.comE<gt>.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
                      Renderer/inline.php                                                                                 0000755                 00000012630 00000000000 0010247 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * "Inline" diff renderer.
 *
 * Copyright 2004-2010 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see https://opensource.org/license/lgpl-2-1/.
 *
 * @author  Ciprian Popovici
 * @package Text_Diff
 */

/** Text_Diff_Renderer */

// WP #7391
require_once dirname(dirname(__FILE__)) . '/Renderer.php';

/**
 * "Inline" diff renderer.
 *
 * This class renders diffs in the Wiki-style "inline" format.
 *
 * @author  Ciprian Popovici
 * @package Text_Diff
 */
class Text_Diff_Renderer_inline extends Text_Diff_Renderer {

    /**
     * Number of leading context "lines" to preserve.
     *
     * @var integer
     */
    var $_leading_context_lines = 10000;

    /**
     * Number of trailing context "lines" to preserve.
     *
     * @var integer
     */
    var $_trailing_context_lines = 10000;

    /**
     * Prefix for inserted text.
     *
     * @var string
     */
    var $_ins_prefix = '<ins>';

    /**
     * Suffix for inserted text.
     *
     * @var string
     */
    var $_ins_suffix = '</ins>';

    /**
     * Prefix for deleted text.
     *
     * @var string
     */
    var $_del_prefix = '<del>';

    /**
     * Suffix for deleted text.
     *
     * @var string
     */
    var $_del_suffix = '</del>';

    /**
     * Header for each change block.
     *
     * @var string
     */
    var $_block_header = '';

    /**
     * Whether to split down to character-level.
     *
     * @var boolean
     */
    var $_split_characters = false;

    /**
     * What are we currently splitting on? Used to recurse to show word-level
     * or character-level changes.
     *
     * @var string
     */
    var $_split_level = 'lines';

    function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
    {
        return $this->_block_header;
    }

    function _startBlock($header)
    {
        return $header;
    }

    function _lines($lines, $prefix = ' ', $encode = true)
    {
        if ($encode) {
            array_walk($lines, array(&$this, '_encode'));
        }

        if ($this->_split_level == 'lines') {
            return implode("\n", $lines) . "\n";
        } else {
            return implode('', $lines);
        }
    }

    function _added($lines)
    {
        array_walk($lines, array(&$this, '_encode'));
        $lines[0] = $this->_ins_prefix . $lines[0];
        $lines[count($lines) - 1] .= $this->_ins_suffix;
        return $this->_lines($lines, ' ', false);
    }

    function _deleted($lines, $words = false)
    {
        array_walk($lines, array(&$this, '_encode'));
        $lines[0] = $this->_del_prefix . $lines[0];
        $lines[count($lines) - 1] .= $this->_del_suffix;
        return $this->_lines($lines, ' ', false);
    }

    function _changed($orig, $final)
    {
        /* If we've already split on characters, just display. */
        if ($this->_split_level == 'characters') {
            return $this->_deleted($orig)
                . $this->_added($final);
        }

        /* If we've already split on words, just display. */
        if ($this->_split_level == 'words') {
            $prefix = '';
            while ($orig[0] !== false && $final[0] !== false &&
                   substr($orig[0], 0, 1) == ' ' &&
                   substr($final[0], 0, 1) == ' ') {
                $prefix .= substr($orig[0], 0, 1);
                $orig[0] = substr($orig[0], 1);
                $final[0] = substr($final[0], 1);
            }
            return $prefix . $this->_deleted($orig) . $this->_added($final);
        }

        $text1 = implode("\n", $orig);
        $text2 = implode("\n", $final);

        /* Non-printing newline marker. */
        $nl = "\0";

        if ($this->_split_characters) {
            $diff = new Text_Diff('native',
                                  array(preg_split('//', $text1),
                                        preg_split('//', $text2)));
        } else {
            /* We want to split on word boundaries, but we need to preserve
             * whitespace as well. Therefore we split on words, but include
             * all blocks of whitespace in the wordlist. */
            $diff = new Text_Diff('native',
                                  array($this->_splitOnWords($text1, $nl),
                                        $this->_splitOnWords($text2, $nl)));
        }

        /* Get the diff in inline format. */
        $renderer = new Text_Diff_Renderer_inline
            (array_merge($this->getParams(),
                         array('split_level' => $this->_split_characters ? 'characters' : 'words')));

        /* Run the diff and get the output. */
        return str_replace($nl, "\n", $renderer->render($diff)) . "\n";
    }

    function _splitOnWords($string, $newlineEscape = "\n")
    {
        // Ignore \0; otherwise the while loop will never finish.
        $string = str_replace("\0", '', $string);

        $words = array();
        $length = strlen($string);
        $pos = 0;

        while ($pos < $length) {
            // Eat a word with any preceding whitespace.
            $spaces = strspn(substr($string, $pos), " \n");
            $nextpos = strcspn(substr($string, $pos + $spaces), " \n");
            $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos));
            $pos += $spaces + $nextpos;
        }

        return $words;
    }

    function _encode(&$string)
    {
        $string = htmlspecialchars($string);
    }

}
                                                                                                        Engine/shell.php                                                                                    0000755                 00000012123 00000000000 0007534 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Class used internally by Diff to actually compute the diffs.
 *
 * This class uses the Unix `diff` program via shell_exec to compute the
 * differences between the two input arrays.
 *
 * Copyright 2007-2010 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see https://opensource.org/license/lgpl-2-1/.
 *
 * @author  Milian Wolff <mail@milianw.de>
 * @package Text_Diff
 * @since   0.3.0
 */
class Text_Diff_Engine_shell {

    /**
     * Path to the diff executable
     *
     * @var string
     */
    var $_diffCommand = 'diff';

    /**
     * Returns the array of differences.
     *
     * @param array $from_lines lines of text from old file
     * @param array $to_lines   lines of text from new file
     *
     * @return array all changes made (array with Text_Diff_Op_* objects)
     */
    function diff($from_lines, $to_lines)
    {
        array_walk($from_lines, array('Text_Diff', 'trimNewlines'));
        array_walk($to_lines, array('Text_Diff', 'trimNewlines'));

        $temp_dir = Text_Diff::_getTempDir();

        // Execute gnu diff or similar to get a standard diff file.
        $from_file = tempnam($temp_dir, 'Text_Diff');
        $to_file = tempnam($temp_dir, 'Text_Diff');
        $fp = fopen($from_file, 'w');
        fwrite($fp, implode("\n", $from_lines));
        fclose($fp);
        $fp = fopen($to_file, 'w');
        fwrite($fp, implode("\n", $to_lines));
        fclose($fp);
        $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file);
        unlink($from_file);
        unlink($to_file);

        if (is_null($diff)) {
            // No changes were made
            return array(new Text_Diff_Op_copy($from_lines));
        }

        $from_line_no = 1;
        $to_line_no = 1;
        $edits = array();

        // Get changed lines by parsing something like:
        // 0a1,2
        // 1,2c4,6
        // 1,5d6
        preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff,
            $matches, PREG_SET_ORDER);

        foreach ($matches as $match) {
            if (!isset($match[5])) {
                // This paren is not set every time (see regex).
                $match[5] = false;
            }

            if ($match[3] == 'a') {
                $from_line_no--;
            }

            if ($match[3] == 'd') {
                $to_line_no--;
            }

            if ($from_line_no < $match[1] || $to_line_no < $match[4]) {
                // copied lines
                assert($match[1] - $from_line_no == $match[4] - $to_line_no);
                array_push($edits,
                    new Text_Diff_Op_copy(
                        $this->_getLines($from_lines, $from_line_no, $match[1] - 1),
                        $this->_getLines($to_lines, $to_line_no, $match[4] - 1)));
            }

            switch ($match[3]) {
            case 'd':
                // deleted lines
                array_push($edits,
                    new Text_Diff_Op_delete(
                        $this->_getLines($from_lines, $from_line_no, $match[2])));
                $to_line_no++;
                break;

            case 'c':
                // changed lines
                array_push($edits,
                    new Text_Diff_Op_change(
                        $this->_getLines($from_lines, $from_line_no, $match[2]),
                        $this->_getLines($to_lines, $to_line_no, $match[5])));
                break;

            case 'a':
                // added lines
                array_push($edits,
                    new Text_Diff_Op_add(
                        $this->_getLines($to_lines, $to_line_no, $match[5])));
                $from_line_no++;
                break;
            }
        }

        if (!empty($from_lines)) {
            // Some lines might still be pending. Add them as copied
            array_push($edits,
                new Text_Diff_Op_copy(
                    $this->_getLines($from_lines, $from_line_no,
                                     $from_line_no + count($from_lines) - 1),
                    $this->_getLines($to_lines, $to_line_no,
                                     $to_line_no + count($to_lines) - 1)));
        }

        return $edits;
    }

    /**
     * Get lines from either the old or new text
     *
     * @access private
     *
     * @param array $text_lines Either $from_lines or $to_lines (passed by reference).
     * @param int   $line_no    Current line number (passed by reference).
     * @param int   $end        Optional end line, when we want to chop more
     *                          than one line.
     *
     * @return array The chopped lines
     */
    function _getLines(&$text_lines, &$line_no, $end = false)
    {
        if (!empty($end)) {
            $lines = array();
            // We can shift even more
            while ($line_no <= $end) {
                array_push($lines, array_shift($text_lines));
                $line_no++;
            }
        } else {
            $lines = array(array_shift($text_lines));
            $line_no++;
        }

        return $lines;
    }

}
                                                                                                                                                                                                                                                                                                                                                                                                                                             Engine/native.php                                                                                   0000755                 00000037261 00000000000 0007725 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Class used internally by Text_Diff to actually compute the diffs.
 *
 * This class is implemented using native PHP code.
 *
 * The algorithm used here is mostly lifted from the perl module
 * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
 * https://cpan.metacpan.org/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 (and a bit of code) are taken from analyze.c, of GNU
 * diffutils-2.7, which can be found at:
 * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
 *
 * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from
 * Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this
 * code was written by him, and is used/adapted with his permission.
 *
 * Copyright 2004-2010 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see https://opensource.org/license/lgpl-2-1/.
 *
 * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 * @package Text_Diff
 */
class Text_Diff_Engine_native {

    public $xchanged;
    public $ychanged;
    public $xv;
    public $yv;
    public $xind;
    public $yind;
    public $seq;
    public $in_seq;
    public $lcs;

    function diff($from_lines, $to_lines)
    {
        array_walk($from_lines, array('Text_Diff', 'trimNewlines'));
        array_walk($to_lines, array('Text_Diff', 'trimNewlines'));

        $n_from = count($from_lines);
        $n_to = count($to_lines);

        $this->xchanged = $this->ychanged = array();
        $this->xv = $this->yv = array();
        $this->xind = $this->yind = array();
        unset($this->seq);
        unset($this->in_seq);
        unset($this->lcs);

        // Skip leading common lines.
        for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
            if ($from_lines[$skip] !== $to_lines[$skip]) {
                break;
            }
            $this->xchanged[$skip] = $this->ychanged[$skip] = false;
        }

        // Skip trailing common lines.
        $xi = $n_from; $yi = $n_to;
        for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
            if ($from_lines[$xi] !== $to_lines[$yi]) {
                break;
            }
            $this->xchanged[$xi] = $this->ychanged[$yi] = false;
        }

        // Ignore lines which do not exist in both files.
        for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
            $xhash[$from_lines[$xi]] = 1;
        }
        for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
            $line = $to_lines[$yi];
            if (($this->ychanged[$yi] = empty($xhash[$line]))) {
                continue;
            }
            $yhash[$line] = 1;
            $this->yv[] = $line;
            $this->yind[] = $yi;
        }
        for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
            $line = $from_lines[$xi];
            if (($this->xchanged[$xi] = empty($yhash[$line]))) {
                continue;
            }
            $this->xv[] = $line;
            $this->xind[] = $xi;
        }

        // Find the LCS.
        $this->_compareseq(0, count($this->xv), 0, count($this->yv));

        // Merge edits when possible.
        $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged);
        $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged);

        // Compute the edit operations.
        $edits = array();
        $xi = $yi = 0;
        while ($xi < $n_from || $yi < $n_to) {
            assert($yi < $n_to || $this->xchanged[$xi]);
            assert($xi < $n_from || $this->ychanged[$yi]);

            // Skip matching "snake".
            $copy = array();
            while ($xi < $n_from && $yi < $n_to
                   && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
                $copy[] = $from_lines[$xi++];
                ++$yi;
            }
            if ($copy) {
                $edits[] = new Text_Diff_Op_copy($copy);
            }

            // Find deletes & adds.
            $delete = array();
            while ($xi < $n_from && $this->xchanged[$xi]) {
                $delete[] = $from_lines[$xi++];
            }

            $add = array();
            while ($yi < $n_to && $this->ychanged[$yi]) {
                $add[] = $to_lines[$yi++];
            }

            if ($delete && $add) {
                $edits[] = new Text_Diff_Op_change($delete, $add);
            } elseif ($delete) {
                $edits[] = new Text_Diff_Op_delete($delete);
            } elseif ($add) {
                $edits[] = new Text_Diff_Op_add($add);
            }
        }

        return $edits;
    }

    /**
     * Divides 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 is 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++) {
                $line = $flip ? $this->yv[$x] : $this->xv[$x];
                if (empty($ymatches[$line])) {
                    continue;
                }
                $matches = $ymatches[$line];
                reset($matches);
                while ($y = current($matches)) {
                    if (empty($this->in_seq[$y])) {
                        $k = $this->_lcsPos($y);
                        assert($k > 0);
                        $ymids[$k] = $ymids[$k - 1];
                        break;
                    }
                    next($matches);
                }
                while ($y = current($matches)) {
                    if ($y > $this->seq[$k - 1]) {
                        assert($y <= $this->seq[$k]);
                        /* 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;
                    } elseif (empty($this->in_seq[$y])) {
                        $k = $this->_lcsPos($y);
                        assert($k > 0);
                        $ymids[$k] = $ymids[$k - 1];
                    }
                    next($matches);
                }
            }
        }

        $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 _lcsPos($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;
            }
        }

        assert($ypos != $this->seq[$end]);

        $this->in_seq[$this->seq[$end]] = false;
        $this->seq[$end] = $ypos;
        $this->in_seq[$ypos] = 1;
        return $end;
    }

    /**
     * Finds 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;
            }
        }
    }

    /**
     * Adjusts 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 has 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 _shiftBoundaries($lines, &$changed, $other_changed)
    {
        $i = 0;
        $j = 0;

        assert(count($lines) == count($changed));
        $len = count($lines);
        $other_len = count($other_changed);

        while (1) {
            /* Scan forward to find the beginning of another run of
             * changes. Also keep track of the corresponding point in the
             * other file.
             *
             * Throughout this code, $i and $j are adjusted together so that
             * the first $i elements of $changed and the first $j elements of
             * $other_changed both contain the same number of zeros (unchanged
             * lines).
             *
             * Furthermore, $j is always kept so that $j == $other_len or
             * $other_changed[$j] == false. */
            while ($j < $other_len && $other_changed[$j]) {
                $j++;
            }

            while ($i < $len && ! $changed[$i]) {
                assert($j < $other_len && ! $other_changed[$j]);
                $i++; $j++;
                while ($j < $other_len && $other_changed[$j]) {
                    $j++;
                }
            }

            if ($i == $len) {
                break;
            }

            $start = $i;

            /* Find the end of this run of changes. */
            while (++$i < $len && $changed[$i]) {
                continue;
            }

            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 > 0 && $lines[$start - 1] == $lines[$i - 1]) {
                    $changed[--$start] = 1;
                    $changed[--$i] = false;
                    while ($start > 0 && $changed[$start - 1]) {
                        $start--;
                    }
                    assert($j > 0);
                    while ($other_changed[--$j]) {
                        continue;
                    }
                    assert($j >= 0 && !$other_changed[$j]);
                }

                /* 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 = $j < $other_len ? $i : $len;

                /* 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 ($i < $len && $changed[$i]) {
                        $i++;
                    }

                    assert($j < $other_len && ! $other_changed[$j]);
                    $j++;
                    if ($j < $other_len && $other_changed[$j]) {
                        $corresponding = $i;
                        while ($j < $other_len && $other_changed[$j]) {
                            $j++;
                        }
                    }
                }
            } 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;
                assert($j > 0);
                while ($other_changed[--$j]) {
                    continue;
                }
                assert($j >= 0 && !$other_changed[$j]);
            }
        }
    }

}
                                                                                                                                                                                                                                                                                                                                               Engine/string.php                                                                                   0000755                 00000020233 00000000000 0007734 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Parses unified or context diffs output from eg. the diff utility.
 *
 * Example:
 * <code>
 * $patch = file_get_contents('example.patch');
 * $diff = new Text_Diff('string', array($patch));
 * $renderer = new Text_Diff_Renderer_inline();
 * echo $renderer->render($diff);
 * </code>
 *
 * Copyright 2005 Örjan Persson <o@42mm.org>
 * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see https://opensource.org/license/lgpl-2-1/.
 *
 * @author  Örjan Persson <o@42mm.org>
 * @package Text_Diff
 * @since   0.2.0
 */
class Text_Diff_Engine_string {

    /**
     * Parses a unified or context diff.
     *
     * First param contains the whole diff and the second can be used to force
     * a specific diff type. If the second parameter is 'autodetect', the
     * diff will be examined to find out which type of diff this is.
     *
     * @param string $diff  The diff content.
     * @param string $mode  The diff mode of the content in $diff. One of
     *                      'context', 'unified', or 'autodetect'.
     *
     * @return array  List of all diff operations.
     */
    function diff($diff, $mode = 'autodetect')
    {
        // Detect line breaks.
        $lnbr = "\n";
        if (strpos($diff, "\r\n") !== false) {
            $lnbr = "\r\n";
        } elseif (strpos($diff, "\r") !== false) {
            $lnbr = "\r";
        }

        // Make sure we have a line break at the EOF.
        if (substr($diff, -strlen($lnbr)) != $lnbr) {
            $diff .= $lnbr;
        }

        if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') {
            return PEAR::raiseError('Type of diff is unsupported');
        }

        if ($mode == 'autodetect') {
            $context = strpos($diff, '***');
            $unified = strpos($diff, '---');
            if ($context === $unified) {
                return PEAR::raiseError('Type of diff could not be detected');
            } elseif ($context === false || $unified === false) {
                $mode = $context !== false ? 'context' : 'unified';
            } else {
                $mode = $context < $unified ? 'context' : 'unified';
            }
        }

        // Split by new line and remove the diff header, if there is one.
        $diff = explode($lnbr, $diff);
        if (($mode == 'context' && strpos($diff[0], '***') === 0) ||
            ($mode == 'unified' && strpos($diff[0], '---') === 0)) {
            array_shift($diff);
            array_shift($diff);
        }

        if ($mode == 'context') {
            return $this->parseContextDiff($diff);
        } else {
            return $this->parseUnifiedDiff($diff);
        }
    }

    /**
     * Parses an array containing the unified diff.
     *
     * @param array $diff  Array of lines.
     *
     * @return array  List of all diff operations.
     */
    function parseUnifiedDiff($diff)
    {
        $edits = array();
        $end = count($diff) - 1;
        for ($i = 0; $i < $end;) {
            $diff1 = array();
            switch (substr($diff[$i], 0, 1)) {
            case ' ':
                do {
                    $diff1[] = substr($diff[$i], 1);
                } while (++$i < $end && substr($diff[$i], 0, 1) == ' ');
                $edits[] = new Text_Diff_Op_copy($diff1);
                break;

            case '+':
                // get all new lines
                do {
                    $diff1[] = substr($diff[$i], 1);
                } while (++$i < $end && substr($diff[$i], 0, 1) == '+');
                $edits[] = new Text_Diff_Op_add($diff1);
                break;

            case '-':
                // get changed or removed lines
                $diff2 = array();
                do {
                    $diff1[] = substr($diff[$i], 1);
                } while (++$i < $end && substr($diff[$i], 0, 1) == '-');

                while ($i < $end && substr($diff[$i], 0, 1) == '+') {
                    $diff2[] = substr($diff[$i++], 1);
                }
                if (count($diff2) == 0) {
                    $edits[] = new Text_Diff_Op_delete($diff1);
                } else {
                    $edits[] = new Text_Diff_Op_change($diff1, $diff2);
                }
                break;

            default:
                $i++;
                break;
            }
        }

        return $edits;
    }

    /**
     * Parses an array containing the context diff.
     *
     * @param array $diff  Array of lines.
     *
     * @return array  List of all diff operations.
     */
    function parseContextDiff(&$diff)
    {
        $edits = array();
        $i = $max_i = $j = $max_j = 0;
        $end = count($diff) - 1;
        while ($i < $end && $j < $end) {
            while ($i >= $max_i && $j >= $max_j) {
                // Find the boundaries of the diff output of the two files
                for ($i = $j;
                     $i < $end && substr($diff[$i], 0, 3) == '***';
                     $i++);
                for ($max_i = $i;
                     $max_i < $end && substr($diff[$max_i], 0, 3) != '---';
                     $max_i++);
                for ($j = $max_i;
                     $j < $end && substr($diff[$j], 0, 3) == '---';
                     $j++);
                for ($max_j = $j;
                     $max_j < $end && substr($diff[$max_j], 0, 3) != '***';
                     $max_j++);
            }

            // find what hasn't been changed
            $array = array();
            while ($i < $max_i &&
                   $j < $max_j &&
                   strcmp($diff[$i], $diff[$j]) == 0) {
                $array[] = substr($diff[$i], 2);
                $i++;
                $j++;
            }

            while ($i < $max_i && ($max_j-$j) <= 1) {
                if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') {
                    break;
                }
                $array[] = substr($diff[$i++], 2);
            }

            while ($j < $max_j && ($max_i-$i) <= 1) {
                if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') {
                    break;
                }
                $array[] = substr($diff[$j++], 2);
            }
            if (count($array) > 0) {
                $edits[] = new Text_Diff_Op_copy($array);
            }

            if ($i < $max_i) {
                $diff1 = array();
                switch (substr($diff[$i], 0, 1)) {
                case '!':
                    $diff2 = array();
                    do {
                        $diff1[] = substr($diff[$i], 2);
                        if ($j < $max_j && substr($diff[$j], 0, 1) == '!') {
                            $diff2[] = substr($diff[$j++], 2);
                        }
                    } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!');
                    $edits[] = new Text_Diff_Op_change($diff1, $diff2);
                    break;

                case '+':
                    do {
                        $diff1[] = substr($diff[$i], 2);
                    } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+');
                    $edits[] = new Text_Diff_Op_add($diff1);
                    break;

                case '-':
                    do {
                        $diff1[] = substr($diff[$i], 2);
                    } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-');
                    $edits[] = new Text_Diff_Op_delete($diff1);
                    break;
                }
            }

            if ($j < $max_j) {
                $diff2 = array();
                switch (substr($diff[$j], 0, 1)) {
                case '+':
                    do {
                        $diff2[] = substr($diff[$j++], 2);
                    } while ($j < $max_j && substr($diff[$j], 0, 1) == '+');
                    $edits[] = new Text_Diff_Op_add($diff2);
                    break;

                case '-':
                    do {
                        $diff2[] = substr($diff[$j++], 2);
                    } while ($j < $max_j && substr($diff[$j], 0, 1) == '-');
                    $edits[] = new Text_Diff_Op_delete($diff2);
                    break;
                }
            }
        }

        return $edits;
    }

}
                                                                                                                                                                                                                                                                                                                                                                     Engine/xdiff.php                                                                                    0000755                 00000004233 00000000000 0007530 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Class used internally by Diff to actually compute the diffs.
 *
 * This class uses the xdiff PECL package (http://pecl.php.net/package/xdiff)
 * to compute the differences between the two input arrays.
 *
 * Copyright 2004-2010 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see https://opensource.org/license/lgpl-2-1/.
 *
 * @author  Jon Parise <jon@horde.org>
 * @package Text_Diff
 */
class Text_Diff_Engine_xdiff {

    /**
     */
    function diff($from_lines, $to_lines)
    {
        array_walk($from_lines, array('Text_Diff', 'trimNewlines'));
        array_walk($to_lines, array('Text_Diff', 'trimNewlines'));

        /* Convert the two input arrays into strings for xdiff processing. */
        $from_string = implode("\n", $from_lines);
        $to_string = implode("\n", $to_lines);

        /* Diff the two strings and convert the result to an array. */
        $diff = xdiff_string_diff($from_string, $to_string, count($to_lines));
        $diff = explode("\n", $diff);

        /* Walk through the diff one line at a time.  We build the $edits
         * array of diff operations by reading the first character of the
         * xdiff output (which is in the "unified diff" format).
         *
         * Note that we don't have enough information to detect "changed"
         * lines using this approach, so we can't add Text_Diff_Op_changed
         * instances to the $edits array.  The result is still perfectly
         * valid, albeit a little less descriptive and efficient. */
        $edits = array();
        foreach ($diff as $line) {
            if (!strlen($line)) {
                continue;
            }
            switch ($line[0]) {
            case ' ':
                $edits[] = new Text_Diff_Op_copy(array(substr($line, 1)));
                break;

            case '+':
                $edits[] = new Text_Diff_Op_add(array(substr($line, 1)));
                break;

            case '-':
                $edits[] = new Text_Diff_Op_delete(array(substr($line, 1)));
                break;
            }
        }

        return $edits;
    }

}
                                                                                                                                                                                                                                                                                                                                                                     Renderer.php                                                                                        0000755                 00000015226 00000000000 0006775 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * A class to render Diffs in different formats.
 *
 * This class renders the diff in classic diff format. It is intended that
 * this class be customized via inheritance, to obtain fancier outputs.
 *
 * Copyright 2004-2010 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see https://opensource.org/license/lgpl-2-1/.
 *
 * @package Text_Diff
 */
class Text_Diff_Renderer {

    /**
     * Number of leading context "lines" to preserve.
     *
     * This should be left at zero for this class, but subclasses may want to
     * set this to other values.
     */
    var $_leading_context_lines = 0;

    /**
     * Number of trailing context "lines" to preserve.
     *
     * This should be left at zero for this class, but subclasses may want to
     * set this to other values.
     */
    var $_trailing_context_lines = 0;

    /**
     * Constructor.
     */
    function __construct( $params = array() )
    {
        foreach ($params as $param => $value) {
            $v = '_' . $param;
            if (isset($this->$v)) {
                $this->$v = $value;
            }
        }
    }

	/**
	 * PHP4 constructor.
	 */
	public function Text_Diff_Renderer( $params = array() ) {
		self::__construct( $params );
	}

    /**
     * Get any renderer parameters.
     *
     * @return array  All parameters of this renderer object.
     */
    function getParams()
    {
        $params = array();
        foreach (get_object_vars($this) as $k => $v) {
            if ($k[0] == '_') {
                $params[substr($k, 1)] = $v;
            }
        }

        return $params;
    }

    /**
     * Renders a diff.
     *
     * @param Text_Diff $diff  A Text_Diff object.
     *
     * @return string  The formatted output.
     */
    function render($diff)
    {
        $xi = $yi = 1;
        $block = false;
        $context = array();

        $nlead = $this->_leading_context_lines;
        $ntrail = $this->_trailing_context_lines;

        $output = $this->_startDiff();

        $diffs = $diff->getDiff();
        foreach ($diffs as $i => $edit) {
            /* If these are unchanged (copied) lines, and we want to keep
             * leading or trailing context lines, extract them from the copy
             * block. */
            if (is_a($edit, 'Text_Diff_Op_copy')) {
                /* Do we have any diff blocks yet? */
                if (is_array($block)) {
                    /* How many lines to keep as context from the copy
                     * block. */
                    $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;
                    if (count($edit->orig) <= $keep) {
                        /* We have less lines in the block than we want for
                         * context => keep the whole block. */
                        $block[] = $edit;
                    } else {
                        if ($ntrail) {
                            /* Create a new block with as many lines as we need
                             * for the trailing context. */
                            $context = array_slice($edit->orig, 0, $ntrail);
                            $block[] = new Text_Diff_Op_copy($context);
                        }
                        /* @todo */
                        $output .= $this->_block($x0, $ntrail + $xi - $x0,
                                                 $y0, $ntrail + $yi - $y0,
                                                 $block);
                        $block = false;
                    }
                }
                /* Keep the copy block as the context for the next block. */
                $context = $edit->orig;
            } else {
                /* Don't we have any diff blocks yet? */
                if (!is_array($block)) {
                    /* Extract context lines from the preceding copy block. */
                    $context = array_slice($context, count($context) - $nlead);
                    $x0 = $xi - count($context);
                    $y0 = $yi - count($context);
                    $block = array();
                    if ($context) {
                        $block[] = new Text_Diff_Op_copy($context);
                    }
                }
                $block[] = $edit;
            }

            if ($edit->orig) {
                $xi += count($edit->orig);
            }
            if ($edit->final) {
                $yi += count($edit->final);
            }
        }

        if (is_array($block)) {
            $output .= $this->_block($x0, $xi - $x0,
                                     $y0, $yi - $y0,
                                     $block);
        }

        return $output . $this->_endDiff();
    }

    function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
    {
        $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));

        foreach ($edits as $edit) {
            switch (strtolower(get_class($edit))) {
            case 'text_diff_op_copy':
                $output .= $this->_context($edit->orig);
                break;

            case 'text_diff_op_add':
                $output .= $this->_added($edit->final);
                break;

            case 'text_diff_op_delete':
                $output .= $this->_deleted($edit->orig);
                break;

            case 'text_diff_op_change':
                $output .= $this->_changed($edit->orig, $edit->final);
                break;
            }
        }

        return $output . $this->_endBlock();
    }

    function _startDiff()
    {
        return '';
    }

    function _endDiff()
    {
        return '';
    }

    function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
    {
        if ($xlen > 1) {
            $xbeg .= ',' . ($xbeg + $xlen - 1);
        }
        if ($ylen > 1) {
            $ybeg .= ',' . ($ybeg + $ylen - 1);
        }

        // this matches the GNU Diff behaviour
        if ($xlen && !$ylen) {
            $ybeg--;
        } elseif (!$xlen) {
            $xbeg--;
        }

        return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
    }

    function _startBlock($header)
    {
        return $header . "\n";
    }

    function _endBlock()
    {
        return '';
    }

    function _lines($lines, $prefix = ' ')
    {
        return $prefix . implode("\n$prefix", $lines) . "\n";
    }

    function _context($lines)
    {
        return $this->_lines($lines, '  ');
    }

    function _added($lines)
    {
        return $this->_lines($lines, '> ');
    }

    function _deleted($lines)
    {
        return $this->_lines($lines, '< ');
    }

    function _changed($orig, $final)
    {
        return $this->_deleted($orig) . "---\n" . $this->_added($final);
    }

}
                                                                                                                                                                                                                                                                                                                                                                          SequenceMatcher.php                                                                                 0000755                 00000042720 00000000000 0010302 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Sequence matcher for Diff
 *
 * PHP version 5
 *
 * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the Chris Boulton nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package Diff
 * @author Chris Boulton <chris.boulton@interspire.com>
 * @copyright (c) 2009 Chris Boulton
 * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
 * @version 1.1
 * @link http://github.com/chrisboulton/php-diff
 */

class Diff_SequenceMatcher
{
	/**
	 * @var string|array Either a string or an array containing a callback function to determine if a line is "junk" or not.
	 */
	private $junkCallback = null;

	/**
	 * @var array The first sequence to compare against.
	 */
	private $a = null;

	/**
	 * @var array The second sequence.
	 */
	private $b = null;

	/**
	 * @var array Array of characters that are considered junk from the second sequence. Characters are the array key.
	 */
	private $junkDict = array();

	/**
	 * @var array Array of indices that do not contain junk elements.
	 */
	private $b2j = array();

	private $options = array();

	private $defaultOptions = array(
		'ignoreNewLines' => false,
		'ignoreWhitespace' => false,
		'ignoreCase' => false
	);
	
	private $matchingBlocks = null;
	private $opCodes = null;
	private $fullBCount = null;

	/**
	 * The constructor. With the sequences being passed, they'll be set for the
	 * sequence matcher and it will perform a basic cleanup & calculate junk
	 * elements.
	 *
	 * @param string|array $a A string or array containing the lines to compare against.
	 * @param string|array $b A string or array containing the lines to compare.
	 * @param string|array $junkCallback Either an array or string that references a callback function (if there is one) to determine 'junk' characters.
	 */
	public function __construct($a, $b, $junkCallback=null, $options=array())
	{
		$this->a = null;
		$this->b = null;
		$this->junkCallback = $junkCallback;
		$this->setOptions($options);
		$this->setSequences($a, $b);
	}

	public function setOptions($options)
	{
		$this->options = array_merge($this->defaultOptions, $options);
	}

	/**
	 * Set the first and second sequences to use with the sequence matcher.
	 *
	 * @param string|array $a A string or array containing the lines to compare against.
	 * @param string|array $b A string or array containing the lines to compare.
	 */
	public function setSequences($a, $b)
	{
		$this->setSeq1($a);
		$this->setSeq2($b);
	}

	/**
	 * Set the first sequence ($a) and reset any internal caches to indicate that
	 * when calling the calculation methods, we need to recalculate them.
	 *
	 * @param string|array $a The sequence to set as the first sequence.
	 */
	public function setSeq1($a)
	{
		if(!is_array($a)) {
			$a = str_split($a);
		}
		if($a == $this->a) {
			return;
		}

		$this->a= $a;
		$this->matchingBlocks = null;
		$this->opCodes = null;
	}

	/**
	 * Set the second sequence ($b) and reset any internal caches to indicate that
	 * when calling the calculation methods, we need to recalculate them.
	 *
	 * @param string|array $b The sequence to set as the second sequence.
	 */
	public function setSeq2($b)
	{
		if(!is_array($b)) {
			$b = str_split($b);
		}
		if($b == $this->b) {
			return;
		}

		$this->b = $b;
		$this->matchingBlocks = null;
		$this->opCodes = null;
		$this->fullBCount = null;
		$this->chainB();
	}

	/**
	 * Generate the internal arrays containing the list of junk and non-junk
	 * characters for the second ($b) sequence.
	 */
	private function chainB()
	{
		$length = count ($this->b);
		$this->b2j = array();
		$popularDict = array();

		for($i = 0; $i < $length; ++$i) {
			$char = $this->b[$i];
			if(isset($this->b2j[$char])) {
				if($length >= 200 && count($this->b2j[$char]) * 100 > $length) {
					$popularDict[$char] = 1;
					unset($this->b2j[$char]);
				}
				else {
					$this->b2j[$char][] = $i;
				}
			}
			else {
				$this->b2j[$char] = array(
					$i
				);
			}
		}

		// Remove leftovers
		foreach(array_keys($popularDict) as $char) {
			unset($this->b2j[$char]);
		}

		$this->junkDict = array();
		if(is_callable($this->junkCallback)) {
			foreach(array_keys($popularDict) as $char) {
				if(call_user_func($this->junkCallback, $char)) {
					$this->junkDict[$char] = 1;
					unset($popularDict[$char]);
				}
			}

			foreach(array_keys($this->b2j) as $char) {
				if(call_user_func($this->junkCallback, $char)) {
					$this->junkDict[$char] = 1;
					unset($this->b2j[$char]);
				}
			}
		}
	}

	/**
	 * Checks if a particular character is in the junk dictionary
	 * for the list of junk characters.
	 *
	 * @return boolean $b True if the character is considered junk. False if not.
	 */
	private function isBJunk($b)
	{
		if(isset($this->juncDict[$b])) {
			return true;
		}

		return false;
	}

	/**
	 * Find the longest matching block in the two sequences, as defined by the
	 * lower and upper constraints for each sequence. (for the first sequence,
	 * $alo - $ahi and for the second sequence, $blo - $bhi)
	 *
	 * Essentially, of all of the maximal matching blocks, return the one that
	 * startest earliest in $a, and all of those maximal matching blocks that
	 * start earliest in $a, return the one that starts earliest in $b.
	 *
	 * If the junk callback is defined, do the above but with the restriction
	 * that the junk element appears in the block. Extend it as far as possible
	 * by matching only junk elements in both $a and $b.
	 *
	 * @param int $alo The lower constraint for the first sequence.
	 * @param int $ahi The upper constraint for the first sequence.
	 * @param int $blo The lower constraint for the second sequence.
	 * @param int $bhi The upper constraint for the second sequence.
	 * @return array Array containing the longest match that includes the starting position in $a, start in $b and the length/size.
	 */
	public function findLongestMatch($alo, $ahi, $blo, $bhi)
	{
		$a = $this->a;
		$b = $this->b;

		$bestI = $alo;
		$bestJ = $blo;
		$bestSize = 0;

		$j2Len = array();
		$nothing = array();

		for($i = $alo; $i < $ahi; ++$i) {
			$newJ2Len = array();
			$jDict = $this->arrayGetDefault($this->b2j, $a[$i], $nothing);
			foreach($jDict as $jKey => $j) {
				if($j < $blo) {
					continue;
				}
				else if($j >= $bhi) {
					break;
				}

				$k = $this->arrayGetDefault($j2Len, $j -1, 0) + 1;
				$newJ2Len[$j] = $k;
				if($k > $bestSize) {
					$bestI = $i - $k + 1;
					$bestJ = $j - $k + 1;
					$bestSize = $k;
				}
			}

			$j2Len = $newJ2Len;
		}

		while($bestI > $alo && $bestJ > $blo && !$this->isBJunk($b[$bestJ - 1]) &&
			!$this->linesAreDifferent($bestI - 1, $bestJ - 1)) {
				--$bestI;
				--$bestJ;
				++$bestSize;
		}

		while($bestI + $bestSize < $ahi && ($bestJ + $bestSize) < $bhi &&
			!$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
				++$bestSize;
		}

		while($bestI > $alo && $bestJ > $blo && $this->isBJunk($b[$bestJ - 1]) &&
			!$this->isLineDifferent($bestI - 1, $bestJ - 1)) {
				--$bestI;
				--$bestJ;
				++$bestSize;
		}

		while($bestI + $bestSize < $ahi && $bestJ + $bestSize < $bhi &&
			$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
					++$bestSize;
		}

		return array(
			$bestI,
			$bestJ,
			$bestSize
		);
	}

	/**
	 * Check if the two lines at the given indexes are different or not.
	 *
	 * @param int $aIndex Line number to check against in a.
	 * @param int $bIndex Line number to check against in b.
	 * @return boolean True if the lines are different and false if not.
	 */
	public function linesAreDifferent($aIndex, $bIndex)
	{
		$lineA = $this->a[$aIndex];
		$lineB = $this->b[$bIndex];

		if($this->options['ignoreWhitespace']) {
			$replace = array("\t", ' ');
			$lineA = str_replace($replace, '', $lineA);
			$lineB = str_replace($replace, '', $lineB);
		}

		if($this->options['ignoreCase']) {
			$lineA = strtolower($lineA);
			$lineB = strtolower($lineB);
		}

		if($lineA != $lineB) {
			return true;
		}

		return false;
	}

	/**
	 * Return a nested set of arrays for all of the matching sub-sequences
	 * in the strings $a and $b.
	 *
	 * Each block contains the lower constraint of the block in $a, the lower
	 * constraint of the block in $b and finally the number of lines that the
	 * block continues for.
	 *
	 * @return array Nested array of the matching blocks, as described by the function.
	 */
	public function getMatchingBlocks()
	{
		if(!empty($this->matchingBlocks)) {
			return $this->matchingBlocks;
		}

		$aLength = count($this->a);
		$bLength = count($this->b);

		$queue = array(
			array(
				0,
				$aLength,
				0,
				$bLength
			)
		);

		$matchingBlocks = array();
		while(!empty($queue)) {
			list($alo, $ahi, $blo, $bhi) = array_pop($queue);
			$x = $this->findLongestMatch($alo, $ahi, $blo, $bhi);
			list($i, $j, $k) = $x;
			if($k) {
				$matchingBlocks[] = $x;
				if($alo < $i && $blo < $j) {
					$queue[] = array(
						$alo,
						$i,
						$blo,
						$j
					);
				}

				if($i + $k < $ahi && $j + $k < $bhi) {
					$queue[] = array(
						$i + $k,
						$ahi,
						$j + $k,
						$bhi
					);
				}
			}
		}

		usort($matchingBlocks, array($this, 'tupleSort'));

		$i1 = 0;
		$j1 = 0;
		$k1 = 0;
		$nonAdjacent = array();
		foreach($matchingBlocks as $block) {
			list($i2, $j2, $k2) = $block;
			if($i1 + $k1 == $i2 && $j1 + $k1 == $j2) {
				$k1 += $k2;
			}
			else {
				if($k1) {
					$nonAdjacent[] = array(
						$i1,
						$j1,
						$k1
					);
				}

				$i1 = $i2;
				$j1 = $j2;
				$k1 = $k2;
			}
		}

		if($k1) {
			$nonAdjacent[] = array(
				$i1,
				$j1,
				$k1
			);
		}

		$nonAdjacent[] = array(
			$aLength,
			$bLength,
			0
		);

		$this->matchingBlocks = $nonAdjacent;
		return $this->matchingBlocks;
	}

	/**
	 * Return a list of all of the opcodes for the differences between the
	 * two strings.
	 *
	 * The nested array returned contains an array describing the opcode
	 * which includes:
	 * 0 - The type of tag (as described below) for the opcode.
	 * 1 - The beginning line in the first sequence.
	 * 2 - The end line in the first sequence.
	 * 3 - The beginning line in the second sequence.
	 * 4 - The end line in the second sequence.
	 *
	 * The different types of tags include:
	 * replace - The string from $i1 to $i2 in $a should be replaced by
	 *           the string in $b from $j1 to $j2.
	 * delete -  The string in $a from $i1 to $j2 should be deleted.
	 * insert -  The string in $b from $j1 to $j2 should be inserted at
	 *           $i1 in $a.
	 * equal  -  The two strings with the specified ranges are equal.
	 *
	 * @return array Array of the opcodes describing the differences between the strings.
	 */
	public function getOpCodes()
	{
		if(!empty($this->opCodes)) {
			return $this->opCodes;
		}

		$i = 0;
		$j = 0;
		$this->opCodes = array();

		$blocks = $this->getMatchingBlocks();
		foreach($blocks as $block) {
			list($ai, $bj, $size) = $block;
			$tag = '';
			if($i < $ai && $j < $bj) {
				$tag = 'replace';
			}
			else if($i < $ai) {
				$tag = 'delete';
			}
			else if($j < $bj) {
				$tag = 'insert';
			}

			if($tag) {
				$this->opCodes[] = array(
					$tag,
					$i,
					$ai,
					$j,
					$bj
				);
			}

			$i = $ai + $size;
			$j = $bj + $size;

			if($size) {
				$this->opCodes[] = array(
					'equal',
					$ai,
					$i,
					$bj,
					$j
				);
			}
		}
		return $this->opCodes;
	}

	/**
	 * Return a series of nested arrays containing different groups of generated
	 * opcodes for the differences between the strings with up to $context lines
	 * of surrounding content.
	 *
	 * Essentially what happens here is any big equal blocks of strings are stripped
	 * out, the smaller subsets of changes are then arranged in to their groups.
	 * This means that the sequence matcher and diffs do not need to include the full
	 * content of the different files but can still provide context as to where the
	 * changes are.
	 *
	 * @param int $context The number of lines of context to provide around the groups.
	 * @return array Nested array of all of the grouped opcodes.
	 */
	public function getGroupedOpcodes($context=3)
	{
		$opCodes = $this->getOpCodes();
		if(empty($opCodes)) {
			$opCodes = array(
				array(
					'equal',
					0,
					1,
					0,
					1
				)
			);
		}

		if($opCodes[0][0] == 'equal') {
			$opCodes[0] = array(
				$opCodes[0][0],
				max($opCodes[0][1], $opCodes[0][2] - $context),
				$opCodes[0][2],
				max($opCodes[0][3], $opCodes[0][4] - $context),
				$opCodes[0][4]
			);
		}

		$lastItem = count($opCodes) - 1;
		if($opCodes[$lastItem][0] == 'equal') {
			list($tag, $i1, $i2, $j1, $j2) = $opCodes[$lastItem];
			$opCodes[$lastItem] = array(
				$tag,
				$i1,
				min($i2, $i1 + $context),
				$j1,
				min($j2, $j1 + $context)
			);
		}

		$maxRange = $context * 2;
		$groups = array();
		$group = array();
		foreach($opCodes as $code) {
			list($tag, $i1, $i2, $j1, $j2) = $code;
			if($tag == 'equal' && $i2 - $i1 > $maxRange) {
				$group[] = array(
					$tag,
					$i1,
					min($i2, $i1 + $context),
					$j1,
					min($j2, $j1 + $context)
				);
				$groups[] = $group;
				$group = array();
				$i1 = max($i1, $i2 - $context);
				$j1 = max($j1, $j2 - $context);
			}
			$group[] = array(
				$tag,
				$i1,
				$i2,
				$j1,
				$j2
			);
		}

		if(!empty($group) && !(count($group) == 1 && $group[0][0] == 'equal')) {
			$groups[] = $group;
		}

		return $groups;
	}

	/**
	 * Return a measure of the similarity between the two sequences.
	 * This will be a float value between 0 and 1.
	 *
	 * Out of all of the ratio calculation functions, this is the most
	 * expensive to call if getMatchingBlocks or getOpCodes is yet to be
	 * called. The other calculation methods (quickRatio and realquickRatio)
	 * can be used to perform quicker calculations but may be less accurate.
	 *
	 * The ratio is calculated as (2 * number of matches) / total number of
	 * elements in both sequences.
	 *
	 * @return float The calculated ratio.
	 */
	public function Ratio()
	{
		$matches = array_reduce($this->getMatchingBlocks(), array($this, 'ratioReduce'), 0);
		return $this->calculateRatio($matches, count ($this->a) + count ($this->b));
	}

	/**
	 * Helper function to calculate the number of matches for Ratio().
	 *
	 * @param int $sum The running total for the number of matches.
	 * @param array $triple Array containing the matching block triple to add to the running total.
	 * @return int The new running total for the number of matches.
	 */
	private function ratioReduce($sum, $triple)
	{
		return $sum + ($triple[count($triple) - 1]);
	}


	/**
	 * Helper function for calculating the ratio to measure similarity for the strings.
	 * The ratio is defined as being 2 * (number of matches / total length)
	 *
	 * @param int $matches The number of matches in the two strings.
	 * @param int $length The length of the two strings.
	 * @return float The calculated ratio.
	 */
	private function calculateRatio($matches, $length=0)
	{
		if($length) {
			return 2 * ($matches / $length);
		}
		else {
			return 1;
		}
	}

	/**
	 * Helper function that provides the ability to return the value for a key
	 * in an array of it exists, or if it doesn't then return a default value.
	 * Essentially cleaner than doing a series of if(isset()) {} else {} calls.
	 *
	 * @param array $array The array to search.
	 * @param string $key The key to check that exists.
	 * @param mixed $default The value to return as the default value if the key doesn't exist.
	 * @return mixed The value from the array if the key exists or otherwise the default.
	 */
	private function arrayGetDefault($array, $key, $default)
	{
		if(isset($array[$key])) {
			return $array[$key];
		}
		else {
			return $default;
		}
	}

	/**
	 * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks
	 *
	 * @param array $a First array to compare.
	 * @param array $b Second array to compare.
	 * @return int -1, 0 or 1, as expected by the usort function.
	 */
	private function tupleSort($a, $b)
	{
		$max = max(count($a), count($b));
		for($i = 0; $i < $max; ++$i) {
			if($a[$i] < $b[$i]) {
				return -1;
			}
			else if($a[$i] > $b[$i]) {
				return 1;
			}
		}

		if(count($a) == count($b)) {
			return 0;
		}
		else if(count($a) < count($b)) {
			return -1;
		}
		else {
			return 1;
		}
	}
}
                                                Renderer/Abstract.php                                                                               0000755                 00000005752 00000000000 0010543 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Abstract class for diff renderers in PHP DiffLib.
 *
 * PHP version 5
 *
 * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the Chris Boulton nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package DiffLib
 * @author Chris Boulton <chris.boulton@interspire.com>
 * @copyright (c) 2009 Chris Boulton
 * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
 * @version 1.1
 * @link http://github.com/chrisboulton/php-diff
 */

abstract class Diff_Renderer_Abstract
{
	/**
	 * @var object Instance of the diff class that this renderer is generating the rendered diff for.
	 */
	public $diff;

	/**
	 * @var array Array of the default options that apply to this renderer.
	 */
	protected $defaultOptions = array();

	/**
	 * @var array Array containing the user applied and merged default options for the renderer.
	 */
	protected $options = array();

	/**
	 * The constructor. Instantiates the rendering engine and if options are passed,
	 * sets the options for the renderer.
	 *
	 * @param array $options Optionally, an array of the options for the renderer.
	 */
	public function __construct(array $options = array())
	{
		$this->setOptions($options);
	}

	/**
	 * Set the options of the renderer to those supplied in the passed in array.
	 * Options are merged with the default to ensure that there aren't any missing
	 * options.
	 *
	 * @param array $options Array of options to set.
	 */
	public function setOptions(array $options)
	{
		$this->options = array_merge($this->defaultOptions, $options);
	}
}                      Renderer/Html/SideBySide.php                                                                        0000755                 00000013660 00000000000 0011665 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Side by Side HTML diff generator for PHP DiffLib.
 *
 * PHP version 5
 *
 * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the Chris Boulton nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package DiffLib
 * @author Chris Boulton <chris.boulton@interspire.com>
 * @copyright (c) 2009 Chris Boulton
 * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
 * @version 1.1
 * @link http://github.com/chrisboulton/php-diff
 */

require_once(dirname(__FILE__) . '/Array.php');

class Diff_Renderer_Html_SideBySide extends Diff_Renderer_Html_Array
{
	/**
	 * Render a and return diff with changes between the two sequences
	 * displayed side by side.
	 *
	 * @return string The generated side by side diff.
	 */
	public function render()
	{
		$changes = parent::render();

		$html = '';
		if(empty($changes)) {
			return $html;
		}

		$html .= '<table class="Differences DifferencesSideBySide">';
		$html .= '<thead>';
		$html .= '<tr>';
		$html .= '<th colspan="2" width="50%">The Original Version of the file</th>';
		$html .= '<th colspan="2" width="50%">The Modified Version on your WordPress system</th>';
		$html .= '</tr>';
		$html .= '</thead>';
		foreach($changes as $i => $blocks) {
			if($i > 0) {
				$html .= '<tbody class="Skipped">';
				$html .= '<th>&hellip;</th><td>&nbsp;</td>';
				$html .= '<th>&hellip;</th><td>&nbsp;</td>';
				$html .= '</tbody>';
			}

			foreach($blocks as $change) {
				$html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
				// Equal changes should be shown on both sides of the diff
				if($change['tag'] == 'equal') {
					foreach($change['base']['lines'] as $no => $line) {
						$fromLine = $change['base']['offset'] + $no + 1;
						$toLine = $change['changed']['offset'] + $no + 1;
						$html .= '<tr>';
						$html .= '<th>'.$fromLine.'</th>';
						$html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</span></td>';
						$html .= '<th>'.$toLine.'</th>';
						$html .= '<td class="Right"><span>'.$line.'</span>&nbsp;</span></td>';
						$html .= '</tr>';
					}
				}
				// Added lines only on the right side
				else if($change['tag'] == 'insert') {
					foreach($change['changed']['lines'] as $no => $line) {
						$toLine = $change['changed']['offset'] + $no + 1;
						$html .= '<tr>';
						$html .= '<th>&nbsp;</th>';
						$html .= '<td class="Left">&nbsp;</td>';
						$html .= '<th>'.$toLine.'</th>';
						$html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</td>';
						$html .= '</tr>';
					}
				}
				// Show deleted lines only on the left side
				else if($change['tag'] == 'delete') {
					foreach($change['base']['lines'] as $no => $line) {
						$fromLine = $change['base']['offset'] + $no + 1;
						$html .= '<tr>';
						$html .= '<th>'.$fromLine.'</th>';
						$html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</td>';
						$html .= '<th>&nbsp;</th>';
						$html .= '<td class="Right">&nbsp;</td>';
						$html .= '</tr>';
					}
				}
				// Show modified lines on both sides
				else if($change['tag'] == 'replace') {
					if(count($change['base']['lines']) >= count($change['changed']['lines'])) {
						foreach($change['base']['lines'] as $no => $line) {
							$fromLine = $change['base']['offset'] + $no + 1;
							$html .= '<tr>';
							$html .= '<th>'.$fromLine.'</th>';
							$html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
							if(!isset($change['changed']['lines'][$no])) {
								$toLine = '&nbsp;';
								$changedLine = '&nbsp;';
							}
							else {
								$toLine = $change['base']['offset'] + $no + 1;
								$changedLine = '<span>'.$change['changed']['lines'][$no].'</span>';
							}
							$html .= '<th>'.$toLine.'</th>';
							$html .= '<td class="Right">'.$changedLine.'</td>';
							$html .= '</tr>';
						}
					}
					else {
						foreach($change['changed']['lines'] as $no => $changedLine) {
							if(!isset($change['base']['lines'][$no])) {
								$fromLine = '&nbsp;';
								$line = '&nbsp;';
							}
							else {
								$fromLine = $change['base']['offset'] + $no + 1;
								$line = '<span>'.$change['base']['lines'][$no].'</span>';
							}
							$html .= '<tr>';
							$html .= '<th>'.$fromLine.'</th>';
							$html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
							$toLine = $change['changed']['offset'] + $no + 1;
							$html .= '<th>'.$toLine.'</th>';
							$html .= '<td class="Right">'.$changedLine.'</td>';
							$html .= '</tr>';
						}
					}
				}
				$html .= '</tbody>';
			}
		}
		$html .= '</table>';
		return $html;
	}
}
                                                                                Renderer/Html/Array.php                                                                             0000755                 00000017074 00000000000 0010762 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?php
/**
 * Base renderer for rendering HTML based diffs for PHP DiffLib.
 *
 * PHP version 5
 *
 * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the Chris Boulton nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package DiffLib
 * @author Chris Boulton <chris.boulton@interspire.com>
 * @copyright (c) 2009 Chris Boulton
 * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
 * @version 1.1
 * @link http://github.com/chrisboulton/php-diff
 */

require_once(dirname(__FILE__) . '/../Abstract.php');

class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract
{
	/**
	 * @var array Array of the default options that apply to this renderer.
	 */
	protected $defaultOptions = array(
		'tabSize' => 4
	);

	/**
	 * Render and return an array structure suitable for generating HTML
	 * based differences. Generally called by subclasses that generate a
	 * HTML based diff and return an array of the changes to show in the diff.
	 *
	 * @return array An array of the generated chances, suitable for presentation in HTML.
	 */
	public function render()
	{
		// As we'll be modifying a & b to include our change markers,
		// we need to get the contents and store them here. That way
		// we're not going to destroy the original data
		$a = $this->diff->getA();
		$b = $this->diff->getB();

		$changes = array();
		$opCodes = $this->diff->getGroupedOpcodes();
		foreach($opCodes as $group) {
			$blocks = array();
			$lastTag = null;
			$lastBlock = 0;
			foreach($group as $code) {
				list($tag, $i1, $i2, $j1, $j2) = $code;

				if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) {
					for($i = 0; $i < ($i2 - $i1); ++$i) {
						$fromLine = $a[$i1 + $i];
						$toLine = $b[$j1 + $i];

						list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
						if($start != 0 || $end != 0) {
							$last = $end + strlen($fromLine);
							$fromLine = substr_replace($fromLine, "\0", $start, 0);
							$fromLine = substr_replace($fromLine, "\1", $last + 1, 0);
							$last = $end + strlen($toLine);
							$toLine = substr_replace($toLine, "\0", $start, 0);
							$toLine = substr_replace($toLine, "\1", $last + 1, 0);
							$a[$i1 + $i] = $fromLine;
							$b[$j1 + $i] = $toLine;
						}
					}
				}

				if($tag != $lastTag) {
					$blocks[] = array(
						'tag' => $tag,
						'base' => array(
							'offset' => $i1,
							'lines' => array()
						),
						'changed' => array(
							'offset' => $j1,
							'lines' => array()
						)
					);
					$lastBlock = count($blocks)-1;
				}

				$lastTag = $tag;

				if($tag == 'equal') {
					$lines = array_slice($a, $i1, ($i2 - $i1));
					$blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
					$lines = array_slice($b, $j1, ($j2 - $j1));
					$blocks[$lastBlock]['changed']['lines'] +=  $this->formatLines($lines);
				}
				else {
					if($tag == 'replace' || $tag == 'delete') {
						$lines = array_slice($a, $i1, ($i2 - $i1));
						$lines = $this->formatLines($lines);
						$lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
						$blocks[$lastBlock]['base']['lines'] += $lines;
					}

					if($tag == 'replace' || $tag == 'insert') {
						$lines = array_slice($b, $j1, ($j2 - $j1));
						$lines =  $this->formatLines($lines);
						$lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
						$blocks[$lastBlock]['changed']['lines'] += $lines;
					}
				}
			}
			$changes[] = $blocks;
		}
		return $changes;
	}

	/**
	 * Given two strings, determine where the changes in the two strings
	 * begin, and where the changes in the two strings end.
	 *
	 * @param string $fromLine The first string.
	 * @param string $toLine The second string.
	 * @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
	 */
	private function getChangeExtent($fromLine, $toLine)
	{
		$start = 0;
		$limit = min(strlen($fromLine), strlen($toLine));
		while($start < $limit && $fromLine[$start] == $toLine[$start]) {
			++$start;
		}
		$end = -1;
		$limit = $limit - $start;
		while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) {
			--$end;
		}
		return array(
			$start,
			$end + 1
		);
	}

	/**
	 * Format a series of lines suitable for output in a HTML rendered diff.
	 * This involves replacing tab characters with spaces, making the HTML safe
	 * for output, ensuring that double spaces are replaced with &nbsp; etc.
	 *
	 * @param array $lines Array of lines to format.
	 * @return array Array of the formatted lines.
	 */
	private function formatLines($lines)
	{
		$lines = array_map(array($this, 'ExpandTabs'), $lines);
		$lines = array_map(array($this, 'HtmlSafe'), $lines);
		foreach($lines as &$line) {
			$line = preg_replace_callback('# ( +)|^ #', array($this, 'fixSpacesCallback'), $line);
		}
		return $lines;
	}

	/**
	 * Using a callback here instead of the /e modifier in preg_replace (now deprecated).
	 *
	 * @param $matches
	 * @return string
	 */
	private function fixSpacesCallback($matches)
	{
		$spaces = (isset($matches[1]) ? $matches[1] : '');
		return $this->fixSpaces($spaces);
	}

	/**
	 * Replace a string containing spaces with a HTML representation using &nbsp;.
	 *
	 * @param string $spaces The string of spaces.
	 * @return string The HTML representation of the string.
	 */
	function fixSpaces($spaces='')
	{
		$count = strlen($spaces);
		if($count == 0) {
			return '';
		}

		$div = floor($count / 2);
		$mod = $count % 2;
		return str_repeat('&nbsp; ', $div).str_repeat('&nbsp;', $mod);
	}

	/**
	 * Replace tabs in a single line with a number of spaces as defined by the tabSize option.
	 *
	 * @param string $line The containing tabs to convert.
	 * @return string The line with the tabs converted to spaces.
	 */
	private function expandTabs($line)
	{
		return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
	}

	/**
	 * Make a string containing HTML safe for output on a page.
	 *
	 * @param string $string The string.
	 * @return string The string with the HTML characters replaced by entities.
	 */
	private function htmlSafe($string)
	{
		if (!is_string($string)) { return ''; }
		return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 