1
+ − 1
<?php
+ − 2
+ − 3
/**
+ − 4
* XHTML diff renderer.
+ − 5
*
+ − 6
* This class renders diffs in XHTML format.
+ − 7
*
+ − 8
* $Horde: framework/Text_Diff/Diff/Renderer/inline.php,v 1.16 2006/01/08 00:06:57 jan Exp $
+ − 9
*
+ − 10
* @author Ciprian Popovici
+ − 11
* @author Dan Fuhry
+ − 12
* @package Text_Diff
+ − 13
*/
+ − 14
class Text_Diff_Renderer_xhtml extends Text_Diff_Renderer {
+ − 15
+ − 16
/**
+ − 17
* Number of leading context "lines" to preserve.
+ − 18
*/
+ − 19
var $_leading_context_lines = 5;
+ − 20
+ − 21
/**
+ − 22
* Number of trailing context "lines" to preserve.
+ − 23
*/
+ − 24
var $_trailing_context_lines = 3;
+ − 25
+ − 26
/**
+ − 27
* Prefix for inserted text.
+ − 28
*/
+ − 29
var $_ins_prefix = "<!-- Start added text -->\n<tr><td style='width: 0px;'>+</td><td class=\"diff-added\" style='width: 100%;'>";
+ − 30
+ − 31
/**
+ − 32
* Suffix for inserted text.
+ − 33
*/
+ − 34
var $_ins_suffix = "</td></tr>\n<!-- End added text -->\n\n";
+ − 35
+ − 36
/**
+ − 37
* Prefix for deleted text.
+ − 38
*/
+ − 39
var $_del_prefix = "<!-- Start deleted text -->\n<tr><td style='width: 0px;'>-</td><td class=\"diff-deleted\" style='width: 100%;'>";
+ − 40
+ − 41
/**
+ − 42
* Suffix for deleted text.
+ − 43
*/
+ − 44
var $_del_suffix = "</td></tr>\n<!-- End deleted text -->\n\n";
+ − 45
+ − 46
/**
+ − 47
* Header for each change block.
+ − 48
*/
+ − 49
var $_block_header = '';
+ − 50
+ − 51
/**
+ − 52
* What are we currently splitting on? Used to recurse to show word-level
+ − 53
* changes.
+ − 54
*/
+ − 55
var $_split_level = 'lines';
+ − 56
+ − 57
function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
+ − 58
{
+ − 59
return "<!-- Start block -->\n<tr><td colspan='2' class='diff-block'>Line $xbeg: {$this->_block_header}</td></tr>";
+ − 60
}
+ − 61
+ − 62
function _startBlock($header)
+ − 63
{
+ − 64
return $header;
+ − 65
}
+ − 66
+ − 67
function _lines($lines, $prefix = ' ', $encode = true)
+ − 68
{
+ − 69
if ($encode) {
+ − 70
array_walk($lines, array(&$this, '_encode'));
+ − 71
}
+ − 72
+ − 73
if ($this->_split_level == 'words') {
+ − 74
return implode('', $lines);
+ − 75
} else {
+ − 76
return implode("<br />", $lines) . "\n";
+ − 77
}
+ − 78
}
+ − 79
+ − 80
function _added($lines)
+ − 81
{
+ − 82
array_walk($lines, array(&$this, '_encode'));
+ − 83
$lines[0] = $this->_ins_prefix . $lines[0];
+ − 84
$lines[count($lines) - 1] .= $this->_ins_suffix;
+ − 85
return $this->_lines($lines, ' ', false);
+ − 86
}
+ − 87
+ − 88
function _deleted($lines, $words = false)
+ − 89
{
+ − 90
array_walk($lines, array(&$this, '_encode'));
+ − 91
$lines[0] = $this->_del_prefix . $lines[0];
+ − 92
$lines[count($lines) - 1] .= $this->_del_suffix;
+ − 93
return $this->_lines($lines, ' ', false);
+ − 94
}
+ − 95
+ − 96
function _context($lines)
+ − 97
{
+ − 98
return "<!-- Start context -->\n<tr><td></td><td class=\"diff-context\">".$this->_lines($lines).'</td></tr>'."\n<!-- End context -->\n\n";
+ − 99
}
+ − 100
+ − 101
function _changed($orig, $final)
+ − 102
{
+ − 103
/* If we've already split on words, don't try to do so again - just display. */
+ − 104
if ($this->_split_level == 'words') {
+ − 105
$prefix = '';
+ − 106
while ($orig[0] !== false && $final[0] !== false &&
+ − 107
substr($orig[0], 0, 1) == ' ' &&
+ − 108
substr($final[0], 0, 1) == ' ') {
+ − 109
$prefix .= substr($orig[0], 0, 1);
+ − 110
$orig[0] = substr($orig[0], 1);
+ − 111
$final[0] = substr($final[0], 1);
+ − 112
}
+ − 113
$ret = $prefix . $this->_deleted($orig) . $this->_added($final) . "\n";
+ − 114
//echo 'DEBUG:<pre>'.htmlspecialchars($ret).'</pre>';
+ − 115
return $ret;
+ − 116
}
+ − 117
+ − 118
$text1 = implode("\n", $orig);
+ − 119
$text2 = implode("\n", $final);
+ − 120
+ − 121
/* Non-printing newline marker. */
+ − 122
$nl = "\0";
+ − 123
+ − 124
/* We want to split on word boundaries, but we need to
+ − 125
* preserve whitespace as well. Therefore we split on words,
+ − 126
* but include all blocks of whitespace in the wordlist. */
+ − 127
$diff = &new Text_Diff($this->_splitOnWords($text1, $nl),
+ − 128
$this->_splitOnWords($text2, $nl));
+ − 129
+ − 130
/* Get the diff in inline format. */
+ − 131
$renderer = &new Text_Diff_Renderer_inline(array_merge($this->getParams(),
+ − 132
array('split_level' => 'words')));
+ − 133
+ − 134
/* Run the diff and get the output. */
+ − 135
$ret = str_replace($nl, "<br />", $renderer->render($diff));
+ − 136
//echo 'DEBUG:<pre>'.htmlspecialchars($ret).'</pre>';
+ − 137
return $ret . "\n";
+ − 138
}
+ − 139
+ − 140
function _splitOnWords($string, $newlineEscape = "<br />")
+ − 141
{
+ − 142
$words = array();
+ − 143
$length = strlen($string);
+ − 144
$pos = 0;
+ − 145
+ − 146
while ($pos < $length) {
+ − 147
// Eat a word with any preceding whitespace.
+ − 148
$spaces = strspn(substr($string, $pos), " \n");
+ − 149
$nextpos = strcspn(substr($string, $pos + $spaces), " \n");
+ − 150
$words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos));
+ − 151
$pos += $spaces + $nextpos;
+ − 152
}
+ − 153
+ − 154
return $words;
+ − 155
}
+ − 156
+ − 157
function _encode(&$string)
+ − 158
{
+ − 159
$string = htmlspecialchars($string);
+ − 160
}
+ − 161
+ − 162
/**
+ − 163
* Renders a diff.
+ − 164
*
+ − 165
* @param Text_Diff $diff A Text_Diff object.
+ − 166
*
+ − 167
* @return string The formatted output.
+ − 168
*/
+ − 169
+ − 170
function render($diff)
+ − 171
{
+ − 172
$xi = $yi = 1;
+ − 173
$block = false;
+ − 174
$context = array();
+ − 175
+ − 176
$nlead = $this->_leading_context_lines;
+ − 177
$ntrail = $this->_trailing_context_lines;
+ − 178
+ − 179
$output = $this->_startDiff();
+ − 180
+ − 181
$diffs = $diff->getDiff();
+ − 182
foreach ($diffs as $i => $edit) {
+ − 183
if (is_a($edit, 'Text_Diff_Op_copy')) {
+ − 184
if (is_array($block)) {
+ − 185
$keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;
+ − 186
if (count($edit->orig) <= $keep) {
+ − 187
$block[] = $edit;
+ − 188
} else {
+ − 189
if ($ntrail) {
+ − 190
$context = array_slice($edit->orig, 0, $ntrail);
+ − 191
$block[] = &new Text_Diff_Op_copy($context);
+ − 192
}
+ − 193
$bk = $this->_block($x0, $ntrail + $xi - $x0,
+ − 194
$y0, $ntrail + $yi - $y0,
+ − 195
$block);
+ − 196
$output .= $bk;
+ − 197
$block = false;
+ − 198
}
+ − 199
}
+ − 200
$context = $edit->orig;
+ − 201
} else {
+ − 202
if (!is_array($block)) {
+ − 203
$context = array_slice($context, count($context) - $nlead);
+ − 204
$x0 = $xi - count($context);
+ − 205
$y0 = $yi - count($context);
+ − 206
$block = array();
+ − 207
if ($context) {
+ − 208
$block[] = &new Text_Diff_Op_copy($context);
+ − 209
}
+ − 210
}
+ − 211
$block[] = $edit;
+ − 212
}
+ − 213
+ − 214
if ($edit->orig) {
+ − 215
$xi += count($edit->orig);
+ − 216
}
+ − 217
if ($edit->final) {
+ − 218
$yi += count($edit->final);
+ − 219
}
+ − 220
}
+ − 221
+ − 222
if (is_array($block)) {
+ − 223
$bk = $this->_block($x0, $xi - $x0,
+ − 224
$y0, $yi - $y0,
+ − 225
$block);
+ − 226
$output .= $bk;
+ − 227
}
+ − 228
+ − 229
$final = $output . $this->_endDiff();
+ − 230
if ($final == '') $final = '<tr><td class="diff-block">No differences.</td></tr>';
+ − 231
//$final = preg_replace('#('.preg_quote($this->_ins_suffix).'|'.preg_quote($this->_del_suffix).')(.+?)('.preg_quote($this->_ins_prefix).'|'.preg_quote($this->_ins_suffix).')#', '\\1<tr><td></td><td class="diff-context>\\2</td></tr>\\3', $final);
+ − 232
return '<table class="diff">'.$final.'</table>'."\n\n";
+ − 233
}
+ − 234
+ − 235
function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
+ − 236
{
+ − 237
$output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));
+ − 238
+ − 239
foreach ($edits as $edit) {
+ − 240
switch (strtolower(get_class($edit))) {
+ − 241
case 'text_diff_op_copy':
+ − 242
$output .= $this->_context($edit->orig);
+ − 243
break;
+ − 244
+ − 245
case 'text_diff_op_add':
+ − 246
$output .= $this->_added($edit->final);
+ − 247
break;
+ − 248
+ − 249
case 'text_diff_op_delete':
+ − 250
$output .= $this->_deleted($edit->orig);
+ − 251
break;
+ − 252
+ − 253
case 'text_diff_op_change':
+ − 254
$output .= $this->_changed($edit->orig, $edit->final);
+ − 255
break;
+ − 256
}
+ − 257
}
+ − 258
+ − 259
return $output . $this->_endBlock();
+ − 260
}
+ − 261
+ − 262
}