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.

Array.php 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. $realEnd = mb_strlen($fromLine) + $end;
  80. $fromLine = mb_substr($fromLine, 0, $start)
  81. . "\0"
  82. . mb_substr($fromLine, $start, $realEnd - $start)
  83. . "\1"
  84. . mb_substr($fromLine, $realEnd);
  85. $realEnd = mb_strlen($toLine) + $end;
  86. $toLine = mb_substr($toLine, 0, $start)
  87. . "\0"
  88. . mb_substr($toLine, $start, $realEnd - $start)
  89. . "\1"
  90. . mb_substr($toLine, $realEnd);
  91. $a[$i1 + $i] = $fromLine;
  92. $b[$j1 + $i] = $toLine;
  93. }
  94. }
  95. }
  96. if($tag != $lastTag) {
  97. $blocks[] = array(
  98. 'tag' => $tag,
  99. 'base' => array(
  100. 'offset' => $i1,
  101. 'lines' => array()
  102. ),
  103. 'changed' => array(
  104. 'offset' => $j1,
  105. 'lines' => array()
  106. )
  107. );
  108. $lastBlock = count($blocks)-1;
  109. }
  110. $lastTag = $tag;
  111. if($tag == 'equal') {
  112. $lines = array_slice($a, $i1, ($i2 - $i1));
  113. $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
  114. $lines = array_slice($b, $j1, ($j2 - $j1));
  115. $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines);
  116. }
  117. else {
  118. if($tag == 'replace' || $tag == 'delete') {
  119. $lines = array_slice($a, $i1, ($i2 - $i1));
  120. $lines = $this->formatLines($lines);
  121. $lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
  122. $blocks[$lastBlock]['base']['lines'] += $lines;
  123. }
  124. if($tag == 'replace' || $tag == 'insert') {
  125. $lines = array_slice($b, $j1, ($j2 - $j1));
  126. $lines = $this->formatLines($lines);
  127. $lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
  128. $blocks[$lastBlock]['changed']['lines'] += $lines;
  129. }
  130. }
  131. }
  132. $changes[] = $blocks;
  133. }
  134. return $changes;
  135. }
  136. /**
  137. * Given two strings, determine where the changes in the two strings
  138. * begin, and where the changes in the two strings end.
  139. *
  140. * @param string $fromLine The first string.
  141. * @param string $toLine The second string.
  142. * @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
  143. */
  144. private function getChangeExtent($fromLine, $toLine)
  145. {
  146. $start = 0;
  147. $limit = min(mb_strlen($fromLine), mb_strlen($toLine));
  148. while($start < $limit && mb_substr($fromLine, $start, 1) == mb_substr($toLine, $start, 1)) {
  149. ++$start;
  150. }
  151. $end = -1;
  152. $limit = $limit - $start;
  153. while(-$end <= $limit && mb_substr($fromLine, $end, 1) == mb_substr($toLine, $end, 1)) {
  154. --$end;
  155. }
  156. return array(
  157. $start,
  158. $end + 1
  159. );
  160. }
  161. /**
  162. * Format a series of lines suitable for output in a HTML rendered diff.
  163. * This involves replacing tab characters with spaces, making the HTML safe
  164. * for output, ensuring that double spaces are replaced with &nbsp; etc.
  165. *
  166. * @param array $lines Array of lines to format.
  167. * @return array Array of the formatted lines.
  168. */
  169. private function formatLines($lines)
  170. {
  171. if ($this->options['tabSize'] !== false) {
  172. $lines = array_map(array($this, 'ExpandTabs'), $lines);
  173. }
  174. $lines = array_map(array($this, 'HtmlSafe'), $lines);
  175. foreach($lines as &$line) {
  176. $line = preg_replace_callback('# ( +)|^ #', __CLASS__."::fixSpaces", $line);
  177. }
  178. return $lines;
  179. }
  180. /**
  181. * Replace a string containing spaces with a HTML representation using &nbsp;.
  182. *
  183. * @param string $matches Regex matches array.
  184. * @return string The HTML representation of the string.
  185. */
  186. public static function fixSpaces($matches)
  187. {
  188. $spaces = isset($matches[1]) ? $matches[1] : '';
  189. $count = strlen($spaces);
  190. if($count == 0) {
  191. return '';
  192. }
  193. $div = floor($count / 2);
  194. $mod = $count % 2;
  195. return str_repeat('&nbsp; ', $div).str_repeat('&nbsp;', $mod);
  196. }
  197. /**
  198. * Replace tabs in a single line with a number of spaces as defined by the tabSize option.
  199. *
  200. * @param string $line The containing tabs to convert.
  201. * @return string The line with the tabs converted to spaces.
  202. */
  203. private function expandTabs($line)
  204. {
  205. return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
  206. }
  207. /**
  208. * Make a string containing HTML safe for output on a page.
  209. *
  210. * @param string $string The string.
  211. * @return string The string with the HTML characters replaced by entities.
  212. */
  213. private function htmlSafe($string)
  214. {
  215. return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
  216. }
  217. }