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 | * $Horde: framework/Text_Diff/Diff/Engine/shell.php,v 1.8 2008/01/04 10:07:50 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 | }
|
---|