dolibarr  13.0.2
utils_diff.class.php
1 <?php
2 /* Copyright (C) 2016 Jean-François Ferry <hello@librethic.io>
3  *
4  * A class containing a diff implementation
5  *
6  * Created by Stephen Morley - http://stephenmorley.org/ - and released under the
7  * terms of the CC0 1.0 Universal legal code:
8  *
9  * http://creativecommons.org/publicdomain/zero/1.0/legalcode
10  */
11 
12 
16 class Diff
17 {
18  // define the constants
19  const UNMODIFIED = 0;
20  const DELETED = 1;
21  const INSERTED = 2;
22 
36  public static function compare($string1, $string2, $compareCharacters = false)
37  {
38  // initialise the sequences and comparison start and end positions
39  $start = 0;
40  if ($compareCharacters) {
41  $sequence1 = $string1;
42  $sequence2 = $string2;
43  $end1 = strlen($string1) - 1;
44  $end2 = strlen($string2) - 1;
45  } else {
46  $sequence1 = preg_split('/\R/', $string1);
47  $sequence2 = preg_split('/\R/', $string2);
48  $end1 = count($sequence1) - 1;
49  $end2 = count($sequence2) - 1;
50  }
51 
52  // skip any common prefix
53  while ($start <= $end1 && $start <= $end2
54  && $sequence1[$start] == $sequence2[$start]) {
55  $start++;
56  }
57 
58  // skip any common suffix
59  while ($end1 >= $start && $end2 >= $start
60  && $sequence1[$end1] == $sequence2[$end2]) {
61  $end1--;
62  $end2--;
63  }
64 
65  // compute the table of longest common subsequence lengths
66  $table = self::computeTable($sequence1, $sequence2, $start, $end1, $end2);
67 
68  // generate the partial diff
69  $partialDiff = self::generatePartialDiff($table, $sequence1, $sequence2, $start);
70 
71  // generate the full diff
72  $diff = array();
73  for ($index = 0; $index < $start; $index++) {
74  $diff[] = array($sequence1[$index], self::UNMODIFIED);
75  }
76  while (count($partialDiff) > 0) {
77  $diff[] = array_pop($partialDiff);
78  }
79 
80  $end2 = ($compareCharacters ? strlen($sequence1) : count($sequence1));
81  for ($index = $end1 + 1; $index < $end2; $index++)
82  {
83  $diff[] = array($sequence1[$index], self::UNMODIFIED);
84  }
85 
86  // return the diff
87  return $diff;
88  }
89 
98  public static function compareFiles(
99  $file1,
100  $file2,
101  $compareCharacters = false
102  ) {
103 
104  // return the diff of the files
105  return self::compare(
106  file_get_contents($file1),
107  file_get_contents($file2),
108  $compareCharacters
109  );
110  }
111 
122  private static function computeTable($sequence1, $sequence2, $start, $end1, $end2)
123  {
124  // determine the lengths to be compared
125  $length1 = $end1 - $start + 1;
126  $length2 = $end2 - $start + 1;
127 
128  // initialise the table
129  $table = array(array_fill(0, $length2 + 1, 0));
130 
131  // loop over the rows
132  for ($index1 = 1; $index1 <= $length1; $index1++) {
133  // create the new row
134  $table[$index1] = array(0);
135 
136  // loop over the columns
137  for ($index2 = 1; $index2 <= $length2; $index2++) {
138  // store the longest common subsequence length
139  if ($sequence1[$index1 + $start - 1] == $sequence2[$index2 + $start - 1]
140  ) {
141  $table[$index1][$index2] = $table[$index1 - 1][$index2 - 1] + 1;
142  } else {
143  $table[$index1][$index2] = max($table[$index1 - 1][$index2], $table[$index1][$index2 - 1]);
144  }
145  }
146  }
147 
148  // return the table
149  return $table;
150  }
151 
162  private static function generatePartialDiff($table, $sequence1, $sequence2, $start)
163  {
164  // initialise the diff
165  $diff = array();
166 
167  // initialise the indices
168  $index1 = count($table) - 1;
169  $index2 = count($table[0]) - 1;
170 
171  // loop until there are no items remaining in either sequence
172  while ($index1 > 0 || $index2 > 0) {
173  // check what has happened to the items at these indices
174  if ($index1 > 0 && $index2 > 0
175  && $sequence1[$index1 + $start - 1] == $sequence2[$index2 + $start - 1]
176  ) {
177  // update the diff and the indices
178  $diff[] = array($sequence1[$index1 + $start - 1], self::UNMODIFIED);
179  $index1--;
180  $index2--;
181  } elseif ($index2 > 0
182  && $table[$index1][$index2] == $table[$index1][$index2 - 1]
183  ) {
184  // update the diff and the indices
185  $diff[] = array($sequence2[$index2 + $start - 1], self::INSERTED);
186  $index2--;
187  } else {
188  // update the diff and the indices
189  $diff[] = array($sequence1[$index1 + $start - 1], self::DELETED);
190  $index1--;
191  }
192  }
193 
194  // return the diff
195  return $diff;
196  }
197 
207  public static function toString($diff, $separator = "\n")
208  {
209  // initialise the string
210  $string = '';
211 
212  // loop over the lines in the diff
213  foreach ($diff as $line) {
214  // extend the string with the line
215  switch ($line[1]) {
216  case self::UNMODIFIED:
217  $string .= ' '.$line[0];
218  break;
219  case self::DELETED:
220  $string .= '- '.$line[0];
221  break;
222  case self::INSERTED:
223  $string .= '+ '.$line[0];
224  break;
225  }
226 
227  // extend the string with the separator
228  $string .= $separator;
229  }
230 
231  // return the string
232  return $string;
233  }
234 
244  public static function toHTML($diff, $separator = '<br>')
245  {
246  // initialise the HTML
247  $html = '';
248 
249  // loop over the lines in the diff
250  foreach ($diff as $line) {
251  // extend the HTML with the line
252  switch ($line[1]) {
253  case self::UNMODIFIED:
254  $element = 'span';
255  break;
256  case self::DELETED:
257  $element = 'del';
258  break;
259  case self::INSERTED:
260  $element = 'ins';
261  break;
262  }
263  $html .=
264  '<'.$element.'>'
265  . htmlspecialchars($line[0])
266  . '</'.$element.'>';
267 
268  // extend the HTML with the separator
269  $html .= $separator;
270  }
271 
272  // return the HTML
273  return $html;
274  }
275 
284  public static function toTable($diff, $indentation = '', $separator = '<br>')
285  {
286  // initialise the HTML
287  $html = $indentation."<table class=\"diff\">\n";
288 
289  // loop over the lines in the diff
290  $index = 0;
291  while ($index < count($diff)) {
292  // determine the line type
293  switch ($diff[$index][1]) {
294  // display the content on the left and right
295  case self::UNMODIFIED:
296  $leftCell = self::getCellContent(
297  $diff,
298  $indentation,
299  $separator,
300  $index,
301  self::UNMODIFIED
302  );
303  $rightCell = $leftCell;
304  break;
305 
306  // display the deleted on the left and inserted content on the right
307  case self::DELETED:
308  $leftCell = self::getCellContent(
309  $diff,
310  $indentation,
311  $separator,
312  $index,
313  self::DELETED
314  );
315  $rightCell = self::getCellContent(
316  $diff,
317  $indentation,
318  $separator,
319  $index,
320  self::INSERTED
321  );
322  break;
323 
324  // display the inserted content on the right
325  case self::INSERTED:
326  $leftCell = '';
327  $rightCell = self::getCellContent(
328  $diff,
329  $indentation,
330  $separator,
331  $index,
332  self::INSERTED
333  );
334  break;
335  }
336 
337  // extend the HTML with the new row
338  $html .=
339  $indentation
340  . " <tr>\n"
341  . $indentation
342  . ' <td class="diff'
343  . ($leftCell == $rightCell
344  ? 'Unmodified'
345  : ($leftCell == '' ? 'Blank' : 'Deleted'))
346  . '">'
347  . $leftCell
348  . "</td>\n"
349  . $indentation
350  . ' <td class="diff'
351  . ($leftCell == $rightCell
352  ? 'Unmodified'
353  : ($rightCell == '' ? 'Blank' : 'Inserted'))
354  . '">'
355  . $rightCell
356  . "</td>\n"
357  . $indentation
358  . " </tr>\n";
359  }
360 
361  // return the HTML
362  return $html.$indentation."</table>\n";
363  }
364 
376  private static function getCellContent($diff, $indentation, $separator, &$index, $type)
377  {
378  // initialise the HTML
379  $html = '';
380 
381  // loop over the matching lines, adding them to the HTML
382  while ($index < count($diff) && $diff[$index][1] == $type) {
383  $html .=
384  '<span>'
385  . htmlspecialchars($diff[$index][0])
386  . '</span>'
387  . $separator;
388  $index++;
389  }
390 
391  // return the HTML
392  return $html;
393  }
394 }
static toHTML($diff, $separator= '< br >')
Returns a diff as an HTML string, where unmodified lines are contained within &#39;span&#39; elements...
static generatePartialDiff($table, $sequence1, $sequence2, $start)
Returns the partial diff for the specificed sequences, in reverse order.
static compare($string1, $string2, $compareCharacters=false)
Returns the diff for two strings.
static computeTable($sequence1, $sequence2, $start, $end1, $end2)
Returns the table of longest common subsequence lengths for the specified sequences.
static toTable($diff, $indentation= '', $separator= '< br >')
Returns a diff as an HTML table.
A class containing functions for computing diffs and formatting the output.
static getCellContent($diff, $indentation, $separator, &$index, $type)
Returns the content of the cell, for use in the toTable function.
static compareFiles($file1, $file2, $compareCharacters=false)
Returns the diff for two files.
static toString($diff, $separator="\n")
Returns a diff as a string, where unmodified lines are prefixed by &#39; &#39;, deletions are prefixed by &#39;- ...