1 | 5 | simandl | <?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 | | | * $Horde: framework/Text_Diff/Diff/Engine/shell.php,v 1.6.2.3 2008/01/04 10:37:27 jan Exp $ |
9 | | | * |
10 | | | * Copyright 2007-2008 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 | | | * @author Milian Wolff <mail@milianw.de> |
16 | | | * @package Text_Diff |
17 | | | * @since 0.3.0 |
18 | | | */ |
19 | | | class Text_Diff_Engine_shell { |
20 | | | |
21 | | | /** |
22 | | | * Path to the diff executable |
23 | | | * |
24 | | | * @var string |
25 | | | */ |
26 | | | var $_diffCommand = 'diff'; |
27 | | | |
28 | | | /** |
29 | | | * Returns the array of differences. |
30 | | | * |
31 | | | * @param array $from_lines lines of text from old file |
32 | | | * @param array $to_lines lines of text from new file |
33 | | | * |
34 | | | * @return array all changes made (array with Text_Diff_Op_* objects) |
35 | | | */ |
36 | | | function diff($from_lines, $to_lines) |
37 | | | { |
38 | | | array_walk($from_lines, array('Text_Diff', 'trimNewlines')); |
39 | | | array_walk($to_lines, array('Text_Diff', 'trimNewlines')); |
40 | | | |
41 | | | $temp_dir = Text_Diff::_getTempDir(); |
42 | | | |
43 | | | // Execute gnu diff or similar to get a standard diff file. |
44 | | | $from_file = tempnam($temp_dir, 'Text_Diff'); |
45 | | | $to_file = tempnam($temp_dir, 'Text_Diff'); |
46 | | | $fp = fopen($from_file, 'w'); |
47 | | | fwrite($fp, implode("\n", $from_lines)); |
48 | | | fclose($fp); |
49 | | | $fp = fopen($to_file, 'w'); |
50 | | | fwrite($fp, implode("\n", $to_lines)); |
51 | | | fclose($fp); |
52 | | | $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); |
53 | | | unlink($from_file); |
54 | | | unlink($to_file); |
55 | | | |
56 | | | if (is_null($diff)) { |
57 | | | // No changes were made |
58 | | | return array(new Text_Diff_Op_copy($from_lines)); |
59 | | | } |
60 | | | |
61 | | | $from_line_no = 1; |
62 | | | $to_line_no = 1; |
63 | | | $edits = array(); |
64 | | | |
65 | | | // Get changed lines by parsing something like: |
66 | | | // 0a1,2 |
67 | | | // 1,2c4,6 |
68 | | | // 1,5d6 |
69 | | | preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, |
70 | | | $matches, PREG_SET_ORDER); |
71 | | | |
72 | | | foreach ($matches as $match) { |
73 | | | if (!isset($match[5])) { |
74 | | | // This paren is not set every time (see regex). |
75 | | | $match[5] = false; |
76 | | | } |
77 | | | |
78 | | | if ($match[3] == 'a') { |
79 | | | $from_line_no--; |
80 | | | } |
81 | | | |
82 | | | if ($match[3] == 'd') { |
83 | | | $to_line_no--; |
84 | | | } |
85 | | | |
86 | | | if ($from_line_no < $match[1] || $to_line_no < $match[4]) { |
87 | | | // copied lines |
88 | | | assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); |
89 | | | array_push($edits, |
90 | | | new Text_Diff_Op_copy( |
91 | | | $this->_getLines($from_lines, $from_line_no, $match[1] - 1), |
92 | | | $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); |
93 | | | } |
94 | | | |
95 | | | switch ($match[3]) { |
96 | | | case 'd': |
97 | | | // deleted lines |
98 | | | array_push($edits, |
99 | | | new Text_Diff_Op_delete( |
100 | | | $this->_getLines($from_lines, $from_line_no, $match[2]))); |
101 | | | $to_line_no++; |
102 | | | break; |
103 | | | |
104 | | | case 'c': |
105 | | | // changed lines |
106 | | | array_push($edits, |
107 | | | new Text_Diff_Op_change( |
108 | | | $this->_getLines($from_lines, $from_line_no, $match[2]), |
109 | | | $this->_getLines($to_lines, $to_line_no, $match[5]))); |
110 | | | break; |
111 | | | |
112 | | | case 'a': |
113 | | | // added lines |
114 | | | array_push($edits, |
115 | | | new Text_Diff_Op_add( |
116 | | | $this->_getLines($to_lines, $to_line_no, $match[5]))); |
117 | | | $from_line_no++; |
118 | | | break; |
119 | | | } |
120 | | | } |
121 | | | |
122 | | | if (!empty($from_lines)) { |
123 | | | // Some lines might still be pending. Add them as copied |
124 | | | array_push($edits, |
125 | | | new Text_Diff_Op_copy( |
126 | | | $this->_getLines($from_lines, $from_line_no, |
127 | | | $from_line_no + count($from_lines) - 1), |
128 | | | $this->_getLines($to_lines, $to_line_no, |
129 | | | $to_line_no + count($to_lines) - 1))); |
130 | | | } |
131 | | | |
132 | | | return $edits; |
133 | | | } |
134 | | | |
135 | | | /** |
136 | | | * Get lines from either the old or new text |
137 | | | * |
138 | | | * @access private |
139 | | | * |
140 | | | * @param array &$text_lines Either $from_lines or $to_lines |
141 | | | * @param int &$line_no Current line number |
142 | | | * @param int $end Optional end line, when we want to chop more |
143 | | | * than one line. |
144 | | | * |
145 | | | * @return array The chopped lines |
146 | | | */ |
147 | | | function _getLines(&$text_lines, &$line_no, $end = false) |
148 | | | { |
149 | | | if (!empty($end)) { |
150 | | | $lines = array(); |
151 | | | // We can shift even more |
152 | | | while ($line_no <= $end) { |
153 | | | array_push($lines, array_shift($text_lines)); |
154 | | | $line_no++; |
155 | | | } |
156 | | | } else { |
157 | | | $lines = array(array_shift($text_lines)); |
158 | | | $line_no++; |
159 | | | } |
160 | | | |
161 | | | return $lines; |
162 | | | } |
163 | | | |
164 | | | } |