35        array_walk($from_lines, array($this, 
'_trimNewlines'));
 
   36        array_walk($to_lines, array($this, 
'_trimNewlines'));
 
   38        if (extension_loaded(
'xdiff')) {
 
   44        $this->_edits = 
$engine->diff($from_lines, $to_lines);
 
   73        $rev->_edits = array();
 
   74        foreach ($this->_edits as $edit) {
 
   75            $rev->_edits[] = $edit->reverse();
 
   87        foreach ($this->_edits as $edit) {
 
  105        foreach ($this->_edits as $edit) {
 
  107                $lcs += count($edit->orig);
 
  123        foreach ($this->_edits as $edit) {
 
  125                array_splice($lines, count($lines), 0, $edit->orig);
 
  141        foreach ($this->_edits as $edit) {
 
  143                array_splice($lines, count($lines), 0, $edit->final);
 
  158        $line = str_replace(array(
"\n", 
"\r"), 
'', $line);
 
  166    public function _check($from_lines, $to_lines)
 
  168        if (serialize($from_lines) != serialize($this->
getOriginal())) {
 
  169            trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
 
  171        if (serialize($to_lines) != serialize($this->
getFinal())) {
 
  172            trigger_error(
"Reconstructed final doesn't match", E_USER_ERROR);
 
  176        if (serialize($to_lines) != serialize($rev->getOriginal())) {
 
  177            trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
 
  179        if (serialize($from_lines) != serialize($rev->getFinal())) {
 
  180            trigger_error(
"Reversed final doesn't match", E_USER_ERROR);
 
  184        foreach ($this->_edits as $edit) {
 
  185            if ($prevtype == get_class($edit)) {
 
  186                trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
 
  188            $prevtype = get_class($edit);
 
  226        assert(count($from_lines) == count($mapped_from_lines));
 
  227        assert(count($to_lines) == count($mapped_to_lines));
 
  229        parent::__construct($mapped_from_lines, $mapped_to_lines);
 
  232        for (
$i = 0; 
$i < count($this->_edits); 
$i++) {
 
  233            $orig = &$this->_edits[
$i]->orig;
 
  234            if (is_array($orig)) {
 
  235                $orig = array_slice($from_lines, $xi, count($orig));
 
  239            $final = &$this->_edits[
$i]->final;
 
  240            if (is_array($final)) {
 
  241                $final = array_slice($to_lines, $yi, count($final));
 
  242                $yi += count($final);
 
  260    public function diff($from_lines, $to_lines)
 
  263        $from_string = implode(
"\n", $from_lines);
 
  264        $to_string = implode(
"\n", $to_lines);
 
  267        $diff = xdiff_string_diff($from_string, $to_string, count($to_lines));
 
  268        $diff = explode(
"\n", $diff);
 
  279        foreach ($diff as $line) {
 
  325    public function diff($from_lines, $to_lines)
 
  327        $n_from = count($from_lines);
 
  328        $n_to = count($to_lines);
 
  330        $this->xchanged = $this->ychanged = array();
 
  331        $this->xv = $this->yv = array();
 
  332        $this->xind = $this->yind = array();
 
  334        unset($this->in_seq);
 
  338        for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
 
  339            if ($from_lines[$skip] != $to_lines[$skip]) {
 
  342            $this->xchanged[$skip] = $this->ychanged[$skip] = 
false;
 
  348        for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
 
  349            if ($from_lines[$xi] != $to_lines[$yi]) {
 
  352            $this->xchanged[$xi] = $this->ychanged[$yi] = 
false;
 
  356        for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
 
  357            $xhash[$from_lines[$xi]] = 1;
 
  359        for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
 
  360            $line = $to_lines[$yi];
 
  361            if (($this->ychanged[$yi] = empty($xhash[$line]))) {
 
  368        for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
 
  369            $line = $from_lines[$xi];
 
  370            if (($this->xchanged[$xi] = empty($yhash[$line]))) {
 
  378        $this->
_compareseq(0, count($this->xv), 0, count($this->yv));
 
  387        while ($xi < $n_from || $yi < $n_to) {
 
  388            assert($yi < $n_to || $this->xchanged[$xi]);
 
  389            assert($xi < $n_from || $this->ychanged[$yi]);
 
  393            while ($xi < $n_from && $yi < $n_to
 
  394                   && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
 
  395                $copy[] = $from_lines[$xi++];
 
  404            while ($xi < $n_from && $this->xchanged[$xi]) {
 
  405                $delete[] = $from_lines[$xi++];
 
  409            while ($yi < $n_to && $this->ychanged[$yi]) {
 
  410                $add[] = $to_lines[$yi++];
 
  413            if ($delete && $add) {
 
  441    public function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)
 
  445        if ($xlim - $xoff > $ylim - $yoff) {
 
  449            list($xoff, $xlim, $yoff, $ylim)
 
  450                = array($yoff, $ylim, $xoff, $xlim);
 
  454            for (
$i = $ylim - 1; 
$i >= $yoff; 
$i--) {
 
  455                $ymatches[$this->xv[
$i]][] = 
$i;
 
  458            for (
$i = $ylim - 1; 
$i >= $yoff; 
$i--) {
 
  459                $ymatches[$this->yv[
$i]][] = 
$i;
 
  464        $this->seq[0] = $yoff - 1;
 
  465        $this->in_seq = array();
 
  468        $numer = $xlim - $xoff + $nchunks - 1;
 
  470        for ($chunk = 0; $chunk < $nchunks; $chunk++) {
 
  472                for (
$i = 0; 
$i <= $this->lcs; 
$i++) {
 
  473                    $ymids[
$i][$chunk - 1] = $this->seq[
$i];
 
  477            $x1 = $xoff + (int) (($numer + ($xlim - $xoff) * $chunk) / $nchunks);
 
  478            for (; 
$x < $x1; 
$x++) {
 
  479                $line = $flip ? $this->yv[
$x] : $this->xv[
$x];
 
  480                if (empty($ymatches[$line])) {
 
  483                $matches = $ymatches[$line];
 
  484                foreach ($matches as 
$y) {
 
  485                    if (empty($this->in_seq[
$y])) {
 
  488                        $ymids[$k] = $ymids[$k - 1];
 
  493                while (list($junk, 
$y) = each($matches)) {
 
  494                    if (
$y > $this->seq[$k - 1]) {
 
  495                        assert($y < $this->seq[$k]);
 
  498                        $this->in_seq[$this->seq[$k]] = 
false;
 
  500                        $this->in_seq[
$y] = 1;
 
  501                    } elseif (empty($this->in_seq[
$y])) {
 
  504                        $ymids[$k] = $ymids[$k - 1];
 
  510        $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
 
  511        $ymid = $ymids[$this->lcs];
 
  512        for (
$n = 0; 
$n < $nchunks - 1; 
$n++) {
 
  513            $x1 = $xoff + (int) (($numer + ($xlim - $xoff) * 
$n) / $nchunks);
 
  515            $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
 
  517        $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
 
  519        return array($this->lcs, $seps);
 
  525        if (
$end == 0 || $ypos > $this->seq[
$end]) {
 
  526            $this->seq[++$this->lcs] = $ypos;
 
  527            $this->in_seq[$ypos] = 1;
 
  532        while ($beg < 
$end) {
 
  533            $mid = (int) (($beg + 
$end) / 2);
 
  534            if ($ypos > $this->seq[$mid]) {
 
  541        assert($ypos != $this->seq[
$end]);
 
  543        $this->in_seq[$this->seq[
$end]] = 
false;
 
  544        $this->seq[
$end] = $ypos;
 
  545        $this->in_seq[$ypos] = 1;
 
  564        while ($xoff < $xlim && $yoff < $ylim
 
  565               && $this->xv[$xoff] == $this->yv[$yoff]) {
 
  571        while ($xlim > $xoff && $ylim > $yoff
 
  572               && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
 
  577        if ($xoff == $xlim || $yoff == $ylim) {
 
  583            $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
 
  585                = $this->
_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
 
  591            while ($yoff < $ylim) {
 
  592                $this->ychanged[$this->yind[$yoff++]] = 1;
 
  594            while ($xoff < $xlim) {
 
  595                $this->xchanged[$this->xind[$xoff++]] = 1;
 
  601            while ($pt2 = next($seps)) {
 
  602                $this->
_compareseq($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
 
  625        assert(count($lines) == count(
$changed));
 
  626        $len = count($lines);
 
  627        $other_len = count($other_changed);
 
  641            while ($j < $other_len && $other_changed[$j]) {
 
  646                assert($j < $other_len && !$other_changed[$j]);
 
  649                while ($j < $other_len && $other_changed[$j]) {
 
  680                    while ($other_changed[--$j]) {
 
  683                    assert($j >= 0 && !$other_changed[$j]);
 
  690                $corresponding = $j < $other_len ? 
$i : $len;
 
  697                while (
$i < $len && $lines[
$start] == $lines[
$i]) {
 
  704                    assert($j < $other_len && !$other_changed[$j]);
 
  706                    if ($j < $other_len && $other_changed[$j]) {
 
  708                        while ($j < $other_len && $other_changed[$j]) {
 
  713            } 
while ($runlength != 
$i - 
$start);
 
  717            while ($corresponding < 
$i) {
 
  721                while ($other_changed[--$j]) {
 
  724                assert($j >= 0 && !$other_changed[$j]);
 
  743        trigger_error(
'Abstract method', E_USER_ERROR);
 
  748        return $this->orig ? count($this->orig) : 0;
 
  753        return $this->
final ? count($this->
final) : 0;
 
  790        $this->orig = $lines;
 
  791        $this->
final = 
false;
 
  810        $this->
final = $lines;
 
An exception for terminatinating execution or to throw for unit testing.
diff($from_lines, $to_lines)
_diag($xoff, $xlim, $yoff, $ylim, $nchunks)
Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, XLIM) and (YOFF,...
_compareseq($xoff, $xlim, $yoff, $ylim)
Finds LCS of two sequences.
_shiftBoundaries($lines, &$changed, $other_changed)
Adjusts inserts/deletes of identical lines to join changes as much as possible.
diff($from_lines, $to_lines)
__construct($orig, $final)
__construct($orig, $final=false)
getOriginal()
Gets the original set of lines.
isEmpty()
Checks for an empty diff.
reverse()
Computes a reversed diff.
getFinal()
Gets the final set of lines.
__construct($from_lines, $to_lines)
Computes diffs between sequences of strings.
lcs()
Computes the length of the Longest Common Subsequence (LCS).
getDiff()
Returns the array of differences.
_trimNewlines(&$line, $key)
Removes trailing newlines from a line of text.
_check($from_lines, $to_lines)
Checks a diff for validity.
__construct( $from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines)
Computes a diff between sequences of strings.