diff options
author | Meik Sievertsen <acydburn@phpbb.com> | 2006-08-22 21:29:45 +0000 |
---|---|---|
committer | Meik Sievertsen <acydburn@phpbb.com> | 2006-08-22 21:29:45 +0000 |
commit | cb23c1f04482278a2478bdd9700503b1f1fcddf4 (patch) | |
tree | 872c36cb155f70666b545f46b38b1d6e19d80771 /phpBB/includes/diff/renderer.php | |
parent | 1d42d1b9817050974c8bc8b91bc34a6c3cfbfef8 (diff) | |
download | forums-cb23c1f04482278a2478bdd9700503b1f1fcddf4.tar forums-cb23c1f04482278a2478bdd9700503b1f1fcddf4.tar.gz forums-cb23c1f04482278a2478bdd9700503b1f1fcddf4.tar.bz2 forums-cb23c1f04482278a2478bdd9700503b1f1fcddf4.tar.xz forums-cb23c1f04482278a2478bdd9700503b1f1fcddf4.zip |
Thanks to the GPL we are able to use the pear package text_diff - now splitted into the diff classes, the renderer and the engine
git-svn-id: file:///svn/phpbb/trunk@6313 89ea8834-ac86-4346-8a33-228a782c2dd0
Diffstat (limited to 'phpBB/includes/diff/renderer.php')
-rw-r--r-- | phpBB/includes/diff/renderer.php | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/phpBB/includes/diff/renderer.php b/phpBB/includes/diff/renderer.php new file mode 100644 index 0000000000..408addb858 --- /dev/null +++ b/phpBB/includes/diff/renderer.php @@ -0,0 +1,832 @@ +<?php +/** +* +* @package phpBB3 +* @version $Id$ +* @copyright (c) 2006 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Code from pear.php.net, Text_Diff-0.2.1 (beta) package +* http://pear.php.net/package/Text_Diff/ +* +* Modified by Acyd Burn to meet our coding standards +* and being able to integrate into phpBB +*/ + +/** +* A class to render Diffs in different formats. +* +* This class renders the diff in classic diff format. It is intended that +* this class be customized via inheritance, to obtain fancier outputs. +* +* @package phpBB3 +*/ +class diff_renderer +{ + /** + * Number of leading context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_leading_context_lines = 0; + + /** + * Number of trailing context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_trailing_context_lines = 0; + + /** + * Constructor. + */ + function diff_renderer($params = array()) + { + foreach ($params as $param => $value) + { + $v = '_' . $param; + if (isset($this->$v)) + { + $this->$v = $value; + } + } + } + + /** + * Get any renderer parameters. + * + * @return array All parameters of this renderer object. + */ + function get_params() + { + $params = array(); + foreach (get_object_vars($this) as $k => $v) + { + if ($k[0] == '_') + { + $params[substr($k, 1)] = $v; + } + } + + return $params; + } + + /** + * Renders a diff. + * + * @param diff $diff A diff object. + * + * @return string The formatted output. + */ + function render(&$diff) + { + $xi = $yi = 1; + $block = false; + $context = array(); + + // Create a new diff object if it is a 3-way diff + if (is_a($diff, 'diff3')) + { + $diff3 = &$diff; + $diff = &new diff($diff3->get_original(), $diff3->merged_output()); + unset($diff3); + } + + $nlead = $this->_leading_context_lines; + $ntrail = $this->_trailing_context_lines; + + $output = $this->_start_diff(); + $diffs = $diff->get_diff(); + + foreach ($diffs as $i => $edit) + { + if (is_a($edit, 'diff_op_copy')) + { + if (is_array($block)) + { + $keep = ($i == sizeof($diffs) - 1) ? $ntrail : $nlead + $ntrail; + if (sizeof($edit->orig) <= $keep) + { + $block[] = $edit; + } + else + { + if ($ntrail) + { + $context = array_slice($edit->orig, 0, $ntrail); + $block[] = &new diff_op_copy($context); + } + + $output .= $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block); + $block = false; + } + } + $context = $edit->orig; + } + else + { + if (!is_array($block)) + { + $context = array_slice($context, sizeof($context) - $nlead); + $x0 = $xi - sizeof($context); + $y0 = $yi - sizeof($context); + $block = array(); + + if ($context) + { + $block[] = &new diff_op_copy($context); + } + } + $block[] = $edit; + } + + $xi += ($edit->orig) ? sizeof($edit->orig) : 0; + $yi += ($edit->final) ? sizeof($edit->final) : 0; + } + + if (is_array($block)) + { + $output .= $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block); + } + + return $output . $this->_end_diff(); + } + + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) + { + $output = $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); + + foreach ($edits as $edit) + { + switch (get_class($edit)) + { + case 'diff_op_copy': + $output .= $this->_context($edit->orig); + break; + + case 'diff_op_add': + $output .= $this->_added($edit->final); + break; + + case 'diff_op_delete': + $output .= $this->_deleted($edit->orig); + break; + + case 'diff_op_change': + $output .= $this->_changed($edit->orig, $edit->final); + break; + } + } + + return $output . $this->_end_block(); + } + + function _start_diff() + { + return ''; + } + + function _end_diff() + { + return ''; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen > 1) + { + $xbeg .= ',' . ($xbeg + $xlen - 1); + } + + if ($ylen > 1) + { + $ybeg .= ',' . ($ybeg + $ylen - 1); + } + + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; + } + + function _start_block($header) + { + return $header . "\n"; + } + + function _end_block() + { + return ''; + } + + function _lines($lines, $prefix = ' ') + { + return $prefix . implode("\n$prefix", $lines) . "\n"; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '> '); + } + + function _deleted($lines) + { + return $this->_lines($lines, '< '); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . "---\n" . $this->_added($final); + } + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return $this->render($diff); + } +} + +/** +* Renders a unified diff +* @package phpBB3 +*/ +class diff_renderer_unified extends diff_renderer +{ + var $_leading_context_lines = 4; + var $_trailing_context_lines = 4; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return nl2br($this->render($diff)); + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) + { + $xbeg .= ',' . $xlen; + } + + if ($ylen != 1) + { + $ybeg .= ',' . $ylen; + } + return '<div class="diff"><big class="info">@@ -' . $xbeg . ' +' . $ybeg . ' @@</big></div>'; + } + + function _context($lines) + { + return '<pre class="diff context">' . htmlspecialchars($this->_lines($lines, ' ')) . '</pre>'; + } + + function _added($lines) + { + return '<pre class="diff added">' . htmlspecialchars($this->_lines($lines, '+')) . '</pre>'; + } + + function _deleted($lines) + { + return '<pre class="diff removed">' . htmlspecialchars($this->_lines($lines, '-')) . '</pre>'; + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } + + function _start_diff() + { + $start = '<div class="file">'; + + return $start; + } + + function _end_diff() + { + return '</div>'; + } + + function _end_block() + { + return ''; + } +} + +/** +* "Inline" diff renderer. +* +* This class renders diffs in the Wiki-style "inline" format. +* +* @author Ciprian Popovici +* @package phpBB3 +*/ +class diff_renderer_inline extends diff_renderer +{ + var $_leading_context_lines = 10000; + var $_trailing_context_lines = 10000; + + // Prefix and suffix for inserted text + var $_ins_prefix = '<span class="ins">'; + var $_ins_suffix = '</span>'; + + // Prefix and suffix for deleted text + var $_del_prefix = '<span class="del">'; + var $_del_suffix = '</span>'; + + var $_block_head = ''; + + // What are we currently splitting on? Used to recurse to show word-level + var $_split_level = 'lines'; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return '<pre>' . nl2br($this->render($diff)) . '</pre>'; + } + + function _start_diff() + { + return ''; + } + + function _end_diff() + { + return ''; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + return $this->_block_head; + } + + function _start_block($header) + { + return $header; + } + + function _lines($lines, $prefix = ' ', $encode = true) + { + if ($encode) + { + array_walk($lines, array(&$this, '_encode')); + } + + if ($this->_split_level == 'words') + { + return implode('', $lines); + } + else + { + return implode("\n", $lines) . "\n"; + } + } + + function _added($lines) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_ins_prefix . $lines[0]; + $lines[sizeof($lines) - 1] .= $this->_ins_suffix; + return $this->_lines($lines, ' ', false); + } + + function _deleted($lines, $words = false) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_del_prefix . $lines[0]; + $lines[sizeof($lines) - 1] .= $this->_del_suffix; + return $this->_lines($lines, ' ', false); + } + + function _changed($orig, $final) + { + // If we've already split on words, don't try to do so again - just display. + if ($this->_split_level == 'words') + { + $prefix = ''; + while ($orig[0] !== false && $final[0] !== false && substr($orig[0], 0, 1) == ' ' && substr($final[0], 0, 1) == ' ') + { + $prefix .= substr($orig[0], 0, 1); + $orig[0] = substr($orig[0], 1); + $final[0] = substr($final[0], 1); + } + + return $prefix . $this->_deleted($orig) . $this->_added($final); + } + + $text1 = implode("\n", $orig); + $text2 = implode("\n", $final); + + // Non-printing newline marker. + $nl = "\0"; + + // We want to split on word boundaries, but we need to preserve whitespace as well. + // Therefore we split on words, but include all blocks of whitespace in the wordlist. + $diff = &new diff($this->_split_on_words($text1, $nl), $this->_split_on_words($text2, $nl)); + + // Get the diff in inline format. + $renderer = &new diff_renderer_inline(array_merge($this->get_params(), array('split_level' => 'words'))); + + // Run the diff and get the output. + return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; + } + + function _split_on_words($string, $newline_escape = "\n") + { + // Ignore \0; otherwise the while loop will never finish. + $string = str_replace("\0", '', $string); + + $words = array(); + $length = strlen($string); + $pos = 0; + + $tab_there = true; + while ($pos < $length) + { + // Check for tabs... do not include them + if ($tab_there && substr($string, $pos, 1) === "\t") + { + $words[] = "\t"; + $pos++; + + continue; + } + else + { + $tab_there = false; + } + + // Eat a word with any preceding whitespace. + $spaces = strspn(substr($string, $pos), " \n"); + $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); + $words[] = str_replace("\n", $newline_escape, substr($string, $pos, $spaces + $nextpos)); + $pos += $spaces + $nextpos; + } + + return $words; + } + + function _encode(&$string) + { + $string = htmlspecialchars($string); + } +} + +/** +* "raw" diff renderer. +* This class could be used to output a raw unified patch file +* +* @package phpBB3 +*/ +class diff_renderer_raw extends diff_renderer +{ + var $_leading_context_lines = 4; + var $_trailing_context_lines = 4; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return '<textarea style="height: 400px;" class="full">' . htmlspecialchars($this->render($diff)) . '</textarea>'; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) + { + $xbeg .= ',' . $xlen; + } + + if ($ylen != 1) + { + $ybeg .= ',' . $ylen; + } + return '@@ -' . $xbeg . ' +' . $ybeg . ' @@'; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '+'); + } + + function _deleted($lines) + { + return $this->_lines($lines, '-'); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } +} + +/** +* "chora (Horde)" diff renderer - similar style. +* This renderer class is a modified human_readable function from the Horde Framework. +* +* @package phpBB3 +*/ +class diff_renderer_side_by_side extends diff_renderer +{ + var $_leading_context_lines = 3; + var $_trailing_context_lines = 3; + + var $lines = array(); + + // Hold the left and right columns of lines for change blocks. + var $cols; + var $state; + + var $data = false; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + global $user; + + $output = ''; + $output .= '<table cellspacing="0" class="hrdiff"> +<caption> + <span class="unmodified"> </span> ' . $user->lang['LINE_UNMODIFIED'] . ' + <span class="added"> </span> ' . $user->lang['LINE_ADDED'] . ' + <span class="modified"> </span> ' . $user->lang['LINE_MODIFIED'] . ' + <span class="removed"> </span> ' . $user->lang['LINE_REMOVED'] . ' +</caption> +<tbody> +'; + + $this->render($diff); + + // Is the diff empty? + if (!sizeof($this->lines)) + { + $output .= '<tr><th colspan="2">' . $user->lang['NO_VISIBLE_CHANGES'] . '</th></tr>'; + } + else + { + // Iterate through every header block of changes + foreach ($this->lines as $header) + { + $output .= '<tr><th>Line ' . $header['oldline'] . '</th><th>' . $user->lang['LINE'] . ' ' . $header['newline'] . '</th></tr>'; + + // Each header block consists of a number of changes (add, remove, change). + $current_context = ''; + + foreach ($header['contents'] as $change) + { + if (!empty($current_context) && $change['type'] != 'empty') + { + $line = $current_context; + $current_context = ''; + + $output .= '<tr class="unmodified"><td><pre>' . ((strlen($line)) ? $line : ' ') . '</pre></td> + <td><pre>' . ((strlen($line)) ? $line : ' ') . '</pre></td></tr>'; + } + + switch ($change['type']) + { + case 'add': + $line = ''; + + foreach ($change['lines'] as $_line) + { + $line .= htmlspecialchars($_line) . '<br />'; + } + + $output .= '<tr><td class="added_empty"> </td><td class="added"><pre>' . ((strlen($line)) ? $line : ' ') . '</pre></td></tr>'; + break; + + case 'remove': + $line = ''; + + foreach ($change['lines'] as $_line) + { + $line .= htmlspecialchars($_line) . '<br />'; + } + + $output .= '<tr><td class="removed"><pre>' . ((strlen($line)) ? $line : ' ') . '</pre></td><td class="removed_empty"> </td></tr>'; + break; + + case 'empty': + $current_context .= htmlspecialchars($change['line']) . '<br />'; + break; + + case 'change': + // Pop the old/new stacks one by one, until both are empty. + $oldsize = sizeof($change['old']); + $newsize = sizeof($change['new']); + $left = $right = ''; + + for ($row = 0, $row_max = max($oldsize, $newsize); $row < $row_max; ++$row) + { + $left .= isset($change['old'][$row]) ? htmlspecialchars($change['old'][$row]) : ''; + $left .= '<br />'; + $right .= isset($change['new'][$row]) ? htmlspecialchars($change['new'][$row]) : ''; + $right .= '<br />'; + } + + $output .= '<tr>'; + + if (!empty($left)) + { + $output .= '<td class="modified"><pre>' . $left . '</pre></td>'; + } + else if ($row < $oldsize) + { + $output .= '<td class="modified"> </td>'; + } + else + { + $output .= '<td class="unmodified"> </td>'; + } + + if (!empty($right)) + { + $output .= '<td class="modified"><pre>' . $right . '</pre></td>'; + } + else if ($row < $newsize) + { + $output .= '<td class="modified"> </td>'; + } + else + { + $output .= '<td class="unmodified"> </td>'; + } + + $output .= '</tr>'; + break; + } + } + + if (!empty($current_context)) + { + $line = $current_context; + $current_context = ''; + + $output .= '<tr class="unmodified"><td><pre>' . ((strlen($line)) ? $line : ' ') . '</pre></td>'; + $output .= '<td><pre>' . ((strlen($line)) ? $line : ' ') . '</pre></td></tr>'; + } + } + } + + $output .= '</tbody></table>'; + + return $output; + } + + function _start_diff() + { + $this->lines = array(); + + $this->data = false; + $this->cols = array(array(), array()); + $this->state = 'empty'; + + return ''; + } + + function _end_diff() + { + // Just flush any remaining entries in the columns stack. + switch ($this->state) + { + case 'add': + $this->data['contents'][] = array('type' => 'add', 'lines' => $this->cols[0]); + break; + + case 'remove': + // We have some removal lines pending in our stack, so flush them. + $this->data['contents'][] = array('type' => 'remove', 'lines' => $this->cols[0]); + break; + + case 'change': + // We have both remove and addition lines, so this is a change block. + $this->data['contents'][] = array('type' => 'change', 'old' => $this->cols[0], 'new' => $this->cols[1]); + break; + } + + if ($this->data !== false) + { + $this->lines[] = $this->data; + } + + return ''; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + // Push any previous header information to the return stack. + if ($this->data !== false) + { + $this->lines[] = $this->data; + } + + $this->data = array('type' => 'header', 'oldline' => $xbeg, 'newline' => $ybeg, 'contents' => array()); + $this->state = 'dump'; + } + + function _added($lines) + { + array_walk($lines, array(&$this, '_perform_add')); + } + + function _perform_add($line) + { + if ($this->state == 'empty') + { + return ''; + } + + // This is just an addition line. + if ($this->state == 'dump' || $this->state == 'add') + { + // Start adding to the addition stack. + $this->cols[0][] = $line; + $this->state = 'add'; + } + else + { + // This is inside a change block, so start accumulating lines. + $this->state = 'change'; + $this->cols[1][] = $line; + } + } + + function _deleted($lines) + { + array_walk($lines, array(&$this, '_perform_delete')); + } + + function _perform_delete($line) + { + // This is a removal line. + $this->state = 'remove'; + $this->cols[0][] = $line; + } + + function _context($lines) + { + array_walk($lines, array(&$this, '_perform_context')); + } + + function _perform_context($line) + { + // An empty block with no action. + switch ($this->state) + { + case 'add': + $this->data['contents'][] = array('type' => 'add', 'lines' => $this->cols[0]); + break; + + case 'remove': + // We have some removal lines pending in our stack, so flush them. + $this->data['contents'][] = array('type' => 'remove', 'lines' => $this->cols[0]); + break; + + case 'change': + // We have both remove and addition lines, so this is a change block. + $this->data['contents'][] = array('type' => 'change', 'old' => $this->cols[0], 'new' => $this->cols[1]); + break; + } + + $this->cols = array(array(), array()); + $this->data['contents'][] = array('type' => 'empty', 'line' => $line); + $this->state = 'dump'; + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } + +} + +?>
\ No newline at end of file |