PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/WeBid/trunk/includes/diff/diff.php

https://github.com/cipiceuca25/webid
PHP | 818 lines | 518 code | 93 blank | 207 comment | 60 complexity | a6f4aac588020535ceaa0ee2af8b14ef MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. * Code from pear.php.net, Text_Diff-1.1.0 package
  4. * http://pear.php.net/package/Text_Diff/
  5. *
  6. * Modified for use with WeBid
  7. *
  8. * General API for generating and formatting diffs - the differences between
  9. * two sequences of strings.
  10. *
  11. * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
  12. * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
  13. *
  14. * @package diff
  15. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  16. */
  17. class diff
  18. {
  19. /**
  20. * Array of changes.
  21. * @var array
  22. */
  23. var $_edits;
  24. /**
  25. * Computes diffs between sequences of strings.
  26. *
  27. * @param array $from_lines An array of strings. Typically these are lines from a file.
  28. * @param array $to_lines An array of strings.
  29. */
  30. function diff(&$from_content, &$to_content, $preserve_cr = true)
  31. {
  32. $diff_engine = new diff_engine();
  33. $this->_edits = $diff_engine->diff($from_content, $to_content, $preserve_cr);
  34. }
  35. /**
  36. * Returns the array of differences.
  37. */
  38. function get_diff()
  39. {
  40. return $this->_edits;
  41. }
  42. /**
  43. * returns the number of new (added) lines in a given diff.
  44. *
  45. * @since Text_Diff 1.1.0
  46. *
  47. * @return integer The number of new lines
  48. */
  49. function count_added_lines()
  50. {
  51. $count = 0;
  52. foreach ($this->_edits as $edit)
  53. {
  54. if (is_a($edit, 'diff_op_add') || is_a($edit, 'diff_op_change'))
  55. {
  56. $count += $edit->nfinal();
  57. }
  58. }
  59. return $count;
  60. }
  61. /**
  62. * Returns the number of deleted (removed) lines in a given diff.
  63. *
  64. * @since Text_Diff 1.1.0
  65. *
  66. * @return integer The number of deleted lines
  67. */
  68. function count_deleted_lines()
  69. {
  70. $count = 0;
  71. foreach ($this->_edits as $edit)
  72. {
  73. if (is_a($edit, 'diff_op_delete') || is_a($edit, 'diff_op_change'))
  74. {
  75. $count += $edit->norig();
  76. }
  77. }
  78. return $count;
  79. }
  80. /**
  81. * Computes a reversed diff.
  82. *
  83. * Example:
  84. * <code>
  85. * $diff = new diff($lines1, $lines2);
  86. * $rev = $diff->reverse();
  87. * </code>
  88. *
  89. * @return diff A Diff object representing the inverse of the original diff.
  90. * Note that we purposely don't return a reference here, since
  91. * this essentially is a clone() method.
  92. */
  93. function reverse()
  94. {
  95. if (version_compare(zend_version(), '2', '>'))
  96. {
  97. $rev = clone($this);
  98. }
  99. else
  100. {
  101. $rev = $this;
  102. }
  103. $rev->_edits = array();
  104. foreach ($this->_edits as $edit)
  105. {
  106. $rev->_edits[] = $edit->reverse();
  107. }
  108. return $rev;
  109. }
  110. /**
  111. * Checks for an empty diff.
  112. *
  113. * @return boolean True if two sequences were identical.
  114. */
  115. function is_empty()
  116. {
  117. foreach ($this->_edits as $edit)
  118. {
  119. if (!is_a($edit, 'diff_op_copy'))
  120. {
  121. return false;
  122. }
  123. }
  124. return true;
  125. }
  126. /**
  127. * Computes the length of the Longest Common Subsequence (LCS).
  128. *
  129. * This is mostly for diagnostic purposes.
  130. *
  131. * @return integer The length of the LCS.
  132. */
  133. function lcs()
  134. {
  135. $lcs = 0;
  136. foreach ($this->_edits as $edit)
  137. {
  138. if (is_a($edit, 'diff_op_copy'))
  139. {
  140. $lcs += sizeof($edit->orig);
  141. }
  142. }
  143. return $lcs;
  144. }
  145. /**
  146. * Gets the original set of lines.
  147. *
  148. * This reconstructs the $from_lines parameter passed to the constructor.
  149. *
  150. * @return array The original sequence of strings.
  151. */
  152. function get_original()
  153. {
  154. $lines = array();
  155. foreach ($this->_edits as $edit)
  156. {
  157. if ($edit->orig)
  158. {
  159. array_splice($lines, sizeof($lines), 0, $edit->orig);
  160. }
  161. }
  162. return $lines;
  163. }
  164. /**
  165. * Gets the final set of lines.
  166. *
  167. * This reconstructs the $to_lines parameter passed to the constructor.
  168. *
  169. * @return array The sequence of strings.
  170. */
  171. function get_final()
  172. {
  173. $lines = array();
  174. foreach ($this->_edits as $edit)
  175. {
  176. if ($edit->final)
  177. {
  178. array_splice($lines, sizeof($lines), 0, $edit->final);
  179. }
  180. }
  181. return $lines;
  182. }
  183. /**
  184. * Removes trailing newlines from a line of text. This is meant to be used with array_walk().
  185. *
  186. * @param string &$line The line to trim.
  187. * @param integer $key The index of the line in the array. Not used.
  188. */
  189. function trim_newlines(&$line, $key)
  190. {
  191. $line = str_replace(array("\n", "\r"), '', $line);
  192. }
  193. /**
  194. * Checks a diff for validity.
  195. *
  196. * This is here only for debugging purposes.
  197. */
  198. function _check($from_lines, $to_lines)
  199. {
  200. if (serialize($from_lines) != serialize($this->get_original()))
  201. {
  202. trigger_error("[diff] Reconstructed original doesn't match", E_USER_ERROR);
  203. }
  204. if (serialize($to_lines) != serialize($this->get_final()))
  205. {
  206. trigger_error("[diff] Reconstructed final doesn't match", E_USER_ERROR);
  207. }
  208. $rev = $this->reverse();
  209. if (serialize($to_lines) != serialize($rev->get_original()))
  210. {
  211. trigger_error("[diff] Reversed original doesn't match", E_USER_ERROR);
  212. }
  213. if (serialize($from_lines) != serialize($rev->get_final()))
  214. {
  215. trigger_error("[diff] Reversed final doesn't match", E_USER_ERROR);
  216. }
  217. $prevtype = null;
  218. foreach ($this->_edits as $edit)
  219. {
  220. if ($prevtype == get_class($edit))
  221. {
  222. trigger_error("[diff] Edit sequence is non-optimal", E_USER_ERROR);
  223. }
  224. $prevtype = get_class($edit);
  225. }
  226. return true;
  227. }
  228. }
  229. /**
  230. * @package diff
  231. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  232. */
  233. class mapped_diff extends diff
  234. {
  235. /**
  236. * Computes a diff between sequences of strings.
  237. *
  238. * This can be used to compute things like case-insensitve diffs, or diffs
  239. * which ignore changes in white-space.
  240. *
  241. * @param array $from_lines An array of strings.
  242. * @param array $to_lines An array of strings.
  243. * @param array $mapped_from_lines This array should have the same size number of elements as $from_lines.
  244. * The elements in $mapped_from_lines and $mapped_to_lines are what is actually
  245. * compared when computing the diff.
  246. * @param array $mapped_to_lines This array should have the same number of elements as $to_lines.
  247. */
  248. function mapped_diff(&$from_lines, &$to_lines, &$mapped_from_lines, &$mapped_to_lines)
  249. {
  250. if (sizeof($from_lines) != sizeof($mapped_from_lines) || sizeof($to_lines) != sizeof($mapped_to_lines))
  251. {
  252. return false;
  253. }
  254. parent::diff($mapped_from_lines, $mapped_to_lines);
  255. $xi = $yi = 0;
  256. for ($i = 0; $i < sizeof($this->_edits); $i++)
  257. {
  258. $orig = &$this->_edits[$i]->orig;
  259. if (is_array($orig))
  260. {
  261. $orig = array_slice($from_lines, $xi, sizeof($orig));
  262. $xi += sizeof($orig);
  263. }
  264. $final = &$this->_edits[$i]->final;
  265. if (is_array($final))
  266. {
  267. $final = array_slice($to_lines, $yi, sizeof($final));
  268. $yi += sizeof($final);
  269. }
  270. }
  271. }
  272. }
  273. /**
  274. * @package diff
  275. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  276. *
  277. * @access private
  278. */
  279. class diff_op
  280. {
  281. var $orig;
  282. var $final;
  283. function &reverse()
  284. {
  285. trigger_error('[diff] Abstract method', E_USER_ERROR);
  286. }
  287. function norig()
  288. {
  289. return ($this->orig) ? sizeof($this->orig) : 0;
  290. }
  291. function nfinal()
  292. {
  293. return ($this->final) ? sizeof($this->final) : 0;
  294. }
  295. }
  296. /**
  297. * @package diff
  298. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  299. *
  300. * @access private
  301. */
  302. class diff_op_copy extends diff_op
  303. {
  304. function diff_op_copy($orig, $final = false)
  305. {
  306. if (!is_array($final))
  307. {
  308. $final = $orig;
  309. }
  310. $this->orig = $orig;
  311. $this->final = $final;
  312. }
  313. function &reverse()
  314. {
  315. $reverse = new diff_op_copy($this->final, $this->orig);
  316. return $reverse;
  317. }
  318. }
  319. /**
  320. * @package diff
  321. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  322. *
  323. * @access private
  324. */
  325. class diff_op_delete extends diff_op
  326. {
  327. function diff_op_delete($lines)
  328. {
  329. $this->orig = $lines;
  330. $this->final = false;
  331. }
  332. function &reverse()
  333. {
  334. $reverse = new diff_op_add($this->orig);
  335. return $reverse;
  336. }
  337. }
  338. /**
  339. * @package diff
  340. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  341. *
  342. * @access private
  343. */
  344. class diff_op_add extends diff_op
  345. {
  346. function diff_op_add($lines)
  347. {
  348. $this->final = $lines;
  349. $this->orig = false;
  350. }
  351. function &reverse()
  352. {
  353. $reverse = new diff_op_delete($this->final);
  354. return $reverse;
  355. }
  356. }
  357. /**
  358. * @package diff
  359. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  360. *
  361. * @access private
  362. */
  363. class diff_op_change extends diff_op
  364. {
  365. function diff_op_change($orig, $final)
  366. {
  367. $this->orig = $orig;
  368. $this->final = $final;
  369. }
  370. function &reverse()
  371. {
  372. $reverse = new diff_op_change($this->final, $this->orig);
  373. return $reverse;
  374. }
  375. }
  376. /**
  377. * A class for computing three way diffs.
  378. *
  379. * @package diff
  380. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  381. */
  382. class diff3 extends diff
  383. {
  384. /**
  385. * Conflict counter.
  386. * @var integer
  387. */
  388. var $_conflicting_blocks = 0;
  389. /**
  390. * Computes diff between 3 sequences of strings.
  391. *
  392. * @param array $orig The original lines to use.
  393. * @param array $final1 The first version to compare to.
  394. * @param array $final2 The second version to compare to.
  395. */
  396. function diff3(&$orig, &$final1, &$final2)
  397. {
  398. $diff_engine = new diff_engine();
  399. $diff_1 = $diff_engine->diff($orig, $final1);
  400. $diff_2 = $diff_engine->diff($orig, $final2);
  401. unset($engine);
  402. $this->_edits = $this->_diff3($diff_1, $diff_2);
  403. }
  404. /**
  405. * Return number of conflicts
  406. */
  407. function get_num_conflicts()
  408. {
  409. $conflicts = 0;
  410. foreach ($this->_edits as $edit)
  411. {
  412. if ($edit->is_conflict())
  413. {
  414. $conflicts++;
  415. }
  416. }
  417. return $conflicts;
  418. }
  419. /**
  420. * Get conflicts content for download. This is generally a merged file, but preserving conflicts and adding explanations to it.
  421. * A user could then go through this file, search for the conflicts and changes the code accordingly.
  422. *
  423. * @param string $label1 the cvs file version/label from the original set of lines
  424. * @param string $label2 the cvs file version/label from the new set of lines
  425. * @param string $label_sep the explanation between label1 and label2 - more of a helper for the user
  426. *
  427. * @return mixed the merged output
  428. */
  429. function get_conflicts_content($label1 = 'CURRENT_FILE', $label2 = 'NEW_FILE', $label_sep = 'DIFF_SEP_EXPLAIN')
  430. {
  431. global $user;
  432. $label1 = (!empty($user->lang[$label1])) ? $user->lang[$label1] : $label1;
  433. $label2 = (!empty($user->lang[$label2])) ? $user->lang[$label2] : $label2;
  434. $label_sep = (!empty($user->lang[$label_sep])) ? $user->lang[$label_sep] : $label_sep;
  435. $lines = array();
  436. foreach ($this->_edits as $edit)
  437. {
  438. if ($edit->is_conflict())
  439. {
  440. // Start conflict label
  441. $label_start = array('<<<<<<< ' . $label1);
  442. $label_mid = array('======= ' . $label_sep);
  443. $label_end = array('>>>>>>> ' . $label2);
  444. $lines = array_merge($lines, $label_start, $edit->final1, $label_mid, $edit->final2, $label_end);
  445. $this->_conflicting_blocks++;
  446. }
  447. else
  448. {
  449. $lines = array_merge($lines, $edit->merged());
  450. }
  451. }
  452. return $lines;
  453. }
  454. /**
  455. * Return merged output (used by the renderer)
  456. *
  457. * @return mixed the merged output
  458. */
  459. function merged_output()
  460. {
  461. return $this->get_conflicts_content();
  462. }
  463. /**
  464. * Merge the output and use the new file code for conflicts
  465. */
  466. function merged_new_output()
  467. {
  468. $lines = array();
  469. foreach ($this->_edits as $edit)
  470. {
  471. if ($edit->is_conflict())
  472. {
  473. $lines = array_merge($lines, $edit->final2);
  474. }
  475. else
  476. {
  477. $lines = array_merge($lines, $edit->merged());
  478. }
  479. }
  480. return $lines;
  481. }
  482. /**
  483. * Merge the output and use the original file code for conflicts
  484. */
  485. function merged_orig_output()
  486. {
  487. $lines = array();
  488. foreach ($this->_edits as $edit)
  489. {
  490. if ($edit->is_conflict())
  491. {
  492. $lines = array_merge($lines, $edit->final1);
  493. }
  494. else
  495. {
  496. $lines = array_merge($lines, $edit->merged());
  497. }
  498. }
  499. return $lines;
  500. }
  501. /**
  502. * Get conflicting block(s)
  503. */
  504. function get_conflicts()
  505. {
  506. $conflicts = array();
  507. foreach ($this->_edits as $edit)
  508. {
  509. if ($edit->is_conflict())
  510. {
  511. $conflicts[] = array($edit->final1, $edit->final2);
  512. }
  513. }
  514. return $conflicts;
  515. }
  516. /**
  517. * @access private
  518. */
  519. function _diff3(&$edits1, &$edits2)
  520. {
  521. $edits = array();
  522. $bb = new diff3_block_builder();
  523. $e1 = current($edits1);
  524. $e2 = current($edits2);
  525. while ($e1 || $e2)
  526. {
  527. if ($e1 && $e2 && is_a($e1, 'diff_op_copy') && is_a($e2, 'diff_op_copy'))
  528. {
  529. // We have copy blocks from both diffs. This is the (only) time we want to emit a diff3 copy block.
  530. // Flush current diff3 diff block, if any.
  531. if ($edit = $bb->finish())
  532. {
  533. $edits[] = $edit;
  534. }
  535. $ncopy = min($e1->norig(), $e2->norig());
  536. $edits[] = new diff3_op_copy(array_slice($e1->orig, 0, $ncopy));
  537. if ($e1->norig() > $ncopy)
  538. {
  539. array_splice($e1->orig, 0, $ncopy);
  540. array_splice($e1->final, 0, $ncopy);
  541. }
  542. else
  543. {
  544. $e1 = next($edits1);
  545. }
  546. if ($e2->norig() > $ncopy)
  547. {
  548. array_splice($e2->orig, 0, $ncopy);
  549. array_splice($e2->final, 0, $ncopy);
  550. }
  551. else
  552. {
  553. $e2 = next($edits2);
  554. }
  555. }
  556. else
  557. {
  558. if ($e1 && $e2)
  559. {
  560. if ($e1->orig && $e2->orig)
  561. {
  562. $norig = min($e1->norig(), $e2->norig());
  563. $orig = array_splice($e1->orig, 0, $norig);
  564. array_splice($e2->orig, 0, $norig);
  565. $bb->input($orig);
  566. }
  567. else
  568. {
  569. $norig = 0;
  570. }
  571. if (is_a($e1, 'diff_op_copy'))
  572. {
  573. $bb->out1(array_splice($e1->final, 0, $norig));
  574. }
  575. if (is_a($e2, 'diff_op_copy'))
  576. {
  577. $bb->out2(array_splice($e2->final, 0, $norig));
  578. }
  579. }
  580. if ($e1 && ! $e1->orig)
  581. {
  582. $bb->out1($e1->final);
  583. $e1 = next($edits1);
  584. }
  585. if ($e2 && ! $e2->orig)
  586. {
  587. $bb->out2($e2->final);
  588. $e2 = next($edits2);
  589. }
  590. }
  591. }
  592. if ($edit = $bb->finish())
  593. {
  594. $edits[] = $edit;
  595. }
  596. return $edits;
  597. }
  598. }
  599. /**
  600. * @package diff
  601. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  602. *
  603. * @access private
  604. */
  605. class diff3_op
  606. {
  607. function diff3_op($orig = false, $final1 = false, $final2 = false)
  608. {
  609. $this->orig = $orig ? $orig : array();
  610. $this->final1 = $final1 ? $final1 : array();
  611. $this->final2 = $final2 ? $final2 : array();
  612. }
  613. function merged()
  614. {
  615. if (!isset($this->_merged))
  616. {
  617. if ($this->final1 === $this->final2)
  618. {
  619. $this->_merged = &$this->final1;
  620. }
  621. else if ($this->final1 === $this->orig)
  622. {
  623. $this->_merged = &$this->final2;
  624. }
  625. else if ($this->final2 === $this->orig)
  626. {
  627. $this->_merged = &$this->final1;
  628. }
  629. else
  630. {
  631. $this->_merged = false;
  632. }
  633. }
  634. return $this->_merged;
  635. }
  636. function is_conflict()
  637. {
  638. return ($this->merged() === false) ? true : false;
  639. }
  640. }
  641. /**
  642. * @package diff
  643. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  644. *
  645. * @access private
  646. */
  647. class diff3_op_copy extends diff3_op
  648. {
  649. function diff3_op_copy($lines = false)
  650. {
  651. $this->orig = $lines ? $lines : array();
  652. $this->final1 = &$this->orig;
  653. $this->final2 = &$this->orig;
  654. }
  655. function merged()
  656. {
  657. return $this->orig;
  658. }
  659. function is_conflict()
  660. {
  661. return false;
  662. }
  663. }
  664. /**
  665. * @package diff
  666. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  667. *
  668. * @access private
  669. */
  670. class diff3_block_builder
  671. {
  672. function diff3_block_builder()
  673. {
  674. $this->_init();
  675. }
  676. function input($lines)
  677. {
  678. if ($lines)
  679. {
  680. $this->_append($this->orig, $lines);
  681. }
  682. }
  683. function out1($lines)
  684. {
  685. if ($lines)
  686. {
  687. $this->_append($this->final1, $lines);
  688. }
  689. }
  690. function out2($lines)
  691. {
  692. if ($lines)
  693. {
  694. $this->_append($this->final2, $lines);
  695. }
  696. }
  697. function is_empty()
  698. {
  699. return !$this->orig && !$this->final1 && !$this->final2;
  700. }
  701. function finish()
  702. {
  703. if ($this->is_empty())
  704. {
  705. return false;
  706. }
  707. else
  708. {
  709. $edit = new diff3_op($this->orig, $this->final1, $this->final2);
  710. $this->_init();
  711. return $edit;
  712. }
  713. }
  714. function _init()
  715. {
  716. $this->orig = $this->final1 = $this->final2 = array();
  717. }
  718. function _append(&$array, $lines)
  719. {
  720. array_splice($array, sizeof($array), 0, $lines);
  721. }
  722. }
  723. ?>