PluginProbe
RSFirewall! / trunk
RSFirewall! vtrunk
rsfirewall / helpers / diff.php

diff.php in RSFirewall! trunk, at helpers/diff.php

458 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4
5 class.Diff.php
6
7 A class containing a diff implementation
8
9 Created by Stephen Morley - http://stephenmorley.org/ - and released under the
10 terms of the CC0 1.0 Universal legal code:
11
12 http://creativecommons.org/publicdomain/zero/1.0/legalcode
13
14 */
15
16 // A class containing functions for computing diffs and formatting the output.
17 /**
18 * Class Diff
19 */
20 class RSFirewall_Helper_Diff
21 {
22 // define the constants
23 const UNMODIFIED = 0;
24 const DELETED = 1;
25 const INSERTED = 2;
26
27 /* Returns the diff for two strings. The return value is an array, each of
28 * whose values is an array containing two values: a line (or character, if
29 * $compareCharacters is true), and one of the constants DIFF::UNMODIFIED (the
30 * line or character is in both strings), DIFF::DELETED (the line or character
31 * is only in the first string), and DIFF::INSERTED (the line or character is
32 * only in the second string). The parameters are:
33 *
34 * $string1 - the first string
35 * $string2 - the second string
36 * $compareCharacters - true to compare characters, and false to compare
37 * lines; this optional parameter defaults to false
38 */
39 /**
40 * @param $string1
41 * @param $string2
42 * @param bool $compareCharacters
43 *
44 * @return array
45 */
46 public static function compare(
47 $string1, $string2, $compareCharacters = false){
48
49 // initialise the sequences and comparison start and end positions
50 $start = 0;
51 if ($compareCharacters){
52 $sequence1 = $string1;
53 $sequence2 = $string2;
54 $end1 = strlen($string1) - 1;
55 $end2 = strlen($string2) - 1;
56 }else{
57 $sequence1 = preg_split('/\R/', $string1);
58 $sequence2 = preg_split('/\R/', $string2);
59 $end1 = count($sequence1) - 1;
60 $end2 = count($sequence2) - 1;
61 }
62
63 // skip any common prefix
64 while ($start <= $end1 && $start <= $end2
65 && $sequence1[$start] == $sequence2[$start]){
66 $start ++;
67 }
68
69 // skip any common suffix
70 while ($end1 >= $start && $end2 >= $start
71 && $sequence1[$end1] == $sequence2[$end2]){
72 $end1 --;
73 $end2 --;
74 }
75
76 // compute the table of longest common subsequence lengths
77 $table = self::computeTable($sequence1, $sequence2, $start, $end1, $end2);
78
79 // generate the partial diff
80 $partialDiff =
81 self::generatePartialDiff($table, $sequence1, $sequence2, $start);
82
83 // generate the full diff
84 $diff = array();
85 for ($index = 0; $index < $start; $index ++){
86 $diff[] = array($sequence1[$index], self::UNMODIFIED);
87 }
88 while (count($partialDiff) > 0) $diff[] = array_pop($partialDiff);
89 for ($index = $end1 + 1;
90 $index < ($compareCharacters ? strlen($sequence1) : count($sequence1));
91 $index ++){
92 $diff[] = array($sequence1[$index], self::UNMODIFIED);
93 }
94
95 // return the diff
96 return $diff;
97
98 }
99
100 /* Returns the diff for two files. The parameters are:
101 *
102 * $file1 - the path to the first file
103 * $file2 - the path to the second file
104 * $compareCharacters - true to compare characters, and false to compare
105 * lines; this optional parameter defaults to false
106 */
107 /**
108 * @param $file1
109 * @param $file2
110 * @param bool $compareCharacters
111 *
112 * @return array
113 */
114 public static function compareFiles(
115 $file1, $file2, $compareCharacters = false){
116
117 // return the diff of the files
118 return self::compare(
119 file_get_contents($file1),
120 file_get_contents($file2),
121 $compareCharacters);
122
123 }
124
125 /* Returns the table of longest common subsequence lengths for the specified
126 * sequences. The parameters are:
127 *
128 * $sequence1 - the first sequence
129 * $sequence2 - the second sequence
130 * $start - the starting index
131 * $end1 - the ending index for the first sequence
132 * $end2 - the ending index for the second sequence
133 */
134 /**
135 * @param $sequence1
136 * @param $sequence2
137 * @param $start
138 * @param $end1
139 * @param $end2
140 *
141 * @return array
142 */
143 private static function computeTable(
144 $sequence1, $sequence2, $start, $end1, $end2){
145
146 // determine the lengths to be compared
147 $length1 = $end1 - $start + 1;
148 $length2 = $end2 - $start + 1;
149
150 // initialise the table
151 $table = array(array_fill(0, $length2 + 1, 0));
152
153 // loop over the rows
154 for ($index1 = 1; $index1 <= $length1; $index1 ++){
155
156 // create the new row
157 $table[$index1] = array(0);
158
159 // loop over the columns
160 for ($index2 = 1; $index2 <= $length2; $index2 ++){
161
162 // store the longest common subsequence length
163 if ($sequence1[$index1 + $start - 1]
164 == $sequence2[$index2 + $start - 1]){
165 $table[$index1][$index2] = $table[$index1 - 1][$index2 - 1] + 1;
166 }else{
167 $table[$index1][$index2] =
168 max($table[$index1 - 1][$index2], $table[$index1][$index2 - 1]);
169 }
170
171 }
172 }
173
174 // return the table
175 return $table;
176
177 }
178
179 /* Returns the partial diff for the specificed sequences, in reverse order.
180 * The parameters are:
181 *
182 * $table - the table returned by the computeTable function
183 * $sequence1 - the first sequence
184 * $sequence2 - the second sequence
185 * $start - the starting index
186 */
187 /**
188 * @param $table
189 * @param $sequence1
190 * @param $sequence2
191 * @param $start
192 *
193 * @return array
194 */
195 private static function generatePartialDiff(
196 $table, $sequence1, $sequence2, $start){
197
198 // initialise the diff
199 $diff = array();
200
201 // initialise the indices
202 $index1 = count($table) - 1;
203 $index2 = count($table[0]) - 1;
204
205 // loop until there are no items remaining in either sequence
206 while ($index1 > 0 || $index2 > 0){
207
208 // check what has happened to the items at these indices
209 if ($index1 > 0 && $index2 > 0
210 && $sequence1[$index1 + $start - 1]
211 == $sequence2[$index2 + $start - 1]){
212
213 // update the diff and the indices
214 $diff[] = array($sequence1[$index1 + $start - 1], self::UNMODIFIED);
215 $index1 --;
216 $index2 --;
217
218 }elseif ($index2 > 0
219 && $table[$index1][$index2] == $table[$index1][$index2 - 1]){
220
221 // update the diff and the indices
222 $diff[] = array($sequence2[$index2 + $start - 1], self::INSERTED);
223 $index2 --;
224
225 }else{
226
227 // update the diff and the indices
228 $diff[] = array($sequence1[$index1 + $start - 1], self::DELETED);
229 $index1 --;
230
231 }
232
233 }
234
235 // return the diff
236 return $diff;
237
238 }
239
240 /* Returns a diff as a string, where unmodified lines are prefixed by ' ',
241 * deletions are prefixed by '- ', and insertions are prefixed by '+ '. The
242 * parameters are:
243 *
244 * $diff - the diff array
245 * $separator - the separator between lines; this optional parameter defaults
246 * to "\n"
247 */
248 /**
249 * @param $diff
250 * @param string $separator
251 *
252 * @return string
253 */
254 public static function toString($diff, $separator = "\n"){
255
256 // initialise the string
257 $string = '';
258
259 // loop over the lines in the diff
260 foreach ($diff as $line){
261
262 // extend the string with the line
263 switch ($line[1]){
264 case self::UNMODIFIED : $string .= ' ' . $line[0];break;
265 case self::DELETED : $string .= '- ' . $line[0];break;
266 case self::INSERTED : $string .= '+ ' . $line[0];break;
267 }
268
269 // extend the string with the separator
270 $string .= $separator;
271
272 }
273
274 // return the string
275 return $string;
276
277 }
278
279 /* Returns a diff as an HTML string, where unmodified lines are contained
280 * within 'span' elements, deletions are contained within 'del' elements, and
281 * insertions are contained within 'ins' elements. The parameters are:
282 *
283 * $diff - the diff array
284 * $separator - the separator between lines; this optional parameter defaults
285 * to '<br>'
286 */
287 /**
288 * @param $diff
289 * @param string $separator
290 *
291 * @return string
292 */
293 public static function toHTML($diff, $separator = '<br>'){
294
295 // initialise the HTML
296 $html = '';
297
298 // loop over the lines in the diff
299 foreach ($diff as $line){
300
301 // extend the HTML with the line
302 switch ($line[1]){
303 case self::UNMODIFIED : $element = 'span'; break;
304 case self::DELETED : $element = 'del'; break;
305 case self::INSERTED : $element = 'ins'; break;
306 }
307 $html .=
308 '<' . $element . '>'
309 . htmlspecialchars($line[0])
310 . '</' . $element . '>';
311
312 // extend the HTML with the separator
313 $html .= $separator;
314
315 }
316
317 // return the HTML
318 return $html;
319
320 }
321
322 /* Returns a diff as an HTML table. The parameters are:
323 *
324 * $diff - the diff array
325 * $indentation - indentation to add to every line of the generated HTML; this
326 * optional parameter defaults to ''
327 * $separator - the separator between lines; this optional parameter
328 * defaults to '<br>'
329 */
330 /**
331 * @param $diff
332 * @param string $indentation
333 * @param string $separator
334 * @param array $headers
335 *
336 * @return string
337 */
338 public static function toTable($diff, $indentation = '', $separator = '<br>', $headers = array()){
339 // initialise the HTML
340 $html = $indentation . "<table class=\"diff table table-bordered\">\n";
341
342 // Add headers if requested
343 if ($headers) {
344 $html .=
345 "<thead>\n".
346 "<tr>\n".
347 "<th>".htmlspecialchars($headers[0], ENT_COMPAT, 'utf-8')."</th>\n".
348 "<th>".htmlspecialchars($headers[1], ENT_COMPAT, 'utf-8')."</th>\n".
349 "</tr>\n".
350 "</thead>\n";
351 }
352
353 // loop over the lines in the diff
354 $index = 0;
355 while ($index < count($diff)){
356
357 // determine the line type
358 switch ($diff[$index][1]){
359
360 // display the content on the left and right
361 case self::UNMODIFIED:
362 $leftCell =
363 self::getCellContent(
364 $diff, $indentation, $separator, $index, self::UNMODIFIED);
365 $rightCell = $leftCell;
366 break;
367
368 // display the deleted on the left and inserted content on the right
369 case self::DELETED:
370 $leftCell =
371 self::getCellContent(
372 $diff, $indentation, $separator, $index, self::DELETED);
373 $rightCell =
374 self::getCellContent(
375 $diff, $indentation, $separator, $index, self::INSERTED);
376 break;
377
378 // display the inserted content on the right
379 case self::INSERTED:
380 $leftCell = '';
381 $rightCell =
382 self::getCellContent(
383 $diff, $indentation, $separator, $index, self::INSERTED);
384 break;
385
386 }
387
388 // extend the HTML with the new row
389 $html .=
390 $indentation
391 . " <tr>\n"
392 . $indentation
393 . ' <td class="diff'
394 . ($leftCell == $rightCell
395 ? 'Unmodified'
396 : ($leftCell == '' ? 'Blank' : 'Deleted'))
397 . '">'
398 . $leftCell
399 . "</td>\n"
400 . $indentation
401 . ' <td class="diff'
402 . ($leftCell == $rightCell
403 ? 'Unmodified'
404 : ($rightCell == '' ? 'Blank' : 'Inserted'))
405 . '">'
406 . $rightCell
407 . "</td>\n"
408 . $indentation
409 . " </tr>\n";
410
411 }
412
413 // return the HTML
414 return $html . $indentation . "</table>\n";
415
416 }
417
418 /* Returns the content of the cell, for use in the toTable function. The
419 * parameters are:
420 *
421 * $diff - the diff array
422 * $indentation - indentation to add to every line of the generated HTML
423 * $separator - the separator between lines
424 * $index - the current index, passes by reference
425 * $type - the type of line
426 */
427 /**
428 * @param $diff
429 * @param $indentation
430 * @param $separator
431 * @param $index
432 * @param $type
433 *
434 * @return string
435 */
436 private static function getCellContent(
437 $diff, $indentation, $separator, &$index, $type){
438
439 // initialise the HTML
440 $html = '';
441
442 // loop over the matching lines, adding them to the HTML
443 while ($index < count($diff) && $diff[$index][1] == $type){
444 $html .=
445 '<span>'
446 . htmlspecialchars($diff[$index][0])
447 . '</span>'
448 . $separator;
449 $index ++;
450 }
451
452 // return the HTML
453 return $html;
454
455 }
456
457 }
458