String.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <?php
  2. /**
  3. * Parses unified or context diffs output from eg. the diff utility.
  4. *
  5. * Example:
  6. * <code>
  7. * $patch = file_get_contents('example.patch');
  8. * $diff = new Horde_Text_Diff('string', array($patch));
  9. * $renderer = new Horde_Text_Diff_Renderer_inline();
  10. * echo $renderer->render($diff);
  11. * </code>
  12. *
  13. * Copyright 2005 Örjan Persson <o@42mm.org>
  14. * Copyright 2005-2012 Horde LLC (http://www.horde.org/)
  15. *
  16. * See the enclosed file COPYING for license information (LGPL). If you did
  17. * not receive this file, see http://www.horde.org/licenses/lgpl21.
  18. *
  19. * @author Örjan Persson <o@42mm.org>
  20. * @package Text_Diff
  21. */
  22. class Horde_Text_Diff_Engine_String
  23. {
  24. /**
  25. * Parses a unified or context diff.
  26. *
  27. * First param contains the whole diff and the second can be used to force
  28. * a specific diff type. If the second parameter is 'autodetect', the
  29. * diff will be examined to find out which type of diff this is.
  30. *
  31. * @param string $diff The diff content.
  32. * @param string $mode The diff mode of the content in $diff. One of
  33. * 'context', 'unified', or 'autodetect'.
  34. *
  35. * @return array List of all diff operations.
  36. * @throws Horde_Text_Diff_Exception
  37. */
  38. public function diff($diff, $mode = 'autodetect')
  39. {
  40. // Detect line breaks.
  41. $lnbr = "\n";
  42. if (strpos($diff, "\r\n") !== false) {
  43. $lnbr = "\r\n";
  44. } elseif (strpos($diff, "\r") !== false) {
  45. $lnbr = "\r";
  46. }
  47. // Make sure we have a line break at the EOF.
  48. if (substr($diff, -strlen($lnbr)) != $lnbr) {
  49. $diff .= $lnbr;
  50. }
  51. if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') {
  52. throw new Horde_Text_Diff_Exception('Type of diff is unsupported');
  53. }
  54. if ($mode == 'autodetect') {
  55. $context = strpos($diff, '***');
  56. $unified = strpos($diff, '---');
  57. if ($context === $unified) {
  58. throw new Horde_Text_Diff_Exception('Type of diff could not be detected');
  59. } elseif ($context === false || $unified === false) {
  60. $mode = $context !== false ? 'context' : 'unified';
  61. } else {
  62. $mode = $context < $unified ? 'context' : 'unified';
  63. }
  64. }
  65. // Split by new line and remove the diff header, if there is one.
  66. $diff = explode($lnbr, $diff);
  67. if (($mode == 'context' && strpos($diff[0], '***') === 0) ||
  68. ($mode == 'unified' && strpos($diff[0], '---') === 0)) {
  69. array_shift($diff);
  70. array_shift($diff);
  71. }
  72. if ($mode == 'context') {
  73. return $this->parseContextDiff($diff);
  74. } else {
  75. return $this->parseUnifiedDiff($diff);
  76. }
  77. }
  78. /**
  79. * Parses an array containing the unified diff.
  80. *
  81. * @param array $diff Array of lines.
  82. *
  83. * @return array List of all diff operations.
  84. */
  85. public function parseUnifiedDiff($diff)
  86. {
  87. $edits = [];
  88. $end = count($diff) - 1;
  89. for ($i = 0; $i < $end;) {
  90. $diff1 = [];
  91. switch (substr($diff[$i], 0, 1)) {
  92. case ' ':
  93. do {
  94. $diff1[] = substr($diff[$i], 1);
  95. } while (++$i < $end && substr($diff[$i], 0, 1) == ' ');
  96. $edits[] = new Horde_Text_Diff_Op_Copy($diff1);
  97. break;
  98. case '+':
  99. // get all new lines
  100. do {
  101. $diff1[] = substr($diff[$i], 1);
  102. } while (++$i < $end && substr($diff[$i], 0, 1) == '+');
  103. $edits[] = new Horde_Text_Diff_Op_Add($diff1);
  104. break;
  105. case '-':
  106. // get changed or removed lines
  107. $diff2 = [];
  108. do {
  109. $diff1[] = substr($diff[$i], 1);
  110. } while (++$i < $end && substr($diff[$i], 0, 1) == '-');
  111. while ($i < $end && substr($diff[$i], 0, 1) == '+') {
  112. $diff2[] = substr($diff[$i++], 1);
  113. }
  114. if (count($diff2) == 0) {
  115. $edits[] = new Horde_Text_Diff_Op_Delete($diff1);
  116. } else {
  117. $edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2);
  118. }
  119. break;
  120. default:
  121. $i++;
  122. break;
  123. }
  124. }
  125. return $edits;
  126. }
  127. /**
  128. * Parses an array containing the context diff.
  129. *
  130. * @param array $diff Array of lines.
  131. *
  132. * @return array List of all diff operations.
  133. */
  134. public function parseContextDiff(&$diff)
  135. {
  136. $edits = [];
  137. $i = $max_i = $j = $max_j = 0;
  138. $end = count($diff) - 1;
  139. while ($i < $end && $j < $end) {
  140. while ($i >= $max_i && $j >= $max_j) {
  141. // Find the boundaries of the diff output of the two files
  142. for ($i = $j;
  143. $i < $end && substr($diff[$i], 0, 3) == '***';
  144. $i++);
  145. for ($max_i = $i;
  146. $max_i < $end && substr($diff[$max_i], 0, 3) != '---';
  147. $max_i++);
  148. for ($j = $max_i;
  149. $j < $end && substr($diff[$j], 0, 3) == '---';
  150. $j++);
  151. for ($max_j = $j;
  152. $max_j < $end && substr($diff[$max_j], 0, 3) != '***';
  153. $max_j++);
  154. }
  155. // find what hasn't been changed
  156. $array = [];
  157. while ($i < $max_i &&
  158. $j < $max_j &&
  159. strcmp($diff[$i], $diff[$j]) == 0) {
  160. $array[] = substr($diff[$i], 2);
  161. $i++;
  162. $j++;
  163. }
  164. while ($i < $max_i && ($max_j-$j) <= 1) {
  165. if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') {
  166. break;
  167. }
  168. $array[] = substr($diff[$i++], 2);
  169. }
  170. while ($j < $max_j && ($max_i-$i) <= 1) {
  171. if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') {
  172. break;
  173. }
  174. $array[] = substr($diff[$j++], 2);
  175. }
  176. if (count($array) > 0) {
  177. $edits[] = new Horde_Text_Diff_Op_Copy($array);
  178. }
  179. if ($i < $max_i) {
  180. $diff1 = [];
  181. switch (substr($diff[$i], 0, 1)) {
  182. case '!':
  183. $diff2 = [];
  184. do {
  185. $diff1[] = substr($diff[$i], 2);
  186. if ($j < $max_j && substr($diff[$j], 0, 1) == '!') {
  187. $diff2[] = substr($diff[$j++], 2);
  188. }
  189. } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!');
  190. $edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2);
  191. break;
  192. case '+':
  193. do {
  194. $diff1[] = substr($diff[$i], 2);
  195. } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+');
  196. $edits[] = new Horde_Text_Diff_Op_Add($diff1);
  197. break;
  198. case '-':
  199. do {
  200. $diff1[] = substr($diff[$i], 2);
  201. } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-');
  202. $edits[] = new Horde_Text_Diff_Op_Delete($diff1);
  203. break;
  204. }
  205. }
  206. if ($j < $max_j) {
  207. $diff2 = [];
  208. switch (substr($diff[$j], 0, 1)) {
  209. case '+':
  210. do {
  211. $diff2[] = substr($diff[$j++], 2);
  212. } while ($j < $max_j && substr($diff[$j], 0, 1) == '+');
  213. $edits[] = new Horde_Text_Diff_Op_Add($diff2);
  214. break;
  215. case '-':
  216. do {
  217. $diff2[] = substr($diff[$j++], 2);
  218. } while ($j < $max_j && substr($diff[$j], 0, 1) == '-');
  219. $edits[] = new Horde_Text_Diff_Op_Delete($diff2);
  220. break;
  221. }
  222. }
  223. }
  224. return $edits;
  225. }
  226. }