1<?php 2/** 3 * "Inline" diff renderer. 4 * 5 * This class renders diffs in the Wiki-style "inline" format. 6 * 7 * Copyright 2004-2017 Horde LLC (http://www.horde.org/) 8 * 9 * See the enclosed file COPYING for license information (LGPL). If you did 10 * not receive this file, see http://www.horde.org/licenses/lgpl21. 11 * 12 * @author Ciprian Popovici 13 * @package Text_Diff 14 */ 15class Horde_Text_Diff_Renderer_Inline extends Horde_Text_Diff_Renderer 16{ 17 /** 18 * Number of leading context "lines" to preserve. 19 * 20 * @var integer 21 */ 22 protected $_leading_context_lines = 10000; 23 24 /** 25 * Number of trailing context "lines" to preserve. 26 * 27 * @var integer 28 */ 29 protected $_trailing_context_lines = 10000; 30 31 /** 32 * Prefix for inserted text. 33 * 34 * @var string 35 */ 36 protected $_ins_prefix = '<ins>'; 37 38 /** 39 * Suffix for inserted text. 40 * 41 * @var string 42 */ 43 protected $_ins_suffix = '</ins>'; 44 45 /** 46 * Prefix for deleted text. 47 * 48 * @var string 49 */ 50 protected $_del_prefix = '<del>'; 51 52 /** 53 * Suffix for deleted text. 54 * 55 * @var string 56 */ 57 protected $_del_suffix = '</del>'; 58 59 /** 60 * Header for each change block. 61 * 62 * @var string 63 */ 64 protected $_block_header = ''; 65 66 /** 67 * Whether to split down to character-level. 68 * 69 * @var boolean 70 */ 71 protected $_split_characters = false; 72 73 /** 74 * What are we currently splitting on? Used to recurse to show word-level 75 * or character-level changes. 76 * 77 * @var string 78 */ 79 protected $_split_level = 'lines'; 80 81 protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) 82 { 83 return $this->_block_header; 84 } 85 86 protected function _startBlock($header) 87 { 88 return $header; 89 } 90 91 protected function _lines($lines, $prefix = ' ', $encode = true) 92 { 93 if ($encode) { 94 array_walk($lines, array(&$this, '_encode')); 95 } 96 97 if ($this->_split_level == 'lines') { 98 return implode("\n", $lines) . "\n"; 99 } else { 100 return implode('', $lines); 101 } 102 } 103 104 protected function _added($lines) 105 { 106 array_walk($lines, array(&$this, '_encode')); 107 $lines[0] = $this->_ins_prefix . $lines[0]; 108 $lines[count($lines) - 1] .= $this->_ins_suffix; 109 return $this->_lines($lines, ' ', false); 110 } 111 112 protected function _deleted($lines, $words = false) 113 { 114 array_walk($lines, array(&$this, '_encode')); 115 $lines[0] = $this->_del_prefix . $lines[0]; 116 $lines[count($lines) - 1] .= $this->_del_suffix; 117 return $this->_lines($lines, ' ', false); 118 } 119 120 protected function _changed($orig, $final) 121 { 122 /* If we've already split on characters, just display. */ 123 if ($this->_split_level == 'characters') { 124 return $this->_deleted($orig) 125 . $this->_added($final); 126 } 127 128 /* If we've already split on words, just display. */ 129 if ($this->_split_level == 'words') { 130 $prefix = ''; 131 while ($orig[0] !== false && $final[0] !== false && 132 substr($orig[0], 0, 1) == ' ' && 133 substr($final[0], 0, 1) == ' ') { 134 $prefix .= substr($orig[0], 0, 1); 135 $orig[0] = substr($orig[0], 1); 136 $final[0] = substr($final[0], 1); 137 } 138 return $prefix . $this->_deleted($orig) . $this->_added($final); 139 } 140 141 $text1 = implode("\n", $orig); 142 $text2 = implode("\n", $final); 143 144 /* Non-printing newline marker. */ 145 $nl = "\0"; 146 147 if ($this->_split_characters) { 148 $diff = new Horde_Text_Diff('native', 149 array(preg_split('//u', str_replace("\n", $nl, $text1)), 150 preg_split('//u', str_replace("\n", $nl, $text2)))); 151 } else { 152 /* We want to split on word boundaries, but we need to preserve 153 * whitespace as well. Therefore we split on words, but include 154 * all blocks of whitespace in the wordlist. */ 155 $diff = new Horde_Text_Diff('native', 156 array($this->_splitOnWords($text1, $nl), 157 $this->_splitOnWords($text2, $nl))); 158 } 159 160 /* Get the diff in inline format. */ 161 $renderer = new Horde_Text_Diff_Renderer_inline 162 (array_merge($this->getParams(), 163 array('split_level' => $this->_split_characters ? 'characters' : 'words'))); 164 165 /* Run the diff and get the output. */ 166 return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; 167 } 168 169 protected function _splitOnWords($string, $newlineEscape = "\n") 170 { 171 // Ignore \0; otherwise the while loop will never finish. 172 $string = str_replace("\0", '', $string); 173 174 $words = array(); 175 $length = strlen($string); 176 $pos = 0; 177 178 while ($pos < $length) { 179 // Eat a word with any preceding whitespace. 180 $spaces = strspn(substr($string, $pos), " \n"); 181 $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); 182 $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); 183 $pos += $spaces + $nextpos; 184 } 185 186 return $words; 187 } 188 189 protected function _encode(&$string) 190 { 191 $string = htmlspecialchars($string); 192 } 193} 194