ThreeWay.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. /**
  3. * A class for computing three way diffs.
  4. *
  5. * $Horde: framework/Text_Diff/Diff/ThreeWay.php,v 1.3.2.4 2009/01/06 15:23:41 jan Exp $
  6. *
  7. * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
  8. *
  9. * See the enclosed file COPYING for license information (LGPL). If you did
  10. * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
  11. *
  12. * @package Text_Diff
  13. * @since 0.3.0
  14. */
  15. /** Text_Diff */
  16. /**
  17. * A class for computing three way diffs.
  18. *
  19. * @package Text_Diff
  20. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  21. */
  22. class Text_Diff_ThreeWay extends Text_Diff {
  23. /**
  24. * Conflict counter.
  25. *
  26. * @var integer
  27. */
  28. var $_conflictingBlocks = 0;
  29. /**
  30. * Computes diff between 3 sequences of strings.
  31. *
  32. * @param array $orig The original lines to use.
  33. * @param array $final1 The first version to compare to.
  34. * @param array $final2 The second version to compare to.
  35. */
  36. function Text_Diff_ThreeWay($orig, $final1, $final2)
  37. {
  38. if (extension_loaded('xdiff')) {
  39. $engine = new Text_Diff_Engine_xdiff();
  40. } else {
  41. $engine = new Text_Diff_Engine_native();
  42. }
  43. $this->_edits = $this->_diff3($engine->diff($orig, $final1),
  44. $engine->diff($orig, $final2));
  45. }
  46. /**
  47. */
  48. function mergedOutput($label1 = false, $label2 = false)
  49. {
  50. $lines = array();
  51. foreach ($this->_edits as $edit) {
  52. if ($edit->isConflict()) {
  53. /* FIXME: this should probably be moved somewhere else. */
  54. $lines = array_merge($lines,
  55. array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')),
  56. $edit->final1,
  57. array("======="),
  58. $edit->final2,
  59. array('>>>>>>>' . ($label2 ? ' ' . $label2 : '')));
  60. $this->_conflictingBlocks++;
  61. } else {
  62. $lines = array_merge($lines, $edit->merged());
  63. }
  64. }
  65. return $lines;
  66. }
  67. /**
  68. * @access private
  69. */
  70. function _diff3($edits1, $edits2)
  71. {
  72. $edits = array();
  73. $bb = new Text_Diff_ThreeWay_BlockBuilder();
  74. $e1 = current($edits1);
  75. $e2 = current($edits2);
  76. while ($e1 || $e2) {
  77. if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) {
  78. /* We have copy blocks from both diffs. This is the (only)
  79. * time we want to emit a diff3 copy block. Flush current
  80. * diff3 diff block, if any. */
  81. if ($edit = $bb->finish()) {
  82. $edits[] = $edit;
  83. }
  84. $ncopy = min($e1->norig(), $e2->norig());
  85. assert($ncopy > 0);
  86. $edits[] = new Text_Diff_ThreeWay_Op_copy(array_slice($e1->orig, 0, $ncopy));
  87. if ($e1->norig() > $ncopy) {
  88. array_splice($e1->orig, 0, $ncopy);
  89. array_splice($e1->final, 0, $ncopy);
  90. } else {
  91. $e1 = next($edits1);
  92. }
  93. if ($e2->norig() > $ncopy) {
  94. array_splice($e2->orig, 0, $ncopy);
  95. array_splice($e2->final, 0, $ncopy);
  96. } else {
  97. $e2 = next($edits2);
  98. }
  99. } else {
  100. if ($e1 && $e2) {
  101. if ($e1->orig && $e2->orig) {
  102. $norig = min($e1->norig(), $e2->norig());
  103. $orig = array_splice($e1->orig, 0, $norig);
  104. array_splice($e2->orig, 0, $norig);
  105. $bb->input($orig);
  106. }
  107. if (is_a($e1, 'Text_Diff_Op_copy')) {
  108. $bb->out1(array_splice($e1->final, 0, $norig));
  109. }
  110. if (is_a($e2, 'Text_Diff_Op_copy')) {
  111. $bb->out2(array_splice($e2->final, 0, $norig));
  112. }
  113. }
  114. if ($e1 && ! $e1->orig) {
  115. $bb->out1($e1->final);
  116. $e1 = next($edits1);
  117. }
  118. if ($e2 && ! $e2->orig) {
  119. $bb->out2($e2->final);
  120. $e2 = next($edits2);
  121. }
  122. }
  123. }
  124. if ($edit = $bb->finish()) {
  125. $edits[] = $edit;
  126. }
  127. return $edits;
  128. }
  129. }
  130. /**
  131. * @package Text_Diff
  132. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  133. *
  134. * @access private
  135. */
  136. class Text_Diff_ThreeWay_Op {
  137. function Text_Diff_ThreeWay_Op($orig = false, $final1 = false, $final2 = false)
  138. {
  139. $this->orig = $orig ? $orig : array();
  140. $this->final1 = $final1 ? $final1 : array();
  141. $this->final2 = $final2 ? $final2 : array();
  142. }
  143. function merged()
  144. {
  145. if (!isset($this->_merged)) {
  146. if ($this->final1 === $this->final2) {
  147. $this->_merged = &$this->final1;
  148. } elseif ($this->final1 === $this->orig) {
  149. $this->_merged = &$this->final2;
  150. } elseif ($this->final2 === $this->orig) {
  151. $this->_merged = &$this->final1;
  152. } else {
  153. $this->_merged = false;
  154. }
  155. }
  156. return $this->_merged;
  157. }
  158. function isConflict()
  159. {
  160. return $this->merged() === false;
  161. }
  162. }
  163. /**
  164. * @package Text_Diff
  165. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  166. *
  167. * @access private
  168. */
  169. class Text_Diff_ThreeWay_Op_copy extends Text_Diff_ThreeWay_Op {
  170. function Text_Diff_ThreeWay_Op_Copy($lines = false)
  171. {
  172. $this->orig = $lines ? $lines : array();
  173. $this->final1 = &$this->orig;
  174. $this->final2 = &$this->orig;
  175. }
  176. function merged()
  177. {
  178. return $this->orig;
  179. }
  180. function isConflict()
  181. {
  182. return false;
  183. }
  184. }
  185. /**
  186. * @package Text_Diff
  187. * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
  188. *
  189. * @access private
  190. */
  191. class Text_Diff_ThreeWay_BlockBuilder {
  192. function Text_Diff_ThreeWay_BlockBuilder()
  193. {
  194. $this->_init();
  195. }
  196. function input($lines)
  197. {
  198. if ($lines) {
  199. $this->_append($this->orig, $lines);
  200. }
  201. }
  202. function out1($lines)
  203. {
  204. if ($lines) {
  205. $this->_append($this->final1, $lines);
  206. }
  207. }
  208. function out2($lines)
  209. {
  210. if ($lines) {
  211. $this->_append($this->final2, $lines);
  212. }
  213. }
  214. function isEmpty()
  215. {
  216. return !$this->orig && !$this->final1 && !$this->final2;
  217. }
  218. function finish()
  219. {
  220. if ($this->isEmpty()) {
  221. return false;
  222. } else {
  223. $edit = new Text_Diff_ThreeWay_Op($this->orig, $this->final1, $this->final2);
  224. $this->_init();
  225. return $edit;
  226. }
  227. }
  228. function _init()
  229. {
  230. $this->orig = $this->final1 = $this->final2 = array();
  231. }
  232. function _append(&$array, $lines)
  233. {
  234. array_splice($array, sizeof($array), 0, $lines);
  235. }
  236. }