163 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			163 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
|  | <?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 http://opensource.org/licenses/lgpl-license.php. | ||
|  |  * | ||
|  |  * @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; | ||
|  |     } | ||
|  | 
 | ||
|  | } |