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