Renderer.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. /**
  3. * A class to render Diffs in different formats.
  4. *
  5. * This class renders the diff in classic diff format. It is intended that
  6. * this class be customized via inheritance, to obtain fancier outputs.
  7. *
  8. * Copyright 2004-2012 Horde LLC (http://www.horde.org/)
  9. *
  10. * See the enclosed file COPYING for license information (LGPL). If you did
  11. * not receive this file, see http://www.horde.org/licenses/lgpl21.
  12. *
  13. * @package Text_Diff
  14. */
  15. class Horde_Text_Diff_Renderer
  16. {
  17. /**
  18. * Number of leading context "lines" to preserve.
  19. *
  20. * This should be left at zero for this class, but subclasses may want to
  21. * set this to other values.
  22. */
  23. protected $_leading_context_lines = 0;
  24. /**
  25. * Number of trailing context "lines" to preserve.
  26. *
  27. * This should be left at zero for this class, but subclasses may want to
  28. * set this to other values.
  29. */
  30. protected $_trailing_context_lines = 0;
  31. /**
  32. * Constructor.
  33. */
  34. public function __construct($params = [])
  35. {
  36. foreach ($params as $param => $value) {
  37. $v = '_' . $param;
  38. if (isset($this->$v)) {
  39. $this->$v = $value;
  40. }
  41. }
  42. }
  43. /**
  44. * Get any renderer parameters.
  45. *
  46. * @return array All parameters of this renderer object.
  47. */
  48. public function getParams()
  49. {
  50. $params = [];
  51. foreach (get_object_vars($this) as $k => $v) {
  52. if ($k[0] == '_') {
  53. $params[substr($k, 1)] = $v;
  54. }
  55. }
  56. return $params;
  57. }
  58. /**
  59. * Renders a diff.
  60. *
  61. * @param Horde_Text_Diff $diff A Horde_Text_Diff object.
  62. *
  63. * @return string The formatted output.
  64. */
  65. public function render($diff)
  66. {
  67. $xi = $yi = 1;
  68. $block = false;
  69. $context = [];
  70. $nlead = $this->_leading_context_lines;
  71. $ntrail = $this->_trailing_context_lines;
  72. $output = $this->_startDiff();
  73. $diffs = $diff->getDiff();
  74. foreach ($diffs as $i => $edit) {
  75. /* If these are unchanged (copied) lines, and we want to keep
  76. * leading or trailing context lines, extract them from the copy
  77. * block. */
  78. if ($edit instanceof Horde_Text_Diff_Op_Copy) {
  79. /* Do we have any diff blocks yet? */
  80. if (is_array($block)) {
  81. /* How many lines to keep as context from the copy
  82. * block. */
  83. $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;
  84. if (count($edit->orig) <= $keep) {
  85. /* We have less lines in the block than we want for
  86. * context => keep the whole block. */
  87. $block[] = $edit;
  88. } else {
  89. if ($ntrail) {
  90. /* Create a new block with as many lines as we need
  91. * for the trailing context. */
  92. $context = array_slice($edit->orig, 0, $ntrail);
  93. $block[] = new Horde_Text_Diff_Op_Copy($context);
  94. }
  95. /* @todo */
  96. $output .= $this->_block($x0, $ntrail + $xi - $x0,
  97. $y0, $ntrail + $yi - $y0,
  98. $block);
  99. $block = false;
  100. }
  101. }
  102. /* Keep the copy block as the context for the next block. */
  103. $context = $edit->orig;
  104. } else {
  105. /* Don't we have any diff blocks yet? */
  106. if (!is_array($block)) {
  107. /* Extract context lines from the preceding copy block. */
  108. $context = array_slice($context, count($context) - $nlead);
  109. $x0 = $xi - count($context);
  110. $y0 = $yi - count($context);
  111. $block = [];
  112. if ($context) {
  113. $block[] = new Horde_Text_Diff_Op_Copy($context);
  114. }
  115. }
  116. $block[] = $edit;
  117. }
  118. if ($edit->orig) {
  119. $xi += count($edit->orig);
  120. }
  121. if ($edit->final) {
  122. $yi += count($edit->final);
  123. }
  124. }
  125. if (is_array($block)) {
  126. $output .= $this->_block($x0, $xi - $x0,
  127. $y0, $yi - $y0,
  128. $block);
  129. }
  130. return $output . $this->_endDiff();
  131. }
  132. protected function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
  133. {
  134. $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));
  135. foreach ($edits as $edit) {
  136. switch (get_class($edit)) {
  137. case 'Horde_Text_Diff_Op_Copy':
  138. $output .= $this->_context($edit->orig);
  139. break;
  140. case 'Horde_Text_Diff_Op_Add':
  141. $output .= $this->_added($edit->final);
  142. break;
  143. case 'Horde_Text_Diff_Op_Delete':
  144. $output .= $this->_deleted($edit->orig);
  145. break;
  146. case 'Horde_Text_Diff_Op_Change':
  147. $output .= $this->_changed($edit->orig, $edit->final);
  148. break;
  149. }
  150. }
  151. return $output . $this->_endBlock();
  152. }
  153. protected function _startDiff()
  154. {
  155. return '';
  156. }
  157. protected function _endDiff()
  158. {
  159. return '';
  160. }
  161. protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
  162. {
  163. if ($xlen > 1) {
  164. $xbeg .= ',' . ($xbeg + $xlen - 1);
  165. }
  166. if ($ylen > 1) {
  167. $ybeg .= ',' . ($ybeg + $ylen - 1);
  168. }
  169. // this matches the GNU Diff behaviour
  170. if ($xlen && !$ylen) {
  171. $ybeg--;
  172. } elseif (!$xlen) {
  173. $xbeg--;
  174. }
  175. return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
  176. }
  177. protected function _startBlock($header)
  178. {
  179. return $header . "\n";
  180. }
  181. protected function _endBlock()
  182. {
  183. return '';
  184. }
  185. protected function _lines($lines, $prefix = ' ')
  186. {
  187. return $prefix . implode("\n$prefix", $lines) . "\n";
  188. }
  189. protected function _context($lines)
  190. {
  191. return $this->_lines($lines, ' ');
  192. }
  193. protected function _added($lines)
  194. {
  195. return $this->_lines($lines, '> ');
  196. }
  197. protected function _deleted($lines)
  198. {
  199. return $this->_lines($lines, '< ');
  200. }
  201. protected function _changed($orig, $final)
  202. {
  203. return $this->_deleted($orig) . "---\n" . $this->_added($final);
  204. }
  205. }