/branches/bermi/vendor/pear/Text/Diff.php

https://github.com/akelos/v1 · PHP · 371 lines · 192 code · 45 blank · 134 comment · 21 complexity · ab9a244c43cdfcb4ae1b41c9018b6bc3 MD5 · raw file

  1. <?php
  2. /**
  3. * Text_Diff
  4. *
  5. * General API for generating and formatting diffs - the differences between
  6. * two sequences of strings.
  7. *
  8. * The PHP diff code used in this package was originally written by Geoffrey
  9. * T. Dairiki and is used with his permission.
  10. *
  11. * $Horde: framework/Text_Diff/Diff.php,v 1.17 2006/02/06 00:16:09 jan Exp $
  12. *
  13. * @package Text_Diff
  14. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  15. */
  16. class Text_Diff {
  17. /**
  18. * Array of changes.
  19. *
  20. * @var array
  21. */
  22. var $_edits;
  23. /**
  24. * Computes diffs between sequences of strings.
  25. *
  26. * @param array $from_lines An array of strings. Typically these are
  27. * lines from a file.
  28. * @param array $to_lines An array of strings.
  29. */
  30. function Text_Diff($engine, $params)
  31. {
  32. // Backward compatibility workaround.
  33. if (!is_string($engine)) {
  34. $params = array($engine, $params);
  35. $engine = 'auto';
  36. }
  37. if ($engine == 'auto') {
  38. $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';
  39. }
  40. $engine = basename($engine);
  41. require_once 'Text/Diff/Engine/' . $engine . '.php';
  42. $class = 'Text_Diff_Engine_' . $engine;
  43. $diff_engine = &new $class();
  44. $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
  45. }
  46. /**
  47. * Returns the array of differences.
  48. */
  49. function getDiff()
  50. {
  51. return $this->_edits;
  52. }
  53. /**
  54. * Computes a reversed diff.
  55. *
  56. * Example:
  57. * <code>
  58. * $diff = &new Text_Diff($lines1, $lines2);
  59. * $rev = $diff->reverse();
  60. * </code>
  61. *
  62. * @return Text_Diff A Diff object representing the inverse of the
  63. * original diff. Note that we purposely don't return a
  64. * reference here, since this essentially is a clone()
  65. * method.
  66. */
  67. function reverse()
  68. {
  69. if (version_compare(zend_version(), '2', '>')) {
  70. $rev = clone($this);
  71. } else {
  72. $rev = $this;
  73. }
  74. $rev->_edits = array();
  75. foreach ($this->_edits as $edit) {
  76. $rev->_edits[] = $edit->reverse();
  77. }
  78. return $rev;
  79. }
  80. /**
  81. * Checks for an empty diff.
  82. *
  83. * @return boolean True if two sequences were identical.
  84. */
  85. function isEmpty()
  86. {
  87. foreach ($this->_edits as $edit) {
  88. if (!is_a($edit, 'Text_Diff_Op_copy')) {
  89. return false;
  90. }
  91. }
  92. return true;
  93. }
  94. /**
  95. * Computes the length of the Longest Common Subsequence (LCS).
  96. *
  97. * This is mostly for diagnostic purposes.
  98. *
  99. * @return integer The length of the LCS.
  100. */
  101. function lcs()
  102. {
  103. $lcs = 0;
  104. foreach ($this->_edits as $edit) {
  105. if (is_a($edit, 'Text_Diff_Op_copy')) {
  106. $lcs += count($edit->orig);
  107. }
  108. }
  109. return $lcs;
  110. }
  111. /**
  112. * Gets the original set of lines.
  113. *
  114. * This reconstructs the $from_lines parameter passed to the constructor.
  115. *
  116. * @return array The original sequence of strings.
  117. */
  118. function getOriginal()
  119. {
  120. $lines = array();
  121. foreach ($this->_edits as $edit) {
  122. if ($edit->orig) {
  123. array_splice($lines, count($lines), 0, $edit->orig);
  124. }
  125. }
  126. return $lines;
  127. }
  128. /**
  129. * Gets the final set of lines.
  130. *
  131. * This reconstructs the $to_lines parameter passed to the constructor.
  132. *
  133. * @return array The sequence of strings.
  134. */
  135. function getFinal()
  136. {
  137. $lines = array();
  138. foreach ($this->_edits as $edit) {
  139. if ($edit->final) {
  140. array_splice($lines, count($lines), 0, $edit->final);
  141. }
  142. }
  143. return $lines;
  144. }
  145. /**
  146. * Removes trailing newlines from a line of text. This is meant to be used
  147. * with array_walk().
  148. *
  149. * @param string $line The line to trim.
  150. * @param integer $key The index of the line in the array. Not used.
  151. */
  152. function trimNewlines(&$line, $key)
  153. {
  154. $line = str_replace(array("\n", "\r"), '', $line);
  155. }
  156. /**
  157. * Checks a diff for validity.
  158. *
  159. * This is here only for debugging purposes.
  160. */
  161. function _check($from_lines, $to_lines)
  162. {
  163. if (serialize($from_lines) != serialize($this->getOriginal())) {
  164. trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
  165. }
  166. if (serialize($to_lines) != serialize($this->getFinal())) {
  167. trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
  168. }
  169. $rev = $this->reverse();
  170. if (serialize($to_lines) != serialize($rev->getOriginal())) {
  171. trigger_error("Reversed original doesn't match", E_USER_ERROR);
  172. }
  173. if (serialize($from_lines) != serialize($rev->getFinal())) {
  174. trigger_error("Reversed final doesn't match", E_USER_ERROR);
  175. }
  176. $prevtype = null;
  177. foreach ($this->_edits as $edit) {
  178. if ($prevtype == get_class($edit)) {
  179. trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
  180. }
  181. $prevtype = get_class($edit);
  182. }
  183. return true;
  184. }
  185. }
  186. /**
  187. * $Horde: framework/Text_Diff/Diff.php,v 1.17 2006/02/06 00:16:09 jan Exp $
  188. *
  189. * @package Text_Diff
  190. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  191. */
  192. class Text_MappedDiff extends Text_Diff {
  193. /**
  194. * Computes a diff between sequences of strings.
  195. *
  196. * This can be used to compute things like case-insensitve diffs, or diffs
  197. * which ignore changes in white-space.
  198. *
  199. * @param array $from_lines An array of strings.
  200. * @param array $to_lines An array of strings.
  201. * @param array $mapped_from_lines This array should have the same size
  202. * number of elements as $from_lines. The
  203. * elements in $mapped_from_lines and
  204. * $mapped_to_lines are what is actually
  205. * compared when computing the diff.
  206. * @param array $mapped_to_lines This array should have the same number
  207. * of elements as $to_lines.
  208. */
  209. function Text_MappedDiff($from_lines, $to_lines,
  210. $mapped_from_lines, $mapped_to_lines)
  211. {
  212. assert(count($from_lines) == count($mapped_from_lines));
  213. assert(count($to_lines) == count($mapped_to_lines));
  214. parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
  215. $xi = $yi = 0;
  216. for ($i = 0; $i < count($this->_edits); $i++) {
  217. $orig = &$this->_edits[$i]->orig;
  218. if (is_array($orig)) {
  219. $orig = array_slice($from_lines, $xi, count($orig));
  220. $xi += count($orig);
  221. }
  222. $final = &$this->_edits[$i]->final;
  223. if (is_array($final)) {
  224. $final = array_slice($to_lines, $yi, count($final));
  225. $yi += count($final);
  226. }
  227. }
  228. }
  229. }
  230. /**
  231. * @package Text_Diff
  232. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  233. *
  234. * @access private
  235. */
  236. class Text_Diff_Op {
  237. var $orig;
  238. var $final;
  239. function reverse()
  240. {
  241. trigger_error('Abstract method', E_USER_ERROR);
  242. }
  243. function norig()
  244. {
  245. return $this->orig ? count($this->orig) : 0;
  246. }
  247. function nfinal()
  248. {
  249. return $this->final ? count($this->final) : 0;
  250. }
  251. }
  252. /**
  253. * @package Text_Diff
  254. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  255. *
  256. * @access private
  257. */
  258. class Text_Diff_Op_copy extends Text_Diff_Op {
  259. function Text_Diff_Op_copy($orig, $final = false)
  260. {
  261. if (!is_array($final)) {
  262. $final = $orig;
  263. }
  264. $this->orig = $orig;
  265. $this->final = $final;
  266. }
  267. function &reverse()
  268. {
  269. $reverse = &new Text_Diff_Op_copy($this->final, $this->orig);
  270. return $reverse;
  271. }
  272. }
  273. /**
  274. * @package Text_Diff
  275. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  276. *
  277. * @access private
  278. */
  279. class Text_Diff_Op_delete extends Text_Diff_Op {
  280. function Text_Diff_Op_delete($lines)
  281. {
  282. $this->orig = $lines;
  283. $this->final = false;
  284. }
  285. function &reverse()
  286. {
  287. $reverse = &new Text_Diff_Op_add($this->orig);
  288. return $reverse;
  289. }
  290. }
  291. /**
  292. * @package Text_Diff
  293. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  294. *
  295. * @access private
  296. */
  297. class Text_Diff_Op_add extends Text_Diff_Op {
  298. function Text_Diff_Op_add($lines)
  299. {
  300. $this->final = $lines;
  301. $this->orig = false;
  302. }
  303. function &reverse()
  304. {
  305. $reverse = &new Text_Diff_Op_delete($this->final);
  306. return $reverse;
  307. }
  308. }
  309. /**
  310. * @package Text_Diff
  311. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  312. *
  313. * @access private
  314. */
  315. class Text_Diff_Op_change extends Text_Diff_Op {
  316. function Text_Diff_Op_change($orig, $final)
  317. {
  318. $this->orig = $orig;
  319. $this->final = $final;
  320. }
  321. function &reverse()
  322. {
  323. $reverse = &new Text_Diff_Op_change($this->final, $this->orig);
  324. return $reverse;
  325. }
  326. }