PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/system/application/3rdparty/Text/Diff.php

https://bitbucket.org/xmonader/ciki
PHP | 450 lines | 231 code | 52 blank | 167 comment | 28 complexity | ef15efc8e6b08f81f759bf11a69096e5 MD5 | raw file
  1. <?php
  2. /**
  3. * General API for generating and formatting diffs - the differences between
  4. * two sequences of strings.
  5. *
  6. * The original PHP version of this code was written by Geoffrey T. Dairiki
  7. * <dairiki@dairiki.org>, and is used/adapted with his permission.
  8. *
  9. * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
  10. * Copyright 2004-2010 The Horde Project (http://www.horde.org/)
  11. *
  12. * See the enclosed file COPYING for license information (LGPL). If you did
  13. * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
  14. *
  15. * @package Text_Diff
  16. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  17. */
  18. class Text_Diff {
  19. /**
  20. * Array of changes.
  21. *
  22. * @var array
  23. */
  24. var $_edits;
  25. /**
  26. * Computes diffs between sequences of strings.
  27. *
  28. * @param string $engine Name of the diffing engine to use. 'auto'
  29. * will automatically select the best.
  30. * @param array $params Parameters to pass to the diffing engine.
  31. * Normally an array of two arrays, each
  32. * containing the lines from a file.
  33. */
  34. function Text_Diff($engine, $params)
  35. {
  36. // Backward compatibility workaround.
  37. if (!is_string($engine)) {
  38. $params = array($engine, $params);
  39. $engine = 'auto';
  40. }
  41. if ($engine == 'auto') {
  42. $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';
  43. } else {
  44. $engine = basename($engine);
  45. }
  46. // WP #7391
  47. require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php';
  48. $class = 'Text_Diff_Engine_' . $engine;
  49. $diff_engine = new $class();
  50. $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
  51. }
  52. /**
  53. * Returns the array of differences.
  54. */
  55. function getDiff()
  56. {
  57. return $this->_edits;
  58. }
  59. /**
  60. * returns the number of new (added) lines in a given diff.
  61. *
  62. * @since Text_Diff 1.1.0
  63. *
  64. * @return integer The number of new lines
  65. */
  66. function countAddedLines()
  67. {
  68. $count = 0;
  69. foreach ($this->_edits as $edit) {
  70. if (is_a($edit, 'Text_Diff_Op_add') ||
  71. is_a($edit, 'Text_Diff_Op_change')) {
  72. $count += $edit->nfinal();
  73. }
  74. }
  75. return $count;
  76. }
  77. /**
  78. * Returns the number of deleted (removed) lines in a given diff.
  79. *
  80. * @since Text_Diff 1.1.0
  81. *
  82. * @return integer The number of deleted lines
  83. */
  84. function countDeletedLines()
  85. {
  86. $count = 0;
  87. foreach ($this->_edits as $edit) {
  88. if (is_a($edit, 'Text_Diff_Op_delete') ||
  89. is_a($edit, 'Text_Diff_Op_change')) {
  90. $count += $edit->norig();
  91. }
  92. }
  93. return $count;
  94. }
  95. /**
  96. * Computes a reversed diff.
  97. *
  98. * Example:
  99. * <code>
  100. * $diff = new Text_Diff($lines1, $lines2);
  101. * $rev = $diff->reverse();
  102. * </code>
  103. *
  104. * @return Text_Diff A Diff object representing the inverse of the
  105. * original diff. Note that we purposely don't return a
  106. * reference here, since this essentially is a clone()
  107. * method.
  108. */
  109. function reverse()
  110. {
  111. if (version_compare(zend_version(), '2', '>')) {
  112. $rev = clone($this);
  113. } else {
  114. $rev = $this;
  115. }
  116. $rev->_edits = array();
  117. foreach ($this->_edits as $edit) {
  118. $rev->_edits[] = $edit->reverse();
  119. }
  120. return $rev;
  121. }
  122. /**
  123. * Checks for an empty diff.
  124. *
  125. * @return boolean True if two sequences were identical.
  126. */
  127. function isEmpty()
  128. {
  129. foreach ($this->_edits as $edit) {
  130. if (!is_a($edit, 'Text_Diff_Op_copy')) {
  131. return false;
  132. }
  133. }
  134. return true;
  135. }
  136. /**
  137. * Computes the length of the Longest Common Subsequence (LCS).
  138. *
  139. * This is mostly for diagnostic purposes.
  140. *
  141. * @return integer The length of the LCS.
  142. */
  143. function lcs()
  144. {
  145. $lcs = 0;
  146. foreach ($this->_edits as $edit) {
  147. if (is_a($edit, 'Text_Diff_Op_copy')) {
  148. $lcs += count($edit->orig);
  149. }
  150. }
  151. return $lcs;
  152. }
  153. /**
  154. * Gets the original set of lines.
  155. *
  156. * This reconstructs the $from_lines parameter passed to the constructor.
  157. *
  158. * @return array The original sequence of strings.
  159. */
  160. function getOriginal()
  161. {
  162. $lines = array();
  163. foreach ($this->_edits as $edit) {
  164. if ($edit->orig) {
  165. array_splice($lines, count($lines), 0, $edit->orig);
  166. }
  167. }
  168. return $lines;
  169. }
  170. /**
  171. * Gets the final set of lines.
  172. *
  173. * This reconstructs the $to_lines parameter passed to the constructor.
  174. *
  175. * @return array The sequence of strings.
  176. */
  177. function getFinal()
  178. {
  179. $lines = array();
  180. foreach ($this->_edits as $edit) {
  181. if ($edit->final) {
  182. array_splice($lines, count($lines), 0, $edit->final);
  183. }
  184. }
  185. return $lines;
  186. }
  187. /**
  188. * Removes trailing newlines from a line of text. This is meant to be used
  189. * with array_walk().
  190. *
  191. * @param string $line The line to trim.
  192. * @param integer $key The index of the line in the array. Not used.
  193. */
  194. function trimNewlines(&$line, $key)
  195. {
  196. $line = str_replace(array("\n", "\r"), '', $line);
  197. }
  198. /**
  199. * Determines the location of the system temporary directory.
  200. *
  201. * @static
  202. *
  203. * @access protected
  204. *
  205. * @return string A directory name which can be used for temp files.
  206. * Returns false if one could not be found.
  207. */
  208. function _getTempDir()
  209. {
  210. $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp',
  211. 'c:\windows\temp', 'c:\winnt\temp');
  212. /* Try PHP's upload_tmp_dir directive. */
  213. $tmp = ini_get('upload_tmp_dir');
  214. /* Otherwise, try to determine the TMPDIR environment variable. */
  215. if (!strlen($tmp)) {
  216. $tmp = getenv('TMPDIR');
  217. }
  218. /* If we still cannot determine a value, then cycle through a list of
  219. * preset possibilities. */
  220. while (!strlen($tmp) && count($tmp_locations)) {
  221. $tmp_check = array_shift($tmp_locations);
  222. if (@is_dir($tmp_check)) {
  223. $tmp = $tmp_check;
  224. }
  225. }
  226. /* If it is still empty, we have failed, so return false; otherwise
  227. * return the directory determined. */
  228. return strlen($tmp) ? $tmp : false;
  229. }
  230. /**
  231. * Checks a diff for validity.
  232. *
  233. * This is here only for debugging purposes.
  234. */
  235. function _check($from_lines, $to_lines)
  236. {
  237. if (serialize($from_lines) != serialize($this->getOriginal())) {
  238. trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
  239. }
  240. if (serialize($to_lines) != serialize($this->getFinal())) {
  241. trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
  242. }
  243. $rev = $this->reverse();
  244. if (serialize($to_lines) != serialize($rev->getOriginal())) {
  245. trigger_error("Reversed original doesn't match", E_USER_ERROR);
  246. }
  247. if (serialize($from_lines) != serialize($rev->getFinal())) {
  248. trigger_error("Reversed final doesn't match", E_USER_ERROR);
  249. }
  250. $prevtype = null;
  251. foreach ($this->_edits as $edit) {
  252. if ($prevtype == get_class($edit)) {
  253. trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
  254. }
  255. $prevtype = get_class($edit);
  256. }
  257. return true;
  258. }
  259. }
  260. /**
  261. * @package Text_Diff
  262. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  263. */
  264. class Text_MappedDiff extends Text_Diff {
  265. /**
  266. * Computes a diff between sequences of strings.
  267. *
  268. * This can be used to compute things like case-insensitve diffs, or diffs
  269. * which ignore changes in white-space.
  270. *
  271. * @param array $from_lines An array of strings.
  272. * @param array $to_lines An array of strings.
  273. * @param array $mapped_from_lines This array should have the same size
  274. * number of elements as $from_lines. The
  275. * elements in $mapped_from_lines and
  276. * $mapped_to_lines are what is actually
  277. * compared when computing the diff.
  278. * @param array $mapped_to_lines This array should have the same number
  279. * of elements as $to_lines.
  280. */
  281. function Text_MappedDiff($from_lines, $to_lines,
  282. $mapped_from_lines, $mapped_to_lines)
  283. {
  284. assert(count($from_lines) == count($mapped_from_lines));
  285. assert(count($to_lines) == count($mapped_to_lines));
  286. parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
  287. $xi = $yi = 0;
  288. for ($i = 0; $i < count($this->_edits); $i++) {
  289. $orig = &$this->_edits[$i]->orig;
  290. if (is_array($orig)) {
  291. $orig = array_slice($from_lines, $xi, count($orig));
  292. $xi += count($orig);
  293. }
  294. $final = &$this->_edits[$i]->final;
  295. if (is_array($final)) {
  296. $final = array_slice($to_lines, $yi, count($final));
  297. $yi += count($final);
  298. }
  299. }
  300. }
  301. }
  302. /**
  303. * @package Text_Diff
  304. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  305. *
  306. * @access private
  307. */
  308. class Text_Diff_Op {
  309. var $orig;
  310. var $final;
  311. function &reverse()
  312. {
  313. trigger_error('Abstract method', E_USER_ERROR);
  314. }
  315. function norig()
  316. {
  317. return $this->orig ? count($this->orig) : 0;
  318. }
  319. function nfinal()
  320. {
  321. return $this->final ? count($this->final) : 0;
  322. }
  323. }
  324. /**
  325. * @package Text_Diff
  326. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  327. *
  328. * @access private
  329. */
  330. class Text_Diff_Op_copy extends Text_Diff_Op {
  331. function Text_Diff_Op_copy($orig, $final = false)
  332. {
  333. if (!is_array($final)) {
  334. $final = $orig;
  335. }
  336. $this->orig = $orig;
  337. $this->final = $final;
  338. }
  339. function &reverse()
  340. {
  341. $reverse = new Text_Diff_Op_copy($this->final, $this->orig);
  342. return $reverse;
  343. }
  344. }
  345. /**
  346. * @package Text_Diff
  347. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  348. *
  349. * @access private
  350. */
  351. class Text_Diff_Op_delete extends Text_Diff_Op {
  352. function Text_Diff_Op_delete($lines)
  353. {
  354. $this->orig = $lines;
  355. $this->final = false;
  356. }
  357. function &reverse()
  358. {
  359. $reverse = new Text_Diff_Op_add($this->orig);
  360. return $reverse;
  361. }
  362. }
  363. /**
  364. * @package Text_Diff
  365. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  366. *
  367. * @access private
  368. */
  369. class Text_Diff_Op_add extends Text_Diff_Op {
  370. function Text_Diff_Op_add($lines)
  371. {
  372. $this->final = $lines;
  373. $this->orig = false;
  374. }
  375. function &reverse()
  376. {
  377. $reverse = new Text_Diff_Op_delete($this->final);
  378. return $reverse;
  379. }
  380. }
  381. /**
  382. * @package Text_Diff
  383. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  384. *
  385. * @access private
  386. */
  387. class Text_Diff_Op_change extends Text_Diff_Op {
  388. function Text_Diff_Op_change($orig, $final)
  389. {
  390. $this->orig = $orig;
  391. $this->final = $final;
  392. }
  393. function &reverse()
  394. {
  395. $reverse = new Text_Diff_Op_change($this->final, $this->orig);
  396. return $reverse;
  397. }
  398. }