1<?php 2/** 3 * Class used internally by Diff to actually compute the diffs. 4 * 5 * This class uses the Unix `diff` program via shell_exec to compute the 6 * differences between the two input arrays. 7 * 8 * Copyright 2007-2017 Horde LLC (http://www.horde.org/) 9 * 10 * See the enclosed file COPYING for license information (LGPL). If you did 11 * not receive this file, see http://www.horde.org/licenses/lgpl21. 12 * 13 * @author Milian Wolff <mail@milianw.de> 14 * @package Text_Diff 15 */ 16class Horde_Text_Diff_Engine_Shell 17{ 18 /** 19 * Path to the diff executable 20 * 21 * @var string 22 */ 23 protected $_diffCommand = 'diff'; 24 25 /** 26 * Returns the array of differences. 27 * 28 * @param array $from_lines lines of text from old file 29 * @param array $to_lines lines of text from new file 30 * 31 * @return array all changes made (array with Horde_Text_Diff_Op_* objects) 32 */ 33 public function diff($from_lines, $to_lines) 34 { 35 array_walk($from_lines, array('Horde_Text_Diff', 'trimNewlines')); 36 array_walk($to_lines, array('Horde_Text_Diff', 'trimNewlines')); 37 38 // Execute gnu diff or similar to get a standard diff file. 39 $from_file = Horde_Util::getTempFile('Horde_Text_Diff'); 40 $to_file = Horde_Util::getTempFile('Horde_Text_Diff'); 41 $fp = fopen($from_file, 'w'); 42 fwrite($fp, implode("\n", $from_lines)); 43 fclose($fp); 44 $fp = fopen($to_file, 'w'); 45 fwrite($fp, implode("\n", $to_lines)); 46 fclose($fp); 47 $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); 48 unlink($from_file); 49 unlink($to_file); 50 51 if (is_null($diff)) { 52 // No changes were made 53 return array(new Horde_Text_Diff_Op_Copy($from_lines)); 54 } 55 56 $from_line_no = 1; 57 $to_line_no = 1; 58 $edits = array(); 59 60 // Get changed lines by parsing something like: 61 // 0a1,2 62 // 1,2c4,6 63 // 1,5d6 64 preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, 65 $matches, PREG_SET_ORDER); 66 67 foreach ($matches as $match) { 68 if (!isset($match[5])) { 69 // This paren is not set every time (see regex). 70 $match[5] = false; 71 } 72 73 if ($match[3] == 'a') { 74 $from_line_no--; 75 } 76 77 if ($match[3] == 'd') { 78 $to_line_no--; 79 } 80 81 if ($from_line_no < $match[1] || $to_line_no < $match[4]) { 82 // copied lines 83 assert($match[1] - $from_line_no == $match[4] - $to_line_no); 84 $edits[] = 85 new Horde_Text_Diff_Op_Copy( 86 $this->_getLines($from_lines, $from_line_no, $match[1] - 1), 87 $this->_getLines($to_lines, $to_line_no, $match[4] - 1)); 88 } 89 90 switch ($match[3]) { 91 case 'd': 92 // deleted lines 93 $edits[] = 94 new Horde_Text_Diff_Op_Delete( 95 $this->_getLines($from_lines, $from_line_no, $match[2])); 96 $to_line_no++; 97 break; 98 99 case 'c': 100 // changed lines 101 $edits[] = 102 new Horde_Text_Diff_Op_Change( 103 $this->_getLines($from_lines, $from_line_no, $match[2]), 104 $this->_getLines($to_lines, $to_line_no, $match[5])); 105 break; 106 107 case 'a': 108 // added lines 109 $edits[] = 110 new Horde_Text_Diff_Op_Add( 111 $this->_getLines($to_lines, $to_line_no, $match[5])); 112 $from_line_no++; 113 break; 114 } 115 } 116 117 if (!empty($from_lines)) { 118 // Some lines might still be pending. Add them as copied 119 $edits[] = 120 new Horde_Text_Diff_Op_Copy( 121 $this->_getLines($from_lines, $from_line_no, 122 $from_line_no + count($from_lines) - 1), 123 $this->_getLines($to_lines, $to_line_no, 124 $to_line_no + count($to_lines) - 1)); 125 } 126 127 return $edits; 128 } 129 130 /** 131 * Get lines from either the old or new text 132 * 133 * @access private 134 * 135 * @param array &$text_lines Either $from_lines or $to_lines 136 * @param int &$line_no Current line number 137 * @param int $end Optional end line, when we want to chop more 138 * than one line. 139 * 140 * @return array The chopped lines 141 */ 142 protected function _getLines(&$text_lines, &$line_no, $end = false) 143 { 144 if (!empty($end)) { 145 $lines = array(); 146 // We can shift even more 147 while ($line_no <= $end) { 148 $lines[] = array_shift($text_lines); 149 $line_no++; 150 } 151 } else { 152 $lines = array(array_shift($text_lines)); 153 $line_no++; 154 } 155 156 return $lines; 157 } 158} 159