DiffFormatter.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. /**
  3. * Base for diff rendering classes. Portions taken from phpwiki-1.3.3.
  4. *
  5. * Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
  6. * You may copy this code freely under the conditions of the GPL.
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along
  19. * with this program; if not, write to the Free Software Foundation, Inc.,
  20. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. * http://www.gnu.org/copyleft/gpl.html
  22. *
  23. * @file
  24. * @ingroup DifferenceEngine
  25. */
  26. /**
  27. * Base class for diff formatters
  28. *
  29. * This class formats the diff in classic diff format.
  30. * It is intended that this class be customized via inheritance,
  31. * to obtain fancier outputs.
  32. * @todo document
  33. * @ingroup DifferenceEngine
  34. */
  35. abstract class DiffFormatter {
  36. /** @var int Number of leading context "lines" to preserve.
  37. *
  38. * This should be left at zero for this class, but subclasses
  39. * may want to set this to other values.
  40. */
  41. protected $leadingContextLines = 0;
  42. /** @var int Number of trailing context "lines" to preserve.
  43. *
  44. * This should be left at zero for this class, but subclasses
  45. * may want to set this to other values.
  46. */
  47. protected $trailingContextLines = 0;
  48. /** @var string The output buffer; holds the output while it is built. */
  49. private $result = '';
  50. /**
  51. * Format a diff.
  52. *
  53. * @param Diff $diff
  54. *
  55. * @return string The formatted output.
  56. */
  57. public function format( $diff ) {
  58. $xi = $yi = 1;
  59. $block = false;
  60. $context = [];
  61. $nlead = $this->leadingContextLines;
  62. $ntrail = $this->trailingContextLines;
  63. $this->startDiff();
  64. // Initialize $x0 and $y0 to prevent IDEs from getting confused.
  65. $x0 = $y0 = 0;
  66. foreach ( $diff->edits as $edit ) {
  67. if ( $edit->type == 'copy' ) {
  68. if ( is_array( $block ) ) {
  69. if ( count( $edit->orig ) <= $nlead + $ntrail ) {
  70. $block[] = $edit;
  71. } else {
  72. if ( $ntrail ) {
  73. $context = array_slice( $edit->orig, 0, $ntrail );
  74. $block[] = new DiffOpCopy( $context );
  75. }
  76. $this->block( $x0, $ntrail + $xi - $x0,
  77. $y0, $ntrail + $yi - $y0,
  78. $block );
  79. $block = false;
  80. }
  81. }
  82. $context = $edit->orig;
  83. } else {
  84. if ( !is_array( $block ) ) {
  85. $context = array_slice( $context, count( $context ) - $nlead );
  86. $x0 = $xi - count( $context );
  87. $y0 = $yi - count( $context );
  88. $block = [];
  89. if ( $context ) {
  90. $block[] = new DiffOpCopy( $context );
  91. }
  92. }
  93. $block[] = $edit;
  94. }
  95. if ( $edit->orig ) {
  96. $xi += count( $edit->orig );
  97. }
  98. if ( $edit->closing ) {
  99. $yi += count( $edit->closing );
  100. }
  101. }
  102. if ( is_array( $block ) ) {
  103. $this->block( $x0, $xi - $x0,
  104. $y0, $yi - $y0,
  105. $block );
  106. }
  107. $end = $this->endDiff();
  108. return $end;
  109. }
  110. /**
  111. * @param int $xbeg
  112. * @param int $xlen
  113. * @param int $ybeg
  114. * @param int $ylen
  115. * @param array &$edits
  116. *
  117. * @throws MWException If the edit type is not known.
  118. */
  119. protected function block( $xbeg, $xlen, $ybeg, $ylen, &$edits ) {
  120. $this->startBlock( $this->blockHeader( $xbeg, $xlen, $ybeg, $ylen ) );
  121. foreach ( $edits as $edit ) {
  122. if ( $edit->type == 'copy' ) {
  123. $this->context( $edit->orig );
  124. } elseif ( $edit->type == 'add' ) {
  125. $this->added( $edit->closing );
  126. } elseif ( $edit->type == 'delete' ) {
  127. $this->deleted( $edit->orig );
  128. } elseif ( $edit->type == 'change' ) {
  129. $this->changed( $edit->orig, $edit->closing );
  130. } else {
  131. throw new MWException( "Unknown edit type: {$edit->type}" );
  132. }
  133. }
  134. $this->endBlock();
  135. }
  136. protected function startDiff() {
  137. $this->result = '';
  138. }
  139. /**
  140. * Writes a string to the output buffer.
  141. *
  142. * @param string $text
  143. */
  144. protected function writeOutput( $text ) {
  145. $this->result .= $text;
  146. }
  147. /**
  148. * @return string
  149. */
  150. protected function endDiff() {
  151. $val = $this->result;
  152. $this->result = '';
  153. return $val;
  154. }
  155. /**
  156. * @param int $xbeg
  157. * @param int $xlen
  158. * @param int $ybeg
  159. * @param int $ylen
  160. *
  161. * @return string
  162. */
  163. protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) {
  164. if ( $xlen > 1 ) {
  165. $xbeg .= ',' . ( $xbeg + $xlen - 1 );
  166. }
  167. if ( $ylen > 1 ) {
  168. $ybeg .= ',' . ( $ybeg + $ylen - 1 );
  169. }
  170. return $xbeg . ( $xlen ? ( $ylen ? 'c' : 'd' ) : 'a' ) . $ybeg;
  171. }
  172. /**
  173. * Called at the start of a block of connected edits.
  174. * This default implementation writes the header and a newline to the output buffer.
  175. *
  176. * @param string $header
  177. */
  178. protected function startBlock( $header ) {
  179. $this->writeOutput( $header . "\n" );
  180. }
  181. /**
  182. * Called at the end of a block of connected edits.
  183. * This default implementation does nothing.
  184. */
  185. protected function endBlock() {
  186. }
  187. /**
  188. * Writes all (optionally prefixed) lines to the output buffer, separated by newlines.
  189. *
  190. * @param string[] $lines
  191. * @param string $prefix
  192. */
  193. protected function lines( $lines, $prefix = ' ' ) {
  194. foreach ( $lines as $line ) {
  195. $this->writeOutput( "$prefix $line\n" );
  196. }
  197. }
  198. /**
  199. * @param string[] $lines
  200. */
  201. protected function context( $lines ) {
  202. $this->lines( $lines );
  203. }
  204. /**
  205. * @param string[] $lines
  206. */
  207. protected function added( $lines ) {
  208. $this->lines( $lines, '>' );
  209. }
  210. /**
  211. * @param string[] $lines
  212. */
  213. protected function deleted( $lines ) {
  214. $this->lines( $lines, '<' );
  215. }
  216. /**
  217. * Writes the two sets of lines to the output buffer, separated by "---" and a newline.
  218. *
  219. * @param string[] $orig
  220. * @param string[] $closing
  221. */
  222. protected function changed( $orig, $closing ) {
  223. $this->deleted( $orig );
  224. $this->writeOutput( "---\n" );
  225. $this->added( $closing );
  226. }
  227. }