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