ThreeWay.php 7.1 KB

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