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/diff.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/diff.php')
-rw-r--r-- | phpBB/includes/diff/diff.php | 779 |
1 files changed, 779 insertions, 0 deletions
diff --git a/phpBB/includes/diff/diff.php b/phpBB/includes/diff/diff.php new file mode 100644 index 0000000000..102f1119b2 --- /dev/null +++ b/phpBB/includes/diff/diff.php @@ -0,0 +1,779 @@ +<?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; +} + +// Include renderer and engine +include_once($phpbb_root_path . 'includes/diff/engine.' . $phpEx); +include_once($phpbb_root_path . 'includes/diff/renderer.' . $phpEx); + +/** +* 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 +*/ + +/** +* General API for generating and formatting diffs - the differences between +* two sequences of strings. +* +* The PHP diff code used in this package was originally written by Geoffrey +* T. Dairiki and is used with his permission. +* +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +*/ +class diff +{ + /** + * Array of changes. + * @var array + */ + var $_edits; + + /** + * Computes diffs between sequences of strings. + * + * @param array $from_lines An array of strings. Typically these are lines from a file. + * @param array $to_lines An array of strings. + */ + function diff($from_lines, $to_lines) + { + $diff_engine = &new diff_engine(); + $this->_edits = call_user_func_array(array($diff_engine, 'diff'), array($from_lines, $to_lines)); + } + + /** + * Returns the array of differences. + */ + function get_diff() + { + return $this->_edits; + } + + /** + * Computes a reversed diff. + * + * Example: + * <code> + * $diff = &new diff($lines1, $lines2); + * $rev = $diff->reverse(); + * </code> + * + * @return diff A Diff object representing the inverse of the original diff. + * Note that we purposely don't return a reference here, since + * this essentially is a clone() method. + */ + function reverse() + { + if (version_compare(zend_version(), '2', '>')) + { + $rev = clone($this); + } + else + { + $rev = $this; + } + + $rev->_edits = array(); + + foreach ($this->_edits as $edit) + { + $rev->_edits[] = $edit->reverse(); + } + + return $rev; + } + + /** + * Checks for an empty diff. + * + * @return boolean True if two sequences were identical. + */ + function is_empty() + { + foreach ($this->_edits as $edit) + { + if (!is_a($edit, 'diff_op_copy')) + { + return false; + } + } + return true; + } + + /** + * Computes the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposes. + * + * @return integer The length of the LCS. + */ + function lcs() + { + $lcs = 0; + + foreach ($this->_edits as $edit) + { + if (is_a($edit, 'diff_op_copy')) + { + $lcs += sizeof($edit->orig); + } + } + return $lcs; + } + + /** + * Gets the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the constructor. + * + * @return array The original sequence of strings. + */ + function get_original() + { + $lines = array(); + + foreach ($this->_edits as $edit) + { + if ($edit->orig) + { + array_splice($lines, sizeof($lines), 0, $edit->orig); + } + } + return $lines; + } + + /** + * Gets the final set of lines. + * + * This reconstructs the $to_lines parameter passed to the constructor. + * + * @return array The sequence of strings. + */ + function get_final() + { + $lines = array(); + + foreach ($this->_edits as $edit) + { + if ($edit->final) + { + array_splice($lines, sizeof($lines), 0, $edit->final); + } + } + return $lines; + } + + /** + * Removes trailing newlines from a line of text. This is meant to be used with array_walk(). + * + * @param string $line The line to trim. + * @param integer $key The index of the line in the array. Not used. + */ + function trim_newlines(&$line, $key) + { + $line = str_replace(array("\n", "\r"), '', $line); + } + + /** + * Checks a diff for validity. + * + * This is here only for debugging purposes. + */ + function _check($from_lines, $to_lines) + { + if (serialize($from_lines) != serialize($this->get_original())) + { + trigger_error("[diff] Reconstructed original doesn't match", E_USER_ERROR); + } + + if (serialize($to_lines) != serialize($this->get_final())) + { + trigger_error("[diff] Reconstructed final doesn't match", E_USER_ERROR); + } + + $rev = $this->reverse(); + + if (serialize($to_lines) != serialize($rev->get_original())) + { + trigger_error("[diff] Reversed original doesn't match", E_USER_ERROR); + } + + if (serialize($from_lines) != serialize($rev->get_final())) + { + trigger_error("[diff] Reversed final doesn't match", E_USER_ERROR); + } + + $prevtype = null; + + foreach ($this->_edits as $edit) + { + if ($prevtype == get_class($edit)) + { + trigger_error("[diff] Edit sequence is non-optimal", E_USER_ERROR); + } + $prevtype = get_class($edit); + } + + return true; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +*/ +class mapped_diff extends diff +{ + /** + * Computes a diff between sequences of strings. + * + * This can be used to compute things like case-insensitve diffs, or diffs + * which ignore changes in white-space. + * + * @param array $from_lines An array of strings. + * @param array $to_lines An array of strings. + * @param array $mapped_from_lines This array should have the same size number of elements as $from_lines. + * The elements in $mapped_from_lines and $mapped_to_lines are what is actually + * compared when computing the diff. + * @param array $mapped_to_lines This array should have the same number of elements as $to_lines. + */ + function mapped_diff($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) + { + if (sizeof($from_lines) != sizeof($mapped_from_lines) || sizeof($to_lines) != sizeof($mapped_to_lines)) + { + return false; + } + + parent::diff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < sizeof($this->_edits); $i++) + { + $orig = &$this->_edits[$i]->orig; + if (is_array($orig)) + { + $orig = array_slice($from_lines, $xi, sizeof($orig)); + $xi += sizeof($orig); + } + + $final = &$this->_edits[$i]->final; + if (is_array($final)) + { + $final = array_slice($to_lines, $yi, sizeof($final)); + $yi += sizeof($final); + } + } + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff_op +{ + var $orig; + var $final; + + function reverse() + { + trigger_error('[diff] Abstract method', E_USER_ERROR); + } + + function norig() + { + return ($this->orig) ? sizeof($this->orig) : 0; + } + + function nfinal() + { + return ($this->final) ? sizeof($this->final) : 0; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff_op_copy extends diff_op +{ + function diff_op_copy($orig, $final = false) + { + if (!is_array($final)) + { + $final = $orig; + } + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = &new diff_op_copy($this->final, $this->orig); + return $reverse; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff_op_delete extends diff_op +{ + function diff_op_delete($lines) + { + $this->orig = $lines; + $this->final = false; + } + + function &reverse() + { + $reverse = &new diff_op_add($this->orig); + return $reverse; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff_op_add extends diff_op +{ + function diff_op_add($lines) + { + $this->final = $lines; + $this->orig = false; + } + + function &reverse() + { + $reverse = &new diff_op_delete($this->final); + return $reverse; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff_op_change extends diff_op +{ + function diff_op_change($orig, $final) + { + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = &new diff_op_change($this->final, $this->orig); + return $reverse; + } +} + + +/** +* A class for computing three way diffs. +* +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +*/ +class diff3 extends diff +{ + /** + * Conflict counter. + * @var integer + */ + var $_conflicting_blocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array $orig The original lines to use. + * @param array $final1 The first version to compare to. + * @param array $final2 The second version to compare to. + */ + function diff3($orig, $final1, $final2) + { + $engine = new diff_engine(); + $this->_edits = $this->_diff3($engine->diff($orig, $final1), $engine->diff($orig, $final2)); + } + + /** + * Return merged output + * + * @param string $label1 the cvs file version/label from the original set of lines + * @param string $label2 the cvs file version/label from the new set of lines + * @param bool $get_conflicts if set to true only the number of conflicts is returned + * @param bool $merge_new if set to true the merged output will have the new file contents on a conflicting merge + * + * @return mixed the merged output + */ + function merged_output($label1 = 'CURRENT_FILE', $label2 = 'NEW_FILE', $get_conflicts = false, $merge_new = false) + { + global $user; + + if ($get_conflicts) + { + foreach ($this->_edits as $edit) + { + if ($edit->is_conflict()) + { + $this->_conflicting_blocks++; + } + } + + return $this->_conflicting_blocks; + } + + $label1 = (!empty($user->lang[$label1])) ? $user->lang[$label1] : $label1; + $label2 = (!empty($user->lang[$label2])) ? $user->lang[$label2] : $label2; + + $lines = array(); + + foreach ($this->_edits as $edit) + { + if ($edit->is_conflict()) + { + if (!$merge_new) + { + $lines = array_merge($lines, array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), $edit->final1, array('======='), $edit->final2, array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); + } + else + { + $lines = array_merge($lines, $edit->final1); + } + $this->_conflicting_blocks++; + } + else + { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * Merge the output and use the new file code for conflicts + */ + function merged_new_output() + { + $lines = array(); + + foreach ($this->_edits as $edit) + { + if ($edit->is_conflict()) + { + $lines = array_merge($lines, $edit->final2); + } + else + { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * Merge the output and use the original file code for conflicts + */ + function merged_orig_output() + { + $lines = array(); + + foreach ($this->_edits as $edit) + { + if ($edit->is_conflict()) + { + $lines = array_merge($lines, $edit->final1); + } + else + { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * Get conflicting block(s) + */ + function get_conflicts() + { + $conflicts = array(); + + foreach ($this->_edits as $edit) + { + if ($edit->is_conflict()) + { + $conflicts[] = array($edit->final1, $edit->final2); + } + } + + return $conflicts; + } + + /** + * @access private + */ + function _diff3($edits1, $edits2) + { + $edits = array(); + $bb = &new diff3_block_builder(); + + $e1 = current($edits1); + $e2 = current($edits2); + + while ($e1 || $e2) + { + if ($e1 && $e2 && is_a($e1, 'diff_op_copy') && is_a($e2, 'diff_op_copy')) + { + // We have copy blocks from both diffs. This is the (only) time we want to emit a diff3 copy block. + // Flush current diff3 diff block, if any. + if ($edit = $bb->finish()) + { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + $edits[] = &new diff3_op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) + { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } + else + { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) + { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } + else + { + $e2 = next($edits2); + } + } + else + { + if ($e1 && $e2) + { + if ($e1->orig && $e2->orig) + { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + else + { + $norig = 0; + } + + if (is_a($e1, 'diff_op_copy')) + { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'diff_op_copy')) + { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) + { + $bb->out1($e1->final); + $e1 = next($edits1); + } + + if ($e2 && ! $e2->orig) + { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) + { + $edits[] = $edit; + } + + return $edits; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff3_op +{ + function diff3_op($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) + { + if ($this->final1 === $this->final2) + { + $this->_merged = &$this->final1; + } + else if ($this->final1 === $this->orig) + { + $this->_merged = &$this->final2; + } + else if ($this->final2 === $this->orig) + { + $this->_merged = &$this->final1; + } + else + { + $this->_merged = false; + } + } + + return $this->_merged; + } + + function is_conflict() + { + return ($this->merged() === false) ? true : false; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff3_op_copy extends diff3_op +{ + function diff3_op_copy($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function is_conflict() + { + return false; + } +} + +/** +* @package phpBB3 +* @author Geoffrey T. Dairiki <dairiki@dairiki.org> +* +* @access private +*/ +class diff3_block_builder +{ + function diff3_block_builder() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) + { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) + { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) + { + $this->_append($this->final2, $lines); + } + } + + function is_empty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->is_empty()) + { + return false; + } + else + { + $edit = &new diff3_op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, sizeof($array), 0, $lines); + } +} + +?>
\ No newline at end of file |