You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 line
7.3KB

  1. <?php
  2. /**
  3. * Base renderer for rendering HTML based diffs for PHP DiffLib.
  4. *
  5. * PHP version 5
  6. *
  7. * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
  8. *
  9. * All rights reserved.
  10. *
  11. * Redistribution and use in source and binary forms, with or without
  12. * modification, are permitted provided that the following conditions are met:
  13. *
  14. * - Redistributions of source code must retain the above copyright notice,
  15. * this list of conditions and the following disclaimer.
  16. * - Redistributions in binary form must reproduce the above copyright notice,
  17. * this list of conditions and the following disclaimer in the documentation
  18. * and/or other materials provided with the distribution.
  19. * - Neither the name of the Chris Boulton nor the names of its contributors
  20. * may be used to endorse or promote products derived from this software
  21. * without specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  27. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. *
  35. * @package DiffLib
  36. * @author Chris Boulton <chris.boulton@interspire.com>
  37. * @copyright (c) 2009 Chris Boulton
  38. * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
  39. * @version 1.1
  40. * @link http://github.com/chrisboulton/php-diff
  41. */
  42. require_once dirname(__FILE__).'/../Abstract.php';
  43. class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract
  44. {
  45. /**
  46. * @var array Array of the default options that apply to this renderer.
  47. */
  48. protected $defaultOptions = array(
  49. 'tabSize' => 4
  50. );
  51. /**
  52. * Render and return an array structure suitable for generating HTML
  53. * based differences. Generally called by subclasses that generate a
  54. * HTML based diff and return an array of the changes to show in the diff.
  55. *
  56. * @return array An array of the generated chances, suitable for presentation in HTML.
  57. */
  58. public function render()
  59. {
  60. // As we'll be modifying a & b to include our change markers,
  61. // we need to get the contents and store them here. That way
  62. // we're not going to destroy the original data
  63. $a = $this->diff->getA();
  64. $b = $this->diff->getB();
  65. $changes = array();
  66. $opCodes = $this->diff->getGroupedOpcodes();
  67. foreach($opCodes as $group) {
  68. $blocks = array();
  69. $lastTag = null;
  70. $lastBlock = 0;
  71. foreach($group as $code) {
  72. list($tag, $i1, $i2, $j1, $j2) = $code;
  73. if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) {
  74. for($i = 0; $i < ($i2 - $i1); ++$i) {
  75. $fromLine = $a[$i1 + $i];
  76. $toLine = $b[$j1 + $i];
  77. list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
  78. if($start != 0 || $end != 0) {
  79. $last = $end + strlen($fromLine);
  80. $fromLine = substr_replace($fromLine, "\0", $start, 0);
  81. $fromLine = substr_replace($fromLine, "\1", $last + 1, 0);
  82. $last = $end + strlen($toLine);
  83. $toLine = substr_replace($toLine, "\0", $start, 0);
  84. $toLine = substr_replace($toLine, "\1", $last + 1, 0);
  85. $a[$i1 + $i] = $fromLine;
  86. $b[$j1 + $i] = $toLine;
  87. }
  88. }
  89. }
  90. if($tag != $lastTag) {
  91. $blocks[] = array(
  92. 'tag' => $tag,
  93. 'base' => array(
  94. 'offset' => $i1,
  95. 'lines' => array()
  96. ),
  97. 'changed' => array(
  98. 'offset' => $j1,
  99. 'lines' => array()
  100. )
  101. );
  102. $lastBlock = count($blocks)-1;
  103. }
  104. $lastTag = $tag;
  105. if($tag == 'equal') {
  106. $lines = array_slice($a, $i1, ($i2 - $i1));
  107. $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
  108. $lines = array_slice($b, $j1, ($j2 - $j1));
  109. $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines);
  110. }
  111. else {
  112. if($tag == 'replace' || $tag == 'delete') {
  113. $lines = array_slice($a, $i1, ($i2 - $i1));
  114. $lines = $this->formatLines($lines);
  115. $lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
  116. $blocks[$lastBlock]['base']['lines'] += $lines;
  117. }
  118. if($tag == 'replace' || $tag == 'insert') {
  119. $lines = array_slice($b, $j1, ($j2 - $j1));
  120. $lines = $this->formatLines($lines);
  121. $lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
  122. $blocks[$lastBlock]['changed']['lines'] += $lines;
  123. }
  124. }
  125. }
  126. $changes[] = $blocks;
  127. }
  128. return $changes;
  129. }
  130. /**
  131. * Given two strings, determine where the changes in the two strings
  132. * begin, and where the changes in the two strings end.
  133. *
  134. * @param string $fromLine The first string.
  135. * @param string $toLine The second string.
  136. * @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
  137. */
  138. private function getChangeExtent($fromLine, $toLine)
  139. {
  140. $start = 0;
  141. $limit = min(strlen($fromLine), strlen($toLine));
  142. while($start < $limit && $fromLine{$start} == $toLine{$start}) {
  143. ++$start;
  144. }
  145. $end = -1;
  146. $limit = $limit - $start;
  147. while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) {
  148. --$end;
  149. }
  150. return array(
  151. $start,
  152. $end + 1
  153. );
  154. }
  155. /**
  156. * Format a series of lines suitable for output in a HTML rendered diff.
  157. * This involves replacing tab characters with spaces, making the HTML safe
  158. * for output, ensuring that double spaces are replaced with &nbsp; etc.
  159. *
  160. * @param array $lines Array of lines to format.
  161. * @return array Array of the formatted lines.
  162. */
  163. private function formatLines($lines)
  164. {
  165. $lines = array_map(array($this, 'ExpandTabs'), $lines);
  166. $lines = array_map(array($this, 'HtmlSafe'), $lines);
  167. foreach($lines as &$line) {
  168. $line = preg_replace_callback('# ( +)|^ #', __CLASS__."::fixSpaces", $line);
  169. }
  170. return $lines;
  171. }
  172. /**
  173. * Replace a string containing spaces with a HTML representation using &nbsp;.
  174. *
  175. * @param string $matches Regex matches array.
  176. * @return string The HTML representation of the string.
  177. */
  178. public static function fixSpaces($matches)
  179. {
  180. $spaces = isset($matches[1]) ? $matches[1] : '';
  181. $count = strlen($spaces);
  182. if($count == 0) {
  183. return '';
  184. }
  185. $div = floor($count / 2);
  186. $mod = $count % 2;
  187. return str_repeat('&nbsp; ', $div).str_repeat('&nbsp;', $mod);
  188. }
  189. /**
  190. * Replace tabs in a single line with a number of spaces as defined by the tabSize option.
  191. *
  192. * @param string $line The containing tabs to convert.
  193. * @return string The line with the tabs converted to spaces.
  194. */
  195. private function expandTabs($line)
  196. {
  197. return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
  198. }
  199. /**
  200. * Make a string containing HTML safe for output on a page.
  201. *
  202. * @param string $string The string.
  203. * @return string The string with the HTML characters replaced by entities.
  204. */
  205. private function htmlSafe($string)
  206. {
  207. return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
  208. }
  209. }