DiffLib.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. /**
  3. * A wrapper class to generate diffs.
  4. *
  5. * Text:
  6. * - Context
  7. * - Unified
  8. * HTML:
  9. * - Inline
  10. * - SideBySide
  11. *
  12. * Currently uses Horde classes, but could also use PEAR or php-diff packages.
  13. *
  14. * @author Mark Scherer
  15. * @license MIT
  16. * 2010-09-04 ms
  17. */
  18. class DiffLib {
  19. /**
  20. * What engine should Text_Diff use.
  21. * Avaible: auto (chooses best), native, xdiff
  22. *
  23. * @var string
  24. */
  25. public $engine = 'auto';
  26. # xdiff needs external libs
  27. public $engines = array('auto', 'native', 'shell', 'xdiff');
  28. /**
  29. * What renderer to use
  30. * for avaible renderers look in Text/Diff/Renderer/*
  31. * Standard: unified, context, inline
  32. * Additional: sidebyside?
  33. *
  34. * @var string
  35. */
  36. public $renderer = 'inline';
  37. public $renderers = array('inline', 'unified', 'context'); //'sidebyside'
  38. /**
  39. * Do you want to use the Character diff renderer additionally to the sidebyside renderer ?
  40. * sidebyside renderer is the only one supporting the additional renderer
  41. *
  42. * @var bool
  43. */
  44. public $character_diff = true;
  45. /**
  46. * If the params are strings on what characters do you want to explode the string?
  47. * Can be an array if you want to explode on multiple chars
  48. *
  49. * @var mixed
  50. */
  51. public $explode_on = "\r\n";
  52. /**
  53. * How many context lines do you want to see around the changed line?
  54. *
  55. * @var int
  56. */
  57. public $context_lines = 4;
  58. /**
  59. * DiffLib::__construct()
  60. *
  61. * @return void
  62. */
  63. public function __construct() {
  64. set_include_path(get_include_path() . PATH_SEPARATOR . CakePlugin::path('Tools') . 'Vendor' . DS);
  65. App::import('Vendor', 'Tools.HordeAutoloader', array('file'=> 'Horde/Autoloader/Default.php'));
  66. }
  67. /**
  68. * Set/Get renderer
  69. *
  70. * @param string $renderType
  71. * 'unified', 'inline', 'context', 'sidebyside'
  72. * defaults to "inline"
  73. * @return boolean true on success, false otherwise
  74. * 2010-01-12 ms
  75. */
  76. public function renderType($type = null) {
  77. if ($type === null) {
  78. return $this->renderer;
  79. }
  80. if (in_array($type, $this->renderers)) {
  81. $this->renderer = $type;
  82. return true;
  83. }
  84. return false;
  85. }
  86. /**
  87. * Set/Get engine
  88. *
  89. * @param string $engineType
  90. * 'auto', 'native', 'xdiff', 'shell', 'string'
  91. * defaults to "auto"
  92. * @return boolean true on success, false otherwise
  93. * 2010-01-12 ms
  94. */
  95. public function engineType($type = null) {
  96. if ($type === null) {
  97. return $this->engine;
  98. }
  99. if (in_array($type, $this->engines)) {
  100. $this->engine = $type;
  101. return true;
  102. }
  103. return false;
  104. }
  105. /**
  106. * compare function
  107. * Compares two strings/arrays using the specified method and renderer
  108. *
  109. * @param mixed $original
  110. * @param mixed $changed
  111. * @param array $options
  112. * - div: true/false
  113. * - class: defaults to "diff"
  114. * - escape: defaults to true
  115. * @return string $output
  116. */
  117. public function compare($original, $changed, $options = array()) {
  118. if (!is_array($original)) {
  119. $original = $this->_explode($original);
  120. }
  121. if (!is_array($changed)) {
  122. $changed = $this->_explode($changed);
  123. }
  124. $rendererClassName = 'Horde_Text_Diff_Renderer_'.ucfirst($this->renderer);
  125. $renderer = new $rendererClassName(array('context_lines' => $this->context_lines, 'character_diff' =>$this->character_diff));
  126. $diff = new Horde_Text_Diff($this->engine, array($original, $changed));
  127. $string = $renderer->render($diff);
  128. return $string;
  129. }
  130. /**
  131. * @param string $string Either context or unified diff snippet
  132. * @param array $options
  133. * - mode (autodetect, context, unified)
  134. */
  135. public function reverse($string, $options = array()) {
  136. $defaults = array(
  137. 'mode' => 'autodetect',
  138. );
  139. $options += $defaults;
  140. $diff = new Horde_Text_Diff('string', array($string, $options['mode']));
  141. $rendererClassName = 'Horde_Text_Diff_Renderer_'.ucfirst($this->renderer);
  142. $renderer = new $rendererClassName(array('context_lines' => $this->context_lines, 'character_diff' =>$this->character_diff));
  143. $string = $renderer->render($diff);
  144. return $string;
  145. }
  146. /**
  147. * explodes the string into an array
  148. *
  149. * @param string $text
  150. * @return array
  151. */
  152. protected function _explode($text) {
  153. if (is_array($this->explode_on)) {
  154. foreach ($this->explode_on as $explode_on) {
  155. $text = explode($explode_on, $text);
  156. }
  157. return $text;
  158. }
  159. return explode($this->explode_on, $text);
  160. }
  161. /**
  162. * Parses a unified diff output
  163. * @param array $text an entire section of a unified diff (between @@ lines)
  164. * @param char $_check a '+' or '-' denoting whether we're looking for lines
  165. * added or removed
  166. * @return
  167. */
  168. public function parseDiff($text, $_check) {
  169. $start = 0; // Start of the diff
  170. $length = 0; // number of lines to recurse
  171. $changes = array(); // all the changes
  172. $regs = array();
  173. if (preg_match("/^@@ ([\+\-])([0-9]+)(?:,([0-9]+))? [\+\-]([0-9]+)(?:,([0-9]+))? @@$/", array_shift($text), $regs) == false) {
  174. return;
  175. }
  176. $start = $regs[4];
  177. $length = count($text);
  178. $instance = new Changes();
  179. /* We don't count removed lines when looking at start of a change. For
  180. * example, we have this :
  181. * - foo
  182. * + bar
  183. * bar starts at line 1, not line 2.
  184. */
  185. $minus = 0;
  186. for ($i = 0; $i < $length; $i++) {
  187. $line = $text[$i];
  188. // empty line? EOF?
  189. if (strlen($line) == 0) {
  190. if ($instance->length > 0) {
  191. array_push($changes, $instance);
  192. $instance = new Changes();
  193. }
  194. continue;
  195. }
  196. if ($_check === '-' && $_check == $line[0]) {
  197. if ($instance->length == 0) {
  198. $instance->line = $start + $i - $minus;
  199. $instance->symbol = $line[0];
  200. $instance->length++;
  201. array_push($instance->oldline, substr($line, 1));
  202. } elseif ($_check === '+' && $_check == $line[0]) {
  203. if ($instance->length == 0) {
  204. $instance->line = $start + $i - $minus;
  205. $instance->symbol = $line[0];
  206. }
  207. $instance->length++;
  208. } else {
  209. if ($instance->length > 0) {
  210. array_push($changes, $instance);
  211. $instance = new Changes();
  212. }
  213. }
  214. if ($line[0] === '-')
  215. $minus++;
  216. }
  217. }
  218. if ($instance->length > 0) {
  219. array_push($changes, $instance);
  220. $instance = new Changes();
  221. }
  222. return $changes;
  223. }
  224. /**
  225. * Appends or Replaces text
  226. * @param array &$text Array of Line objects
  227. * @param array $change Array of Change objects
  228. * @param int &$offset how many lines to skip due to previous additions
  229. */
  230. public function applyChange(&$text, $change, &$offset = 0) {
  231. $index = 0;
  232. // $i is the change we are on
  233. for ($i = 0; $i < count($change); $i++) {
  234. $lines = $change[$i];
  235. // $j is the line within the change
  236. for ($j = 0; $j < $lines->length; $j++) {
  237. $linenum = $lines->line - 1;
  238. $line = $text[$linenum+$j+$offset];
  239. $color = 'green';
  240. if (strlen(ltrim($line->text)) == 0) {
  241. continue;
  242. }
  243. if ($lines->symbol === '-') {
  244. $add = $lines->oldline;
  245. array_splice($text, $linenum + $j + $offset, 0, $add);
  246. // $k is the counter for the old lines we
  247. // removed from the previous version
  248. for ($k = 0; $k < count($add); $k++) {
  249. $l = new Line();
  250. $l->changed = true;
  251. $l->symbol = '-';
  252. $l->text = sprintf("%s <span class='diff-remove'>%s</span>\n", "-", rtrim($add[$k], "\r\n"));
  253. $text[$linenum+$j+$k+$offset] = $l;
  254. }
  255. $offset += count($add);
  256. } else {
  257. $l = new Line();
  258. $l->symbol = '+';
  259. $l->changed = true;
  260. $l->text = sprintf("%s <span class='diff-add'>%s</span>\n", $lines->symbol, rtrim($line->text, "\r\n"));
  261. $text[$linenum+$j] = $l;
  262. }
  263. }
  264. }
  265. }
  266. }
  267. /*** other files **/
  268. class Changes {
  269. public $line = 0;
  270. public $length = 0;
  271. public $symbol;
  272. public $oldline = array(); // only for code removed
  273. }
  274. // This object is created for every line of text in the file.
  275. // It was either this, or some funk string changes
  276. class Line {
  277. public $text = '';
  278. public $symbol = '';
  279. public $changed = false;
  280. }