From: Max Semenik Date: Tue, 29 Oct 2013 22:25:28 +0000 (+0400) Subject: Refactor diffs X-Git-Tag: 1.31.0-rc.0~18212^2 X-Git-Url: http://git.cyclocoop.org/fichier?a=commitdiff_plain;h=c37b4dc96418faffc7db5e78f79dc762cb83fb0c;p=lhc%2Fweb%2Fwiklou.git Refactor diffs * Move some classes to separate files to avoid having a monstrous file with a dozen classes. * Remove weird underscores from class names. * Instead of prefixing members with underscores, specify visibility explicitly. * Rename c_style -> camelCase per coding conventions. * Throw exceptions instead of fataling out with trigger_error(). * Remove pointless constant used only once. Change-Id: Icac23c0f6259c73f5fe07f201b83b9c332ba0469 --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index dbba50007c..ce98d7b8a2 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -531,21 +531,21 @@ $wgAutoloadLocalClasses = array( 'MWDebug' => 'includes/debug/Debug.php', # includes/diff - '_DiffEngine' => 'includes/diff/DairikiDiff.php', - '_DiffOp' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Add' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Change' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Copy' => 'includes/diff/DairikiDiff.php', - '_DiffOp_Delete' => 'includes/diff/DairikiDiff.php', - '_HWLDF_WordAccumulator' => 'includes/diff/DairikiDiff.php', - 'ArrayDiffFormatter' => 'includes/diff/DairikiDiff.php', + 'DiffEngine' => 'includes/diff/DairikiDiff.php', + 'DiffOp' => 'includes/diff/DairikiDiff.php', + 'DiffOp_Add' => 'includes/diff/DairikiDiff.php', + 'DiffOp_Change' => 'includes/diff/DairikiDiff.php', + 'DiffOp_Copy' => 'includes/diff/DairikiDiff.php', + 'DiffOp_Delete' => 'includes/diff/DairikiDiff.php', + 'HWLDF_WordAccumulator' => 'includes/diff/DairikiDiff.php', + 'ArrayDiffFormatter' => 'includes/diff/ArrayDiffFormatter.php', 'Diff' => 'includes/diff/DairikiDiff.php', 'DifferenceEngine' => 'includes/diff/DifferenceEngine.php', - 'DiffFormatter' => 'includes/diff/DairikiDiff.php', + 'DiffFormatter' => 'includes/diff/DiffFormatter.php', 'MappedDiff' => 'includes/diff/DairikiDiff.php', 'RangeDifference' => 'includes/diff/WikiDiff3.php', - 'TableDiffFormatter' => 'includes/diff/DairikiDiff.php', - 'UnifiedDiffFormatter' => 'includes/diff/DairikiDiff.php', + 'TableDiffFormatter' => 'includes/diff/TableDiffFormatter.php', + 'UnifiedDiffFormatter' => 'includes/diff/UnifiedDiffFormatter.php', 'WikiDiff3' => 'includes/diff/WikiDiff3.php', 'WordLevelDiff' => 'includes/diff/DairikiDiff.php', diff --git a/includes/diff/ArrayDiffFormatter.php b/includes/diff/ArrayDiffFormatter.php new file mode 100644 index 0000000000..7da6905cf8 --- /dev/null +++ b/includes/diff/ArrayDiffFormatter.php @@ -0,0 +1,79 @@ + + * You may copy this code freely under the conditions of the GPL. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup DifferenceEngine + */ + +/** + * A pseudo-formatter that just passes along the Diff::$edits array + * @ingroup DifferenceEngine + */ +class ArrayDiffFormatter extends DiffFormatter { + + /** + * @param $diff + * @return array + */ + public function format( $diff ) { + $oldline = 1; + $newline = 1; + $retval = array(); + foreach ( $diff->edits as $edit ) { + switch ( $edit->type ) { + case 'add': + foreach ( $edit->closing as $l ) { + $retval[] = array( + 'action' => 'add', + 'new' => $l, + 'newline' => $newline++ + ); + } + break; + case 'delete': + foreach ( $edit->orig as $l ) { + $retval[] = array( + 'action' => 'delete', + 'old' => $l, + 'oldline' => $oldline++, + ); + } + break; + case 'change': + foreach ( $edit->orig as $i => $l ) { + $retval[] = array( + 'action' => 'change', + 'old' => $l, + 'new' => isset( $edit->closing[$i] ) ? $edit->closing[$i] : null, + 'oldline' => $oldline++, + 'newline' => $newline++, + ); + } + break; + case 'copy': + $oldline += count( $edit->orig ); + $newline += count( $edit->orig ); + } + } + return $retval; + } +} diff --git a/includes/diff/DairikiDiff.php b/includes/diff/DairikiDiff.php index 298d724091..53e80c4dc9 100644 --- a/includes/diff/DairikiDiff.php +++ b/includes/diff/DairikiDiff.php @@ -30,14 +30,12 @@ * @private * @ingroup DifferenceEngine */ -class _DiffOp { - var $type; - var $orig; - var $closing; +abstract class DiffOp { + public $type; + public $orig; + public $closing; - function reverse() { - trigger_error( 'pure virtual', E_USER_ERROR ); - } + public abstract function reverse(); /** * @return int @@ -59,8 +57,8 @@ class _DiffOp { * @private * @ingroup DifferenceEngine */ -class _DiffOp_Copy extends _DiffOp { - var $type = 'copy'; +class DiffOp_Copy extends DiffOp { + public $type = 'copy'; function __construct( $orig, $closing = false ) { if ( !is_array( $closing ) ) { @@ -71,10 +69,10 @@ class _DiffOp_Copy extends _DiffOp { } /** - * @return _DiffOp_Copy + * @return DiffOp_Copy */ function reverse() { - return new _DiffOp_Copy( $this->closing, $this->orig ); + return new DiffOp_Copy( $this->closing, $this->orig ); } } @@ -83,8 +81,8 @@ class _DiffOp_Copy extends _DiffOp { * @private * @ingroup DifferenceEngine */ -class _DiffOp_Delete extends _DiffOp { - var $type = 'delete'; +class DiffOp_Delete extends DiffOp { + public $type = 'delete'; function __construct( $lines ) { $this->orig = $lines; @@ -92,10 +90,10 @@ class _DiffOp_Delete extends _DiffOp { } /** - * @return _DiffOp_Add + * @return DiffOp_Add */ function reverse() { - return new _DiffOp_Add( $this->orig ); + return new DiffOp_Add( $this->orig ); } } @@ -104,8 +102,8 @@ class _DiffOp_Delete extends _DiffOp { * @private * @ingroup DifferenceEngine */ -class _DiffOp_Add extends _DiffOp { - var $type = 'add'; +class DiffOp_Add extends DiffOp { + public $type = 'add'; function __construct( $lines ) { $this->closing = $lines; @@ -113,10 +111,10 @@ class _DiffOp_Add extends _DiffOp { } /** - * @return _DiffOp_Delete + * @return DiffOp_Delete */ function reverse() { - return new _DiffOp_Delete( $this->closing ); + return new DiffOp_Delete( $this->closing ); } } @@ -125,8 +123,8 @@ class _DiffOp_Add extends _DiffOp { * @private * @ingroup DifferenceEngine */ -class _DiffOp_Change extends _DiffOp { - var $type = 'change'; +class DiffOp_Change extends DiffOp { + public $type = 'change'; function __construct( $orig, $closing ) { $this->orig = $orig; @@ -134,10 +132,10 @@ class _DiffOp_Change extends _DiffOp { } /** - * @return _DiffOp_Change + * @return DiffOp_Change */ function reverse() { - return new _DiffOp_Change( $this->closing, $this->orig ); + return new DiffOp_Change( $this->closing, $this->orig ); } } @@ -165,7 +163,7 @@ class _DiffOp_Change extends _DiffOp { * @private * @ingroup DifferenceEngine */ -class _DiffEngine { +class DiffEngine { const MAX_XREF_LENGTH = 10000; @@ -187,11 +185,11 @@ class _DiffEngine { wfProfileIn( __METHOD__ ); // Diff and store locally - $this->diff_local( $from_lines, $to_lines ); + $this->diffLocal( $from_lines, $to_lines ); // Merge edits when possible - $this->_shift_boundaries( $from_lines, $this->xchanged, $this->ychanged ); - $this->_shift_boundaries( $to_lines, $this->ychanged, $this->xchanged ); + $this->shiftBoundaries( $from_lines, $this->xchanged, $this->ychanged ); + $this->shiftBoundaries( $to_lines, $this->ychanged, $this->xchanged ); // Compute the edit operations. $n_from = count( $from_lines ); @@ -211,7 +209,7 @@ class _DiffEngine { ++$yi; } if ( $copy ) { - $edits[] = new _DiffOp_Copy( $copy ); + $edits[] = new DiffOp_Copy( $copy ); } // Find deletes & adds. @@ -226,11 +224,11 @@ class _DiffEngine { } if ( $delete && $add ) { - $edits[] = new _DiffOp_Change( $delete, $add ); + $edits[] = new DiffOp_Change( $delete, $add ); } elseif ( $delete ) { - $edits[] = new _DiffOp_Delete( $delete ); + $edits[] = new DiffOp_Delete( $delete ); } elseif ( $add ) { - $edits[] = new _DiffOp_Add( $add ); + $edits[] = new DiffOp_Add( $add ); } } wfProfileOut( __METHOD__ ); @@ -241,7 +239,7 @@ class _DiffEngine { * @param $from_lines * @param $to_lines */ - function diff_local( $from_lines, $to_lines ) { + private function diffLocal( $from_lines, $to_lines ) { global $wgExternalDiffEngine; wfProfileIn( __METHOD__ ); @@ -282,21 +280,21 @@ class _DiffEngine { // Ignore lines which do not exist in both files. for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) { - $xhash[$this->_line_hash( $from_lines[$xi] )] = 1; + $xhash[$this->lineHash( $from_lines[$xi] )] = 1; } for ( $yi = $skip; $yi < $n_to - $endskip; $yi++ ) { $line = $to_lines[$yi]; - if ( ( $this->ychanged[$yi] = empty( $xhash[$this->_line_hash( $line )] ) ) ) { + if ( ( $this->ychanged[$yi] = empty( $xhash[$this->lineHash( $line )] ) ) ) { continue; } - $yhash[$this->_line_hash( $line )] = 1; + $yhash[$this->lineHash( $line )] = 1; $this->yv[] = $line; $this->yind[] = $yi; } for ( $xi = $skip; $xi < $n_from - $endskip; $xi++ ) { $line = $from_lines[$xi]; - if ( ( $this->xchanged[$xi] = empty( $yhash[$this->_line_hash( $line )] ) ) ) { + if ( ( $this->xchanged[$xi] = empty( $yhash[$this->lineHash( $line )] ) ) ) { continue; } $this->xv[] = $line; @@ -304,7 +302,7 @@ class _DiffEngine { } // Find the LCS. - $this->_compareseq( 0, count( $this->xv ), 0, count( $this->yv ) ); + $this->compareSeq( 0, count( $this->xv ), 0, count( $this->yv ) ); } wfProfileOut( __METHOD__ ); } @@ -314,7 +312,7 @@ class _DiffEngine { * @param $line string * @return string */ - function _line_hash( $line ) { + private function lineHash( $line ) { if ( strlen( $line ) > self::MAX_XREF_LENGTH ) { return md5( $line ); } else { @@ -345,7 +343,7 @@ class _DiffEngine { * @param $nchunks * @return array */ - function _diag( $xoff, $xlim, $yoff, $ylim, $nchunks ) { + private function diag( $xoff, $xlim, $yoff, $ylim, $nchunks ) { $flip = false; if ( $xlim - $xoff > $ylim - $yoff ) { @@ -389,7 +387,7 @@ class _DiffEngine { reset( $matches ); while ( list( , $y ) = each( $matches ) ) { if ( empty( $this->in_seq[$y] ) ) { - $k = $this->_lcs_pos( $y ); + $k = $this->lcsPos( $y ); assert( '$k > 0' ); $ymids[$k] = $ymids[$k -1]; break; @@ -404,7 +402,7 @@ class _DiffEngine { $this->seq[$k] = $y; $this->in_seq[$y] = 1; } elseif ( empty( $this->in_seq[$y] ) ) { - $k = $this->_lcs_pos( $y ); + $k = $this->lcsPos( $y ); assert( '$k > 0' ); $ymids[$k] = $ymids[$k -1]; } @@ -428,7 +426,7 @@ class _DiffEngine { * @param $ypos * @return int */ - function _lcs_pos( $ypos ) { + private function lcsPos( $ypos ) { $end = $this->lcs; if ( $end == 0 || $ypos > $this->seq[$end] ) { $this->seq[++$this->lcs] = $ypos; @@ -470,7 +468,7 @@ class _DiffEngine { * @param $yoff * @param $ylim */ - function _compareseq( $xoff, $xlim, $yoff, $ylim ) { + private function compareSeq( $xoff, $xlim, $yoff, $ylim ) { // Slide down the bottom initial diagonal. while ( $xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff] ) { ++$xoff; @@ -491,7 +489,7 @@ class _DiffEngine { // $nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); // $nchunks = max(2,min(8,(int)$nchunks)); $nchunks = min( 7, $xlim - $xoff, $ylim - $yoff ) + 1; - list( $lcs, $seps ) = $this->_diag( $xoff, $xlim, $yoff, $ylim, $nchunks ); + list( $lcs, $seps ) = $this->diag( $xoff, $xlim, $yoff, $ylim, $nchunks ); } if ( $lcs == 0 ) { @@ -508,7 +506,7 @@ class _DiffEngine { reset( $seps ); $pt1 = $seps[0]; while ( $pt2 = next( $seps ) ) { - $this->_compareseq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] ); + $this->compareSeq( $pt1[0], $pt2[0], $pt1[1], $pt2[1] ); $pt1 = $pt2; } } @@ -527,7 +525,7 @@ class _DiffEngine { * * This is extracted verbatim from analyze.c (GNU diffutils-2.7). */ - function _shift_boundaries( $lines, &$changed, $other_changed ) { + private function shiftBoundaries( $lines, &$changed, $other_changed ) { wfProfileIn( __METHOD__ ); $i = 0; $j = 0; @@ -654,7 +652,7 @@ class _DiffEngine { * @ingroup DifferenceEngine */ class Diff { - var $edits; + public $edits; /** * Constructor. @@ -665,9 +663,9 @@ class Diff { * @param $to_lines array An array of strings. */ function __construct( $from_lines, $to_lines ) { - $eng = new _DiffEngine; + $eng = new DiffEngine; $this->edits = $eng->diff( $from_lines, $to_lines ); - // $this->_check($from_lines, $to_lines); + // $this->check($from_lines, $to_lines); } /** @@ -683,6 +681,7 @@ class Diff { function reverse() { $rev = $this; $rev->edits = array(); + /** @var DiffOp $edit */ foreach ( $this->edits as $edit ) { $rev->edits[] = $edit->reverse(); } @@ -765,7 +764,7 @@ class Diff { * @param $from_lines * @param $to_lines */ - function _check( $from_lines, $to_lines ) { + private function check( $from_lines, $to_lines ) { wfProfileIn( __METHOD__ ); if ( serialize( $from_lines ) != serialize( $this->orig() ) ) { trigger_error( "Reconstructed original doesn't match", E_USER_ERROR ); @@ -852,363 +851,61 @@ class MappedDiff extends Diff { } } -/** - * A class to format Diffs - * - * This class formats the diff in classic diff format. - * It is intended that this class be customized via inheritance, - * to obtain fancier outputs. - * @todo document - * @private - * @ingroup DifferenceEngine - */ -class DiffFormatter { - /** - * 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; - - /** - * Format a diff. - * - * @param $diff Diff A Diff object. - * @return string The formatted output. - */ - function format( $diff ) { - wfProfileIn( __METHOD__ ); - - $xi = $yi = 1; - $block = false; - $context = array(); - - $nlead = $this->leading_context_lines; - $ntrail = $this->trailing_context_lines; - - $this->_start_diff(); - - foreach ( $diff->edits as $edit ) { - if ( $edit->type == 'copy' ) { - if ( is_array( $block ) ) { - if ( count( $edit->orig ) <= $nlead + $ntrail ) { - $block[] = $edit; - } else { - if ( $ntrail ) { - $context = array_slice( $edit->orig, 0, $ntrail ); - $block[] = new _DiffOp_Copy( $context ); - } - $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, count( $context ) - $nlead ); - $x0 = $xi - count( $context ); - $y0 = $yi - count( $context ); - $block = array(); - if ( $context ) { - $block[] = new _DiffOp_Copy( $context ); - } - } - $block[] = $edit; - } - - if ( $edit->orig ) { - $xi += count( $edit->orig ); - } - if ( $edit->closing ) { - $yi += count( $edit->closing ); - } - } - - if ( is_array( $block ) ) { - $this->_block( $x0, $xi - $x0, - $y0, $yi - $y0, - $block ); - } - - $end = $this->_end_diff(); - wfProfileOut( __METHOD__ ); - return $end; - } - - /** - * @param $xbeg - * @param $xlen - * @param $ybeg - * @param $ylen - * @param $edits - */ - function _block( $xbeg, $xlen, $ybeg, $ylen, &$edits ) { - wfProfileIn( __METHOD__ ); - $this->_start_block( $this->_block_header( $xbeg, $xlen, $ybeg, $ylen ) ); - foreach ( $edits as $edit ) { - if ( $edit->type == 'copy' ) { - $this->_context( $edit->orig ); - } elseif ( $edit->type == 'add' ) { - $this->_added( $edit->closing ); - } elseif ( $edit->type == 'delete' ) { - $this->_deleted( $edit->orig ); - } elseif ( $edit->type == 'change' ) { - $this->_changed( $edit->orig, $edit->closing ); - } else { - trigger_error( 'Unknown edit type', E_USER_ERROR ); - } - } - $this->_end_block(); - wfProfileOut( __METHOD__ ); - } - - function _start_diff() { - ob_start(); - } - - /** - * @return string - */ - function _end_diff() { - $val = ob_get_contents(); - ob_end_clean(); - return $val; - } - - /** - * @param $xbeg - * @param $xlen - * @param $ybeg - * @param $ylen - * @return string - */ - 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 ) { - echo $header . "\n"; - } - - function _end_block() { - } - - /** - * @param $lines - * @param $prefix string - */ - function _lines( $lines, $prefix = ' ' ) { - foreach ( $lines as $line ) { - echo "$prefix $line\n"; - } - } - - /** - * @param $lines - */ - function _context( $lines ) { - $this->_lines( $lines ); - } - - /** - * @param $lines - */ - function _added( $lines ) { - $this->_lines( $lines, '>' ); - } - - /** - * @param $lines - */ - function _deleted( $lines ) { - $this->_lines( $lines, '<' ); - } - - /** - * @param $orig - * @param $closing - */ - function _changed( $orig, $closing ) { - $this->_deleted( $orig ); - echo "---\n"; - $this->_added( $closing ); - } -} - -/** - * A formatter that outputs unified diffs - * @ingroup DifferenceEngine - */ -class UnifiedDiffFormatter extends DiffFormatter { - var $leading_context_lines = 2; - var $trailing_context_lines = 2; - - /** - * @param $lines - */ - function _added( $lines ) { - $this->_lines( $lines, '+' ); - } - - /** - * @param $lines - */ - function _deleted( $lines ) { - $this->_lines( $lines, '-' ); - } - - /** - * @param $orig - * @param $closing - */ - function _changed( $orig, $closing ) { - $this->_deleted( $orig ); - $this->_added( $closing ); - } - - /** - * @param $xbeg - * @param $xlen - * @param $ybeg - * @param $ylen - * @return string - */ - function _block_header( $xbeg, $xlen, $ybeg, $ylen ) { - return "@@ -$xbeg,$xlen +$ybeg,$ylen @@"; - } -} - -/** - * A pseudo-formatter that just passes along the Diff::$edits array - * @ingroup DifferenceEngine - */ -class ArrayDiffFormatter extends DiffFormatter { - - /** - * @param $diff - * @return array - */ - function format( $diff ) { - $oldline = 1; - $newline = 1; - $retval = array(); - foreach ( $diff->edits as $edit ) { - switch ( $edit->type ) { - case 'add': - foreach ( $edit->closing as $l ) { - $retval[] = array( - 'action' => 'add', - 'new' => $l, - 'newline' => $newline++ - ); - } - break; - case 'delete': - foreach ( $edit->orig as $l ) { - $retval[] = array( - 'action' => 'delete', - 'old' => $l, - 'oldline' => $oldline++, - ); - } - break; - case 'change': - foreach ( $edit->orig as $i => $l ) { - $retval[] = array( - 'action' => 'change', - 'old' => $l, - 'new' => isset( $edit->closing[$i] ) ? $edit->closing[$i] : null, - 'oldline' => $oldline++, - 'newline' => $newline++, - ); - } - break; - case 'copy': - $oldline += count( $edit->orig ); - $newline += count( $edit->orig ); - } - } - return $retval; - } -} - /** * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 */ -define( 'NBSP', ' ' ); // iso-8859-x non-breaking space. - /** * @todo document * @private * @ingroup DifferenceEngine */ -class _HWLDF_WordAccumulator { - function __construct() { - $this->_lines = array(); - $this->_line = ''; - $this->_group = ''; - $this->_tag = ''; - } +class HWLDF_WordAccumulator { + private $lines = array(); + private $line = ''; + private $group = ''; + private $tag = ''; /** * @param $new_tag */ - function _flushGroup( $new_tag ) { - if ( $this->_group !== '' ) { - if ( $this->_tag == 'ins' ) { - $this->_line .= '' . - htmlspecialchars( $this->_group ) . ''; - } elseif ( $this->_tag == 'del' ) { - $this->_line .= '' . - htmlspecialchars( $this->_group ) . ''; + private function flushGroup( $new_tag ) { + if ( $this->group !== '' ) { + if ( $this->tag == 'ins' ) { + $this->line .= '' . + htmlspecialchars( $this->group ) . ''; + } elseif ( $this->tag == 'del' ) { + $this->line .= '' . + htmlspecialchars( $this->group ) . ''; } else { - $this->_line .= htmlspecialchars( $this->_group ); + $this->line .= htmlspecialchars( $this->group ); } } - $this->_group = ''; - $this->_tag = $new_tag; + $this->group = ''; + $this->tag = $new_tag; } /** * @param $new_tag */ - function _flushLine( $new_tag ) { - $this->_flushGroup( $new_tag ); - if ( $this->_line != '' ) { - array_push( $this->_lines, $this->_line ); + private function flushLine( $new_tag ) { + $this->flushGroup( $new_tag ); + if ( $this->line != '' ) { + array_push( $this->lines, $this->line ); } else { # make empty lines visible by inserting an NBSP - array_push( $this->_lines, NBSP ); + array_push( $this->lines, ' ' ); } - $this->_line = ''; + $this->line = ''; } /** * @param $words * @param $tag string */ - function addWords( $words, $tag = '' ) { - if ( $tag != $this->_tag ) { - $this->_flushGroup( $tag ); + public function addWords( $words, $tag = '' ) { + if ( $tag != $this->tag ) { + $this->flushGroup( $tag ); } foreach ( $words as $word ) { @@ -1217,20 +914,20 @@ class _HWLDF_WordAccumulator { continue; } if ( $word[0] == "\n" ) { - $this->_flushLine( $tag ); + $this->flushLine( $tag ); $word = substr( $word, 1 ); } assert( '!strstr( $word, "\n" )' ); - $this->_group .= $word; + $this->group .= $word; } } /** * @return array */ - function getLines() { - $this->_flushLine( '~done' ); - return $this->_lines; + public function getLines() { + $this->flushLine( '~done' ); + return $this->lines; } } @@ -1249,8 +946,8 @@ class WordLevelDiff extends MappedDiff { function __construct( $orig_lines, $closing_lines ) { wfProfileIn( __METHOD__ ); - list( $orig_words, $orig_stripped ) = $this->_split( $orig_lines ); - list( $closing_words, $closing_stripped ) = $this->_split( $closing_lines ); + list( $orig_words, $orig_stripped ) = $this->split( $orig_lines ); + list( $closing_words, $closing_stripped ) = $this->split( $closing_lines ); parent::__construct( $orig_words, $closing_words, $orig_stripped, $closing_stripped ); @@ -1261,7 +958,7 @@ class WordLevelDiff extends MappedDiff { * @param $lines * @return array */ - function _split( $lines ) { + private function split( $lines ) { wfProfileIn( __METHOD__ ); $words = array(); @@ -1300,9 +997,9 @@ class WordLevelDiff extends MappedDiff { /** * @return array */ - function orig() { + public function orig() { wfProfileIn( __METHOD__ ); - $orig = new _HWLDF_WordAccumulator; + $orig = new HWLDF_WordAccumulator; foreach ( $this->edits as $edit ) { if ( $edit->type == 'copy' ) { @@ -1319,9 +1016,9 @@ class WordLevelDiff extends MappedDiff { /** * @return array */ - function closing() { + public function closing() { wfProfileIn( __METHOD__ ); - $closing = new _HWLDF_WordAccumulator; + $closing = new HWLDF_WordAccumulator; foreach ( $this->edits as $edit ) { if ( $edit->type == 'copy' ) { @@ -1335,161 +1032,3 @@ class WordLevelDiff extends MappedDiff { return $lines; } } - -/** - * Wikipedia Table style diff formatter. - * @todo document - * @private - * @ingroup DifferenceEngine - */ -class TableDiffFormatter extends DiffFormatter { - function __construct() { - $this->leading_context_lines = 2; - $this->trailing_context_lines = 2; - } - - /** - * @static - * @param $msg - * @return mixed - */ - public static function escapeWhiteSpace( $msg ) { - $msg = preg_replace( '/^ /m', '  ', $msg ); - $msg = preg_replace( '/ $/m', '  ', $msg ); - $msg = preg_replace( '/ /', '  ', $msg ); - return $msg; - } - - /** - * @param $xbeg - * @param $xlen - * @param $ybeg - * @param $ylen - * @return string - */ - function _block_header( $xbeg, $xlen, $ybeg, $ylen ) { - $r = '\n" . - '\n"; - return $r; - } - - /** - * @param $header - */ - function _start_block( $header ) { - echo $header; - } - - function _end_block() { - } - - function _lines( $lines, $prefix = ' ', $color = 'white' ) { - } - - /** - * HTML-escape parameter before calling this - * @param $line - * @return string - */ - function addedLine( $line ) { - return $this->wrapLine( '+', 'diff-addedline', $line ); - } - - /** - * HTML-escape parameter before calling this - * @param $line - * @return string - */ - function deletedLine( $line ) { - return $this->wrapLine( '−', 'diff-deletedline', $line ); - } - - /** - * HTML-escape parameter before calling this - * @param $line - * @return string - */ - function contextLine( $line ) { - return $this->wrapLine( ' ', 'diff-context', $line ); - } - - /** - * @param $marker - * @param $class - * @param $line - * @return string - */ - private function wrapLine( $marker, $class, $line ) { - if ( $line !== '' ) { - // The
wrapper is needed for 'overflow: auto' style to scroll properly - $line = Xml::tags( 'div', null, $this->escapeWhiteSpace( $line ) ); - } - return "$marker$line"; - } - - /** - * @return string - */ - function emptyLine() { - return ' '; - } - - /** - * @param $lines array - */ - function _added( $lines ) { - foreach ( $lines as $line ) { - echo '' . $this->emptyLine() . - $this->addedLine( '' . - htmlspecialchars( $line ) . '' ) . "\n"; - } - } - - /** - * @param $lines - */ - function _deleted( $lines ) { - foreach ( $lines as $line ) { - echo '' . $this->deletedLine( '' . - htmlspecialchars( $line ) . '' ) . - $this->emptyLine() . "\n"; - } - } - - /** - * @param $lines - */ - function _context( $lines ) { - foreach ( $lines as $line ) { - echo '' . - $this->contextLine( htmlspecialchars( $line ) ) . - $this->contextLine( htmlspecialchars( $line ) ) . "\n"; - } - } - - /** - * @param $orig - * @param $closing - */ - function _changed( $orig, $closing ) { - wfProfileIn( __METHOD__ ); - - $diff = new WordLevelDiff( $orig, $closing ); - $del = $diff->orig(); - $add = $diff->closing(); - - # Notice that WordLevelDiff returns HTML-escaped output. - # Hence, we will be calling addedLine/deletedLine without HTML-escaping. - - while ( $line = array_shift( $del ) ) { - $aline = array_shift( $add ); - echo '' . $this->deletedLine( $line ) . - $this->addedLine( $aline ) . "\n"; - } - foreach ( $add as $line ) { # If any leftovers - echo '' . $this->emptyLine() . - $this->addedLine( $line ) . "\n"; - } - wfProfileOut( __METHOD__ ); - } -} diff --git a/includes/diff/DiffFormatter.php b/includes/diff/DiffFormatter.php new file mode 100644 index 0000000000..78ac77f175 --- /dev/null +++ b/includes/diff/DiffFormatter.php @@ -0,0 +1,225 @@ + + * You may copy this code freely under the conditions of the GPL. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup DifferenceEngine + */ + +/** + * Base class for diff formatters + * + * This class formats the diff in classic diff format. + * It is intended that this class be customized via inheritance, + * to obtain fancier outputs. + * @todo document + * @ingroup DifferenceEngine + */ +abstract class DiffFormatter { + /** + * 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. + */ + protected $leadingContextLines = 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. + */ + protected $trailingContextLines = 0; + + /** + * Format a diff. + * + * @param $diff Diff A Diff object. + * @return string The formatted output. + */ + public function format( $diff ) { + wfProfileIn( __METHOD__ ); + + $xi = $yi = 1; + $block = false; + $context = array(); + + $nlead = $this->leadingContextLines; + $ntrail = $this->trailingContextLines; + + $this->startDiff(); + + foreach ( $diff->edits as $edit ) { + if ( $edit->type == 'copy' ) { + if ( is_array( $block ) ) { + if ( count( $edit->orig ) <= $nlead + $ntrail ) { + $block[] = $edit; + } else { + if ( $ntrail ) { + $context = array_slice( $edit->orig, 0, $ntrail ); + $block[] = new DiffOp_Copy( $context ); + } + $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, count( $context ) - $nlead ); + $x0 = $xi - count( $context ); + $y0 = $yi - count( $context ); + $block = array(); + if ( $context ) { + $block[] = new DiffOp_Copy( $context ); + } + } + $block[] = $edit; + } + + if ( $edit->orig ) { + $xi += count( $edit->orig ); + } + if ( $edit->closing ) { + $yi += count( $edit->closing ); + } + } + + if ( is_array( $block ) ) { + $this->block( $x0, $xi - $x0, + $y0, $yi - $y0, + $block ); + } + + $end = $this->endDiff(); + wfProfileOut( __METHOD__ ); + return $end; + } + + /** + * @param $xbeg + * @param $xlen + * @param $ybeg + * @param $ylen + * @param $edits + */ + protected function block( $xbeg, $xlen, $ybeg, $ylen, &$edits ) { + wfProfileIn( __METHOD__ ); + $this->startBlock( $this->blockHeader( $xbeg, $xlen, $ybeg, $ylen ) ); + foreach ( $edits as $edit ) { + if ( $edit->type == 'copy' ) { + $this->context( $edit->orig ); + } elseif ( $edit->type == 'add' ) { + $this->added( $edit->closing ); + } elseif ( $edit->type == 'delete' ) { + $this->deleted( $edit->orig ); + } elseif ( $edit->type == 'change' ) { + $this->changed( $edit->orig, $edit->closing ); + } else { + throw new MWException( "Unknown edit type: {$edit->type}" ); + } + } + $this->endBlock(); + wfProfileOut( __METHOD__ ); + } + + protected function startDiff() { + ob_start(); + } + + /** + * @return string + */ + protected function endDiff() { + $val = ob_get_contents(); + ob_end_clean(); + return $val; + } + + /** + * @param $xbeg + * @param $xlen + * @param $ybeg + * @param $ylen + * @return string + */ + protected function blockHeader( $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; + } + + protected function startBlock( $header ) { + echo $header . "\n"; + } + + protected function endBlock() { + } + + /** + * @param $lines + * @param $prefix string + */ + protected function lines( $lines, $prefix = ' ' ) { + foreach ( $lines as $line ) { + echo "$prefix $line\n"; + } + } + + /** + * @param $lines + */ + protected function context( $lines ) { + $this->lines( $lines ); + } + + /** + * @param $lines + */ + protected function added( $lines ) { + $this->lines( $lines, '>' ); + } + + /** + * @param $lines + */ + protected function deleted( $lines ) { + $this->lines( $lines, '<' ); + } + + /** + * @param $orig + * @param $closing + */ + protected function changed( $orig, $closing ) { + $this->deleted( $orig ); + echo "---\n"; + $this->added( $closing ); + } +} diff --git a/includes/diff/TableDiffFormatter.php b/includes/diff/TableDiffFormatter.php new file mode 100644 index 0000000000..0ce17df2b0 --- /dev/null +++ b/includes/diff/TableDiffFormatter.php @@ -0,0 +1,183 @@ + + * You may copy this code freely under the conditions of the GPL. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup DifferenceEngine + */ + +/** + * MediaWiki default table style diff formatter + * @todo document + * @private + * @ingroup DifferenceEngine + */ +class TableDiffFormatter extends DiffFormatter { + function __construct() { + $this->leadingContextLines = 2; + $this->trailingContextLines = 2; + } + + /** + * @static + * @param $msg + * @return mixed + */ + public static function escapeWhiteSpace( $msg ) { + $msg = preg_replace( '/^ /m', '  ', $msg ); + $msg = preg_replace( '/ $/m', '  ', $msg ); + $msg = preg_replace( '/ /', '  ', $msg ); + return $msg; + } + + /** + * @param $xbeg + * @param $xlen + * @param $ybeg + * @param $ylen + * @return string + */ + protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) { + $r = '\n" . + '\n"; + return $r; + } + + /** + * @param $header + */ + protected function startBlock( $header ) { + echo $header; + } + + protected function endBlock() { + } + + protected function lines( $lines, $prefix = ' ', $color = 'white' ) { + } + + /** + * HTML-escape parameter before calling this + * @param $line + * @return string + */ + protected function addedLine( $line ) { + return $this->wrapLine( '+', 'diff-addedline', $line ); + } + + /** + * HTML-escape parameter before calling this + * @param $line + * @return string + */ + protected function deletedLine( $line ) { + return $this->wrapLine( '−', 'diff-deletedline', $line ); + } + + /** + * HTML-escape parameter before calling this + * @param $line + * @return string + */ + protected function contextLine( $line ) { + return $this->wrapLine( ' ', 'diff-context', $line ); + } + + /** + * @param $marker + * @param $class + * @param $line + * @return string + */ + protected function wrapLine( $marker, $class, $line ) { + if ( $line !== '' ) { + // The
wrapper is needed for 'overflow: auto' style to scroll properly + $line = Xml::tags( 'div', null, $this->escapeWhiteSpace( $line ) ); + } + return "$marker$line"; + } + + /** + * @return string + */ + protected function emptyLine() { + return ' '; + } + + /** + * @param $lines array + */ + protected function added( $lines ) { + foreach ( $lines as $line ) { + echo '' . $this->emptyLine() . + $this->addedLine( '' . + htmlspecialchars( $line ) . '' ) . "\n"; + } + } + + /** + * @param $lines + */ + protected function deleted( $lines ) { + foreach ( $lines as $line ) { + echo '' . $this->deletedLine( '' . + htmlspecialchars( $line ) . '' ) . + $this->emptyLine() . "\n"; + } + } + + /** + * @param $lines + */ + protected function context( $lines ) { + foreach ( $lines as $line ) { + echo '' . + $this->contextLine( htmlspecialchars( $line ) ) . + $this->contextLine( htmlspecialchars( $line ) ) . "\n"; + } + } + + /** + * @param $orig + * @param $closing + */ + protected function changed( $orig, $closing ) { + wfProfileIn( __METHOD__ ); + + $diff = new WordLevelDiff( $orig, $closing ); + $del = $diff->orig(); + $add = $diff->closing(); + + # Notice that WordLevelDiff returns HTML-escaped output. + # Hence, we will be calling addedLine/deletedLine without HTML-escaping. + + while ( $line = array_shift( $del ) ) { + $aline = array_shift( $add ); + echo '' . $this->deletedLine( $line ) . + $this->addedLine( $aline ) . "\n"; + } + foreach ( $add as $line ) { # If any leftovers + echo '' . $this->emptyLine() . + $this->addedLine( $line ) . "\n"; + } + wfProfileOut( __METHOD__ ); + } +} diff --git a/includes/diff/UnifiedDiffFormatter.php b/includes/diff/UnifiedDiffFormatter.php new file mode 100644 index 0000000000..d489538b85 --- /dev/null +++ b/includes/diff/UnifiedDiffFormatter.php @@ -0,0 +1,68 @@ + + * You may copy this code freely under the conditions of the GPL. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup DifferenceEngine + */ + +/** + * A formatter that outputs unified diffs + * @ingroup DifferenceEngine + */ +class UnifiedDiffFormatter extends DiffFormatter { + protected $leadingContextLines = 2; + protected $trailingContextLines = 2; + + /** + * @param $lines + */ + protected function added( $lines ) { + $this->lines( $lines, '+' ); + } + + /** + * @param $lines + */ + protected function deleted( $lines ) { + $this->lines( $lines, '-' ); + } + + /** + * @param $orig + * @param $closing + */ + protected function changed( $orig, $closing ) { + $this->deleted( $orig ); + $this->added( $closing ); + } + + /** + * @param $xbeg + * @param $xlen + * @param $ybeg + * @param $ylen + * @return string + */ + protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) { + return "@@ -$xbeg,$xlen +$ybeg,$ylen @@"; + } +}