1<?php 2/** 3 * A class to render Diffs in different formats. 4 * 5 * This class renders the diff in classic diff format. It is intended that 6 * this class be customized via inheritance, to obtain fancier outputs. 7 * 8 * Copyright 2004-2017 Horde LLC (http://www.horde.org/) 9 * 10 * See the enclosed file COPYING for license information (LGPL). If you did 11 * not receive this file, see http://www.horde.org/licenses/lgpl21. 12 * 13 * @package Text_Diff 14 */ 15class Horde_Text_Diff_Renderer 16{ 17 /** 18 * Number of leading context "lines" to preserve. 19 * 20 * This should be left at zero for this class, but subclasses may want to 21 * set this to other values. 22 */ 23 protected $_leading_context_lines = 0; 24 25 /** 26 * Number of trailing context "lines" to preserve. 27 * 28 * This should be left at zero for this class, but subclasses may want to 29 * set this to other values. 30 */ 31 protected $_trailing_context_lines = 0; 32 33 /** 34 * Constructor. 35 */ 36 public function __construct($params = array()) 37 { 38 foreach ($params as $param => $value) { 39 $v = '_' . $param; 40 if (isset($this->$v)) { 41 $this->$v = $value; 42 } 43 } 44 } 45 46 /** 47 * Get any renderer parameters. 48 * 49 * @return array All parameters of this renderer object. 50 */ 51 public function getParams() 52 { 53 $params = array(); 54 foreach (get_object_vars($this) as $k => $v) { 55 if ($k[0] == '_') { 56 $params[substr($k, 1)] = $v; 57 } 58 } 59 60 return $params; 61 } 62 63 /** 64 * Renders a diff. 65 * 66 * @param Horde_Text_Diff $diff A Horde_Text_Diff object. 67 * 68 * @return string The formatted output. 69 */ 70 public function render($diff) 71 { 72 $xi = $yi = 1; 73 $block = false; 74 $context = array(); 75 76 $nlead = $this->_leading_context_lines; 77 $ntrail = $this->_trailing_context_lines; 78 79 $output = $this->_startDiff(); 80 81 $diffs = $diff->getDiff(); 82 foreach ($diffs as $i => $edit) { 83 /* If these are unchanged (copied) lines, and we want to keep 84 * leading or trailing context lines, extract them from the copy 85 * block. */ 86 if ($edit instanceof Horde_Text_Diff_Op_Copy) { 87 /* Do we have any diff blocks yet? */ 88 if (is_array($block)) { 89 /* How many lines to keep as context from the copy 90 * block. */ 91 $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; 92 if (count($edit->orig) <= $keep) { 93 /* We have less lines in the block than we want for 94 * context => keep the whole block. */ 95 $block[] = $edit; 96 } else { 97 if ($ntrail) { 98 /* Create a new block with as many lines as we need 99 * for the trailing context. */ 100 $context = array_slice($edit->orig, 0, $ntrail); 101 $block[] = new Horde_Text_Diff_Op_Copy($context); 102 } 103 /* @todo */ 104 $output .= $this->_block($x0, $ntrail + $xi - $x0, 105 $y0, $ntrail + $yi - $y0, 106 $block); 107 $block = false; 108 } 109 } 110 /* Keep the copy block as the context for the next block. */ 111 $context = $edit->orig; 112 } else { 113 /* Don't we have any diff blocks yet? */ 114 if (!is_array($block)) { 115 /* Extract context lines from the preceding copy block. */ 116 $context = array_slice($context, count($context) - $nlead); 117 $x0 = $xi - count($context); 118 $y0 = $yi - count($context); 119 $block = array(); 120 if ($context) { 121 $block[] = new Horde_Text_Diff_Op_Copy($context); 122 } 123 } 124 $block[] = $edit; 125 } 126 127 if ($edit->orig) { 128 $xi += count($edit->orig); 129 } 130 if ($edit->final) { 131 $yi += count($edit->final); 132 } 133 } 134 135 if (is_array($block)) { 136 $output .= $this->_block($x0, $xi - $x0, 137 $y0, $yi - $y0, 138 $block); 139 } 140 141 return $output . $this->_endDiff(); 142 } 143 144 protected function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) 145 { 146 $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen)); 147 148 foreach ($edits as $edit) { 149 switch (get_class($edit)) { 150 case 'Horde_Text_Diff_Op_Copy': 151 $output .= $this->_context($edit->orig); 152 break; 153 154 case 'Horde_Text_Diff_Op_Add': 155 $output .= $this->_added($edit->final); 156 break; 157 158 case 'Horde_Text_Diff_Op_Delete': 159 $output .= $this->_deleted($edit->orig); 160 break; 161 162 case 'Horde_Text_Diff_Op_Change': 163 $output .= $this->_changed($edit->orig, $edit->final); 164 break; 165 } 166 } 167 168 return $output . $this->_endBlock(); 169 } 170 171 protected function _startDiff() 172 { 173 return ''; 174 } 175 176 protected function _endDiff() 177 { 178 return ''; 179 } 180 181 protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) 182 { 183 if ($xlen > 1) { 184 $xbeg .= ',' . ($xbeg + $xlen - 1); 185 } 186 if ($ylen > 1) { 187 $ybeg .= ',' . ($ybeg + $ylen - 1); 188 } 189 190 // this matches the GNU Diff behaviour 191 if ($xlen && !$ylen) { 192 $ybeg--; 193 } elseif (!$xlen) { 194 $xbeg--; 195 } 196 197 return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; 198 } 199 200 protected function _startBlock($header) 201 { 202 return $header . "\n"; 203 } 204 205 protected function _endBlock() 206 { 207 return ''; 208 } 209 210 protected function _lines($lines, $prefix = ' ') 211 { 212 return $prefix . implode("\n$prefix", $lines) . "\n"; 213 } 214 215 protected function _context($lines) 216 { 217 return $this->_lines($lines, ' '); 218 } 219 220 protected function _added($lines) 221 { 222 return $this->_lines($lines, '> '); 223 } 224 225 protected function _deleted($lines) 226 { 227 return $this->_lines($lines, '< '); 228 } 229 230 protected function _changed($orig, $final) 231 { 232 return $this->_deleted($orig) . "---\n" . $this->_added($final); 233 } 234} 235