1 | 5 | simandl | <?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.10 2008/01/04 10:37:27 jan Exp $ |
9 | | | * |
10 | | | * Copyright 2004-2008 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 | | | */ |
17 | | | class 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 Text_Diff_Renderer($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 | | | } |