- Rewrote the Diff class to be much more easy-to-read and extend
[viewsvn.git] / includes / class_diff.php
1 <?php
2 /*=====================================================================*\
3 || ###################################################################
4 || # ViewSVN [#]version[#]
5 || # Copyright ©2002-[#]year[#] Blue Static
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version [#]gpl[#] of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Diff
24 *
25 * This class parses a diff file from a unified diff file and turns it
26 * into a HTML-prettied output
27 *
28 * @author Blue Static
29 * @copyright Copyright (c)2002 - [#]year[#], Blue Static
30 * @version $Revision$
31 * @package ViewSVN
32 *
33 */
34 class Diff
35 {
36 /**
37 * Raw unified diff text
38 * @var array
39 */
40 private $raw;
41
42 /**
43 * The current index (file)
44 * @var string
45 */
46 private $index;
47
48 /**
49 * The array index of where capturing of diff data starts
50 * @var integer
51 */
52 private $captureStart = 0;
53
54 /**
55 * Current hunk that is being processed
56 * @var integer
57 */
58 private $hunkId = 0;
59
60 /**
61 * The lines on which hunks start
62 * @var array
63 */
64 private $hunkLines = array('old' => array('line' => 0, 'count' => 0), 'new' => array('line' => 0, 'count' => 0));
65
66 /**
67 * The parsed diff output
68 * @var array
69 */
70 private $diff = array();
71
72 // ###################################################################
73 /**
74 * Constructor
75 *
76 * @param string Raw unified diff output
77 */
78 public function __construct($raw)
79 {
80 $this->raw = $raw;
81 }
82
83 // ###################################################################
84 /**
85 * This function initiates the parsing process and returns the output
86 * in hunks, line-by-line
87 *
88 * @return array Array of outputted hunked information
89 */
90 public function fetch()
91 {
92 for ($i = 0; $i < sizeof($this->raw); $i++)
93 {
94 $line = $this->raw[$i];
95
96 if (preg_match('/^Index: (.*)/', $line, $index))
97 {
98 $this->_processFile(array_slice($this->raw, $this->captureStart, $i - $this->captureStart));
99 $this->captureStart = $i;
100 $this->index = $index[1];
101 }
102 }
103 $this->_processFile(array_slice($this->raw, $this->captureStart));
104
105 return $this->diff;
106 }
107
108 // ###################################################################
109 /**
110 * Processes a file part of a diff
111 *
112 * @param array An array of lines that make up a file chunk
113 */
114 private function _processFile($lines)
115 {
116 preg_match_all('/[\-\+]{3}\s+(.*)\s+\(revision ([0-9]*)\)/', $lines[2] . "\n" . $lines[3], $revs);
117 $this->diff[$this->index]['revision']['low'] = $revs[2][0];
118 $this->diff[$this->index]['revision']['high'] = $revs[2][1];
119
120 $this->hunkId = 0;
121
122 $captureStart = 4;
123 for ($i = 4; $i < sizeof($lines); $i++)
124 {
125 $line = $lines[$i];
126
127 if (preg_match('/^@@ \-([0-9]*),([0-9]*) \+([0-9]*),([0-9]*) @@/', $line, $hunkHead))
128 {
129 $this->hunkLines = array('old' => array('line' => $hunkHead[1], 'count' => $hunkHead[2]), 'new' => array('line' => $hunkHead[3], 'count' => $hunkHead[4]));
130 $this->_processHunk(array_slice($lines, $captureStart, $i - $captureStart));
131 $captureStart = $i;
132 $this->hunkId++;
133 }
134 else if (preg_match('#^__*_$#', $line))
135 {
136 $this->_processHunk(array_slice($lines, $captureStart, $i - $captureStart));
137 $this->_processProperties(array_slice($lines, $i));
138 return;
139 }
140 }
141 $this->_processHunk(array_slice($lines, $captureStart));
142 }
143
144 // ###################################################################
145 /**
146 * This processes an individual hunk of a file
147 *
148 * @param array Array of lines
149 */
150 private function _processHunk($lines)
151 {
152 $delstack = array();
153 for ($i = 1; $i < sizeof($lines); $i++)
154 {
155 $line = $lines[$i];
156
157 if (preg_match('/^([\+\- ])(.*)/', $line, $matches))
158 {
159 $action = $matches[1];
160 $content = $matches[2];
161
162 // leader line
163 if ($action == ' ')
164 {
165 $this->diff[$this->index]['lines'][$this->hunkId][] = array(
166 'line' => $content,
167 'act' => '',
168 'oldlineno' => ++$this->hunkLines['old']['line'],
169 'newlineno' => ++$this->hunkLines['new']['line']
170 );
171
172 $delstack = array();
173 }
174 // addition
175 else if ($action == '+')
176 {
177 // potential line delta
178 if (sizeof($delstack) > 0)
179 {
180 $lastline = array_shift($delstack);
181
182 if ($delta = @$this->_fetchDiffExtent($lastline['line'], $content))
183 {
184 if (strlen($lastline['line']) > ($delta['start'] - $delta['end']))
185 {
186 $end = strlen($lastline['line']) + $delta['end'];
187 $change = '{@-' . '-}' . substr($lastline['line'], $delta['start'], $end - $delta['start']) . '{/@-' . '-}';
188 $this->diff[$this->index]['lines'][$this->hunkId][$lastline['index']]['line'] = substr($lastline['line'], 0, $delta['start']) . $change . substr($lastline['line'], $end);
189 }
190
191 if (strlen($content) > $delta['start'] - $delta['end'])
192 {
193 $end = strlen($content) + $delta['end'];
194 $change = '{@+' . '+}' . substr($content, $delta['start'], $end - $delta['start']) . '{/@+' . '+}';
195 $content = substr($content, 0, $delta['start']) . $change . substr($content, $end);
196 }
197 }
198 }
199
200 $this->diff[$this->index]['lines'][$this->hunkId][] = array(
201 'line' => $content,
202 'act' => '+',
203 'oldlineno' => 0,
204 'newlineno' => ++$this->hunkLines['new']['line']
205 );
206 }
207 // deletion
208 else if ($action == '-')
209 {
210 $this->diff[$this->index]['lines'][$this->hunkId][] = $temp = array(
211 'line' => $content,
212 'act' => '-',
213 'oldlineno' => ++$this->hunkLines['old']['line'],
214 'newlineno' => 0
215 );
216
217 $temp['index'] = sizeof($this->diff[$this->index]['lines'][$this->hunkId]) - 1;
218 array_push($delstack, $temp);
219 }
220 }
221 }
222 }
223
224 // ###################################################################
225 /**
226 * Processes properties
227 *
228 * @param array Lines of properties
229 */
230 private function _processProperties($lines)
231 {
232 $curprop = '';
233 $mode = '';
234 $captureStart = 1;
235 for ($i = 1; $i < sizeof($lines); $i++)
236 {
237 $line = $lines[$i];
238 if (preg_match('#Name: (.*?)$#', $line, $matches))
239 {
240 $curprop = $matches[1];
241 BSRegister::Debug("prop: $curprop");
242 }
243 else
244 {
245 if (preg_match('#^\s+?\+(.*)#', $line, $matches))
246 {
247 $mode = 'add';
248 $this->diff[$this->index]['props'][$curprop]['add'] .= $matches[1];
249 }
250 else if (preg_match('#^\s+?\-(.*)#', $line, $matches))
251 {
252 $mode = 'del';
253 $this->diff[$this->index]['props'][$curprop]['del'] .= $matches[1];
254 }
255 else if (!preg_match('#^\s+[\+\- ](.*)#', $line) AND trim($line) != '')
256 {
257 $this->diff[$this->index]['props'][$curprop][$mode] .= "\n" . $line;
258 }
259 }
260 }
261 }
262
263 // ###################################################################
264 /**
265 * Returns the amount of change that occured between two lines
266 *
267 * @param string Old line
268 * @param string New line
269 *
270 * @return array Difference of positions
271 */
272 private function _fetchDiffExtent($old, $new)
273 {
274 $start = 0;
275 $min = min(strlen($old), strlen($new));
276
277 while ($start < $min AND $old[$start] == $new[$start])
278 {
279 $start++;
280 }
281
282 $end = -1;
283 $min = $min - $start;
284
285 while (-$end <= $min AND $old[strlen($old) + $end] == $new[strlen($new) + $end])
286 {
287 $end--;
288 }
289
290 return array('start' => $start, 'end' => $end + 1);
291 }
292 }
293
294 /*=====================================================================*\
295 || ###################################################################
296 || # $HeadURL$
297 || # $Id$
298 || ###################################################################
299 \*=====================================================================*/
300 ?>