balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

Diff.php (12915B)


      1 <?php
      2 /**
      3  * General API for generating and formatting diffs - the differences between
      4  * two sequences of strings.
      5  *
      6  * The original PHP version of this code was written by Geoffrey T. Dairiki
      7  * <dairiki@dairiki.org>, and is used/adapted with his permission.
      8  *
      9  * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
     10  * Copyright 2004-2010 The Horde Project (http://www.horde.org/)
     11  *
     12  * See the enclosed file COPYING for license information (LGPL). If you did
     13  * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
     14  *
     15  * @package Text_Diff
     16  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
     17  */
     18 class Text_Diff {
     19 
     20     /**
     21      * Array of changes.
     22      *
     23      * @var array
     24      */
     25     var $_edits;
     26 
     27     /**
     28      * Computes diffs between sequences of strings.
     29      *
     30      * @param string $engine     Name of the diffing engine to use.  'auto'
     31      *                           will automatically select the best.
     32      * @param array $params      Parameters to pass to the diffing engine.
     33      *                           Normally an array of two arrays, each
     34      *                           containing the lines from a file.
     35      */
     36     function __construct( $engine, $params )
     37     {
     38         // Backward compatibility workaround.
     39         if (!is_string($engine)) {
     40             $params = array($engine, $params);
     41             $engine = 'auto';
     42         }
     43 
     44         if ($engine == 'auto') {
     45             $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';
     46         } else {
     47             $engine = basename($engine);
     48         }
     49 
     50         // WP #7391
     51         require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php';
     52         $class = 'Text_Diff_Engine_' . $engine;
     53         $diff_engine = new $class();
     54 
     55         $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
     56     }
     57 
     58 	/**
     59 	 * PHP4 constructor.
     60 	 */
     61 	public function Text_Diff( $engine, $params ) {
     62 		self::__construct( $engine, $params );
     63 	}
     64 
     65     /**
     66      * Returns the array of differences.
     67      */
     68     function getDiff()
     69     {
     70         return $this->_edits;
     71     }
     72 
     73     /**
     74      * returns the number of new (added) lines in a given diff.
     75      *
     76      * @since Text_Diff 1.1.0
     77      *
     78      * @return int The number of new lines
     79      */
     80     function countAddedLines()
     81     {
     82         $count = 0;
     83         foreach ($this->_edits as $edit) {
     84             if (is_a($edit, 'Text_Diff_Op_add') ||
     85                 is_a($edit, 'Text_Diff_Op_change')) {
     86                 $count += $edit->nfinal();
     87             }
     88         }
     89         return $count;
     90     }
     91 
     92     /**
     93      * Returns the number of deleted (removed) lines in a given diff.
     94      *
     95      * @since Text_Diff 1.1.0
     96      *
     97      * @return int The number of deleted lines
     98      */
     99     function countDeletedLines()
    100     {
    101         $count = 0;
    102         foreach ($this->_edits as $edit) {
    103             if (is_a($edit, 'Text_Diff_Op_delete') ||
    104                 is_a($edit, 'Text_Diff_Op_change')) {
    105                 $count += $edit->norig();
    106             }
    107         }
    108         return $count;
    109     }
    110 
    111     /**
    112      * Computes a reversed diff.
    113      *
    114      * Example:
    115      * <code>
    116      * $diff = new Text_Diff($lines1, $lines2);
    117      * $rev = $diff->reverse();
    118      * </code>
    119      *
    120      * @return Text_Diff  A Diff object representing the inverse of the
    121      *                    original diff.  Note that we purposely don't return a
    122      *                    reference here, since this essentially is a clone()
    123      *                    method.
    124      */
    125     function reverse()
    126     {
    127         if (version_compare(zend_version(), '2', '>')) {
    128             $rev = clone($this);
    129         } else {
    130             $rev = $this;
    131         }
    132         $rev->_edits = array();
    133         foreach ($this->_edits as $edit) {
    134             $rev->_edits[] = $edit->reverse();
    135         }
    136         return $rev;
    137     }
    138 
    139     /**
    140      * Checks for an empty diff.
    141      *
    142      * @return bool True if two sequences were identical.
    143      */
    144     function isEmpty()
    145     {
    146         foreach ($this->_edits as $edit) {
    147             if (!is_a($edit, 'Text_Diff_Op_copy')) {
    148                 return false;
    149             }
    150         }
    151         return true;
    152     }
    153 
    154     /**
    155      * Computes the length of the Longest Common Subsequence (LCS).
    156      *
    157      * This is mostly for diagnostic purposes.
    158      *
    159      * @return int The length of the LCS.
    160      */
    161     function lcs()
    162     {
    163         $lcs = 0;
    164         foreach ($this->_edits as $edit) {
    165             if (is_a($edit, 'Text_Diff_Op_copy')) {
    166                 $lcs += count($edit->orig);
    167             }
    168         }
    169         return $lcs;
    170     }
    171 
    172     /**
    173      * Gets the original set of lines.
    174      *
    175      * This reconstructs the $from_lines parameter passed to the constructor.
    176      *
    177      * @return array  The original sequence of strings.
    178      */
    179     function getOriginal()
    180     {
    181         $lines = array();
    182         foreach ($this->_edits as $edit) {
    183             if ($edit->orig) {
    184                 array_splice($lines, count($lines), 0, $edit->orig);
    185             }
    186         }
    187         return $lines;
    188     }
    189 
    190     /**
    191      * Gets the final set of lines.
    192      *
    193      * This reconstructs the $to_lines parameter passed to the constructor.
    194      *
    195      * @return array  The sequence of strings.
    196      */
    197     function getFinal()
    198     {
    199         $lines = array();
    200         foreach ($this->_edits as $edit) {
    201             if ($edit->final) {
    202                 array_splice($lines, count($lines), 0, $edit->final);
    203             }
    204         }
    205         return $lines;
    206     }
    207 
    208     /**
    209      * Removes trailing newlines from a line of text. This is meant to be used
    210      * with array_walk().
    211      *
    212      * @param string $line  The line to trim.
    213      * @param int    $key   The index of the line in the array. Not used.
    214      */
    215     static function trimNewlines(&$line, $key)
    216     {
    217         $line = str_replace(array("\n", "\r"), '', $line);
    218     }
    219 
    220     /**
    221      * Determines the location of the system temporary directory.
    222      *
    223      * @access protected
    224      *
    225      * @return string  A directory name which can be used for temp files.
    226      *                 Returns false if one could not be found.
    227      */
    228     static function _getTempDir()
    229     {
    230         $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp',
    231                                'c:\windows\temp', 'c:\winnt\temp');
    232 
    233         /* Try PHP's upload_tmp_dir directive. */
    234         $tmp = ini_get('upload_tmp_dir');
    235 
    236         /* Otherwise, try to determine the TMPDIR environment variable. */
    237         if (!strlen($tmp)) {
    238             $tmp = getenv('TMPDIR');
    239         }
    240 
    241         /* If we still cannot determine a value, then cycle through a list of
    242          * preset possibilities. */
    243         while (!strlen($tmp) && count($tmp_locations)) {
    244             $tmp_check = array_shift($tmp_locations);
    245             if (@is_dir($tmp_check)) {
    246                 $tmp = $tmp_check;
    247             }
    248         }
    249 
    250         /* If it is still empty, we have failed, so return false; otherwise
    251          * return the directory determined. */
    252         return strlen($tmp) ? $tmp : false;
    253     }
    254 
    255     /**
    256      * Checks a diff for validity.
    257      *
    258      * This is here only for debugging purposes.
    259      */
    260     function _check($from_lines, $to_lines)
    261     {
    262         if (serialize($from_lines) != serialize($this->getOriginal())) {
    263             trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
    264         }
    265         if (serialize($to_lines) != serialize($this->getFinal())) {
    266             trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
    267         }
    268 
    269         $rev = $this->reverse();
    270         if (serialize($to_lines) != serialize($rev->getOriginal())) {
    271             trigger_error("Reversed original doesn't match", E_USER_ERROR);
    272         }
    273         if (serialize($from_lines) != serialize($rev->getFinal())) {
    274             trigger_error("Reversed final doesn't match", E_USER_ERROR);
    275         }
    276 
    277         $prevtype = null;
    278         foreach ($this->_edits as $edit) {
    279             if ($edit instanceof $prevtype) {
    280                 trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
    281             }
    282             $prevtype = get_class($edit);
    283         }
    284 
    285         return true;
    286     }
    287 
    288 }
    289 
    290 /**
    291  * @package Text_Diff
    292  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
    293  */
    294 class Text_MappedDiff extends Text_Diff {
    295 
    296     /**
    297      * Computes a diff between sequences of strings.
    298      *
    299      * This can be used to compute things like case-insensitve diffs, or diffs
    300      * which ignore changes in white-space.
    301      *
    302      * @param array $from_lines         An array of strings.
    303      * @param array $to_lines           An array of strings.
    304      * @param array $mapped_from_lines  This array should have the same size
    305      *                                  number of elements as $from_lines.  The
    306      *                                  elements in $mapped_from_lines and
    307      *                                  $mapped_to_lines are what is actually
    308      *                                  compared when computing the diff.
    309      * @param array $mapped_to_lines    This array should have the same number
    310      *                                  of elements as $to_lines.
    311      */
    312     function __construct($from_lines, $to_lines,
    313                              $mapped_from_lines, $mapped_to_lines)
    314     {
    315         assert(count($from_lines) == count($mapped_from_lines));
    316         assert(count($to_lines) == count($mapped_to_lines));
    317 
    318         parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
    319 
    320         $xi = $yi = 0;
    321         for ($i = 0; $i < count($this->_edits); $i++) {
    322             $orig = &$this->_edits[$i]->orig;
    323             if (is_array($orig)) {
    324                 $orig = array_slice($from_lines, $xi, count($orig));
    325                 $xi += count($orig);
    326             }
    327 
    328             $final = &$this->_edits[$i]->final;
    329             if (is_array($final)) {
    330                 $final = array_slice($to_lines, $yi, count($final));
    331                 $yi += count($final);
    332             }
    333         }
    334     }
    335 
    336 	/**
    337 	 * PHP4 constructor.
    338 	 */
    339 	public function Text_MappedDiff( $from_lines, $to_lines,
    340                              $mapped_from_lines, $mapped_to_lines ) {
    341 		self::__construct( $from_lines, $to_lines,
    342                              $mapped_from_lines, $mapped_to_lines );
    343 	}
    344 
    345 }
    346 
    347 /**
    348  * @package Text_Diff
    349  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
    350  *
    351  * @access private
    352  */
    353 class Text_Diff_Op {
    354 
    355     var $orig;
    356     var $final;
    357 
    358     function &reverse()
    359     {
    360         trigger_error('Abstract method', E_USER_ERROR);
    361     }
    362 
    363     function norig()
    364     {
    365         return $this->orig ? count($this->orig) : 0;
    366     }
    367 
    368     function nfinal()
    369     {
    370         return $this->final ? count($this->final) : 0;
    371     }
    372 
    373 }
    374 
    375 /**
    376  * @package Text_Diff
    377  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
    378  *
    379  * @access private
    380  */
    381 class Text_Diff_Op_copy extends Text_Diff_Op {
    382 
    383 	/**
    384 	 * PHP5 constructor.
    385 	 */
    386     function __construct( $orig, $final = false )
    387     {
    388         if (!is_array($final)) {
    389             $final = $orig;
    390         }
    391         $this->orig = $orig;
    392         $this->final = $final;
    393     }
    394 
    395 	/**
    396 	 * PHP4 constructor.
    397 	 */
    398 	public function Text_Diff_Op_copy( $orig, $final = false ) {
    399 		self::__construct( $orig, $final );
    400 	}
    401 
    402     function &reverse()
    403     {
    404         $reverse = new Text_Diff_Op_copy($this->final, $this->orig);
    405         return $reverse;
    406     }
    407 
    408 }
    409 
    410 /**
    411  * @package Text_Diff
    412  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
    413  *
    414  * @access private
    415  */
    416 class Text_Diff_Op_delete extends Text_Diff_Op {
    417 
    418 	/**
    419 	 * PHP5 constructor.
    420 	 */
    421 	function __construct( $lines )
    422     {
    423         $this->orig = $lines;
    424         $this->final = false;
    425     }
    426 
    427 	/**
    428 	 * PHP4 constructor.
    429 	 */
    430 	public function Text_Diff_Op_delete( $lines ) {
    431 		self::__construct( $lines );
    432 	}
    433 
    434     function &reverse()
    435     {
    436         $reverse = new Text_Diff_Op_add($this->orig);
    437         return $reverse;
    438     }
    439 
    440 }
    441 
    442 /**
    443  * @package Text_Diff
    444  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
    445  *
    446  * @access private
    447  */
    448 class Text_Diff_Op_add extends Text_Diff_Op {
    449 
    450 	/**
    451 	 * PHP5 constructor.
    452 	 */
    453     function __construct( $lines )
    454     {
    455         $this->final = $lines;
    456         $this->orig = false;
    457     }
    458 
    459 	/**
    460 	 * PHP4 constructor.
    461 	 */
    462 	public function Text_Diff_Op_add( $lines ) {
    463 		self::__construct( $lines );
    464 	}
    465 
    466     function &reverse()
    467     {
    468         $reverse = new Text_Diff_Op_delete($this->final);
    469         return $reverse;
    470     }
    471 
    472 }
    473 
    474 /**
    475  * @package Text_Diff
    476  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
    477  *
    478  * @access private
    479  */
    480 class Text_Diff_Op_change extends Text_Diff_Op {
    481 
    482 	/**
    483 	 * PHP5 constructor.
    484 	 */
    485     function __construct( $orig, $final )
    486     {
    487         $this->orig = $orig;
    488         $this->final = $final;
    489     }
    490 
    491 	/**
    492 	 * PHP4 constructor.
    493 	 */
    494 	public function Text_Diff_Op_change( $orig, $final ) {
    495 		self::__construct( $orig, $final );
    496 	}
    497 
    498     function &reverse()
    499     {
    500         $reverse = new Text_Diff_Op_change($this->final, $this->orig);
    501         return $reverse;
    502     }
    503 
    504 }