Refactor diffs
* Merge MappedDiff into WordLevelDiff * Rename <something insane>WordAccumulator into WordAccumulator and namespace * Better variable names Change-Id: I5847f2bb89402d0537b9e99cccd24c547ce4e4e2
This commit is contained in:
parent
c9219c4f81
commit
1cac442f1f
4 changed files with 252 additions and 233 deletions
|
|
@ -779,7 +779,6 @@ $wgAutoloadLocalClasses = [
|
|||
'MalformedTitleException' => __DIR__ . '/includes/title/MalformedTitleException.php',
|
||||
'ManualLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
|
||||
'MapCacheLRU' => __DIR__ . '/includes/libs/MapCacheLRU.php',
|
||||
'MappedDiff' => __DIR__ . '/includes/diff/DairikiDiff.php',
|
||||
'MappedIterator' => __DIR__ . '/includes/libs/MappedIterator.php',
|
||||
'MarkpatrolledAction' => __DIR__ . '/includes/actions/MarkpatrolledAction.php',
|
||||
'McTest' => __DIR__ . '/maintenance/mctest.php',
|
||||
|
|
@ -829,6 +828,7 @@ $wgAutoloadLocalClasses = [
|
|||
'MediaWiki\\Auth\\Throttler' => __DIR__ . '/includes/auth/Throttler.php',
|
||||
'MediaWiki\\Auth\\UserDataAuthenticationRequest' => __DIR__ . '/includes/auth/UserDataAuthenticationRequest.php',
|
||||
'MediaWiki\\Auth\\UsernameAuthenticationRequest' => __DIR__ . '/includes/auth/UsernameAuthenticationRequest.php',
|
||||
'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
|
||||
'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
|
||||
'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php',
|
||||
'MediaWiki\\Linker\\LinkTarget' => __DIR__ . '/includes/linker/LinkTarget.php',
|
||||
|
|
@ -1518,7 +1518,7 @@ $wgAutoloadLocalClasses = [
|
|||
'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
|
||||
'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/WinCacheBagOStuff.php',
|
||||
'WithoutInterwikiPage' => __DIR__ . '/includes/specials/SpecialWithoutinterwiki.php',
|
||||
'WordLevelDiff' => __DIR__ . '/includes/diff/DairikiDiff.php',
|
||||
'WordLevelDiff' => __DIR__ . '/includes/diff/WordLevelDiff.php',
|
||||
'WrapOldPasswords' => __DIR__ . '/maintenance/wrapOldPasswords.php',
|
||||
'XCFHandler' => __DIR__ . '/includes/media/XCF.php',
|
||||
'XCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/XCacheBagOStuff.php',
|
||||
|
|
|
|||
|
|
@ -559,236 +559,7 @@ class Diff {
|
|||
}
|
||||
|
||||
/**
|
||||
* @todo document, bad name.
|
||||
* @private
|
||||
* @ingroup DifferenceEngine
|
||||
* @deprecated Alias for WordAccumulator, to be soon removed
|
||||
*/
|
||||
class MappedDiff extends Diff {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Computes 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 string[] $from_lines An array of strings.
|
||||
* Typically these are lines from a file.
|
||||
* @param string[] $to_lines An array of strings.
|
||||
* @param string[] $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 string[] $mapped_to_lines This array should
|
||||
* have the same number of elements as $to_lines.
|
||||
*/
|
||||
public function __construct( $from_lines, $to_lines,
|
||||
$mapped_from_lines, $mapped_to_lines ) {
|
||||
|
||||
assert( count( $from_lines ) == count( $mapped_from_lines ) );
|
||||
assert( count( $to_lines ) == count( $mapped_to_lines ) );
|
||||
|
||||
parent::__construct( $mapped_from_lines, $mapped_to_lines );
|
||||
|
||||
$xi = $yi = 0;
|
||||
$editCount = count( $this->edits );
|
||||
for ( $i = 0; $i < $editCount; $i++ ) {
|
||||
$orig = &$this->edits[$i]->orig;
|
||||
if ( is_array( $orig ) ) {
|
||||
$orig = array_slice( $from_lines, $xi, count( $orig ) );
|
||||
$xi += count( $orig );
|
||||
}
|
||||
|
||||
$closing = &$this->edits[$i]->closing;
|
||||
if ( is_array( $closing ) ) {
|
||||
$closing = array_slice( $to_lines, $yi, count( $closing ) );
|
||||
$yi += count( $closing );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
|
||||
*/
|
||||
|
||||
/**
|
||||
* @todo document
|
||||
* @private
|
||||
* @ingroup DifferenceEngine
|
||||
*/
|
||||
class HWLDFWordAccumulator {
|
||||
public $insClass = ' class="diffchange diffchange-inline"';
|
||||
public $delClass = ' class="diffchange diffchange-inline"';
|
||||
|
||||
private $lines = [];
|
||||
private $line = '';
|
||||
private $group = '';
|
||||
private $tag = '';
|
||||
|
||||
/**
|
||||
* @param string $new_tag
|
||||
*/
|
||||
private function flushGroup( $new_tag ) {
|
||||
if ( $this->group !== '' ) {
|
||||
if ( $this->tag == 'ins' ) {
|
||||
$this->line .= "<ins{$this->insClass}>" .
|
||||
htmlspecialchars( $this->group ) . '</ins>';
|
||||
} elseif ( $this->tag == 'del' ) {
|
||||
$this->line .= "<del{$this->delClass}>" .
|
||||
htmlspecialchars( $this->group ) . '</del>';
|
||||
} else {
|
||||
$this->line .= htmlspecialchars( $this->group );
|
||||
}
|
||||
}
|
||||
$this->group = '';
|
||||
$this->tag = $new_tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $new_tag
|
||||
*/
|
||||
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, ' ' );
|
||||
}
|
||||
$this->line = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $words
|
||||
* @param string $tag
|
||||
*/
|
||||
public function addWords( $words, $tag = '' ) {
|
||||
if ( $tag != $this->tag ) {
|
||||
$this->flushGroup( $tag );
|
||||
}
|
||||
|
||||
foreach ( $words as $word ) {
|
||||
// new-line should only come as first char of word.
|
||||
if ( $word == '' ) {
|
||||
continue;
|
||||
}
|
||||
if ( $word[0] == "\n" ) {
|
||||
$this->flushLine( $tag );
|
||||
$word = substr( $word, 1 );
|
||||
}
|
||||
assert( !strstr( $word, "\n" ) );
|
||||
$this->group .= $word;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLines() {
|
||||
$this->flushLine( '~done' );
|
||||
|
||||
return $this->lines;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo document
|
||||
* @private
|
||||
* @ingroup DifferenceEngine
|
||||
*/
|
||||
class WordLevelDiff extends MappedDiff {
|
||||
const MAX_LINE_LENGTH = 10000;
|
||||
|
||||
/**
|
||||
* @param string[] $orig_lines
|
||||
* @param string[] $closing_lines
|
||||
*/
|
||||
public function __construct( $orig_lines, $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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $lines
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
private function split( $lines ) {
|
||||
|
||||
$words = [];
|
||||
$stripped = [];
|
||||
$first = true;
|
||||
foreach ( $lines as $line ) {
|
||||
# If the line is too long, just pretend the entire line is one big word
|
||||
# This prevents resource exhaustion problems
|
||||
if ( $first ) {
|
||||
$first = false;
|
||||
} else {
|
||||
$words[] = "\n";
|
||||
$stripped[] = "\n";
|
||||
}
|
||||
if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
|
||||
$words[] = $line;
|
||||
$stripped[] = $line;
|
||||
} else {
|
||||
$m = [];
|
||||
if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
|
||||
$line, $m )
|
||||
) {
|
||||
foreach ( $m[0] as $word ) {
|
||||
$words[] = $word;
|
||||
}
|
||||
foreach ( $m[1] as $stripped_word ) {
|
||||
$stripped[] = $stripped_word;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ $words, $stripped ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function orig() {
|
||||
$orig = new HWLDFWordAccumulator;
|
||||
|
||||
foreach ( $this->edits as $edit ) {
|
||||
if ( $edit->type == 'copy' ) {
|
||||
$orig->addWords( $edit->orig );
|
||||
} elseif ( $edit->orig ) {
|
||||
$orig->addWords( $edit->orig, 'del' );
|
||||
}
|
||||
}
|
||||
$lines = $orig->getLines();
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function closing() {
|
||||
$closing = new HWLDFWordAccumulator;
|
||||
|
||||
foreach ( $this->edits as $edit ) {
|
||||
if ( $edit->type == 'copy' ) {
|
||||
$closing->addWords( $edit->closing );
|
||||
} elseif ( $edit->closing ) {
|
||||
$closing->addWords( $edit->closing, 'ins' );
|
||||
}
|
||||
}
|
||||
$lines = $closing->getLines();
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
class HWLDFWordAccumulator extends MediaWiki\Diff\WordAccumulator {
|
||||
}
|
||||
|
|
|
|||
107
includes/diff/WordAccumulator.php
Normal file
107
includes/diff/WordAccumulator.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
|
||||
* 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
|
||||
* @defgroup DifferenceEngine DifferenceEngine
|
||||
*/
|
||||
|
||||
namespace MediaWiki\Diff;
|
||||
|
||||
/**
|
||||
* Stores, escapes and formats the results of word-level diff
|
||||
*
|
||||
* @private
|
||||
* @ingroup DifferenceEngine
|
||||
*/
|
||||
class WordAccumulator {
|
||||
public $insClass = ' class="diffchange diffchange-inline"';
|
||||
public $delClass = ' class="diffchange diffchange-inline"';
|
||||
|
||||
private $lines = [];
|
||||
private $line = '';
|
||||
private $group = '';
|
||||
private $tag = '';
|
||||
|
||||
/**
|
||||
* @param string $new_tag
|
||||
*/
|
||||
private function flushGroup( $new_tag ) {
|
||||
if ( $this->group !== '' ) {
|
||||
if ( $this->tag == 'ins' ) {
|
||||
$this->line .= "<ins{$this->insClass}>" .
|
||||
htmlspecialchars( $this->group ) . '</ins>';
|
||||
} elseif ( $this->tag == 'del' ) {
|
||||
$this->line .= "<del{$this->delClass}>" .
|
||||
htmlspecialchars( $this->group ) . '</del>';
|
||||
} else {
|
||||
$this->line .= htmlspecialchars( $this->group );
|
||||
}
|
||||
}
|
||||
$this->group = '';
|
||||
$this->tag = $new_tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $new_tag
|
||||
*/
|
||||
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, ' ' );
|
||||
}
|
||||
$this->line = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $words
|
||||
* @param string $tag
|
||||
*/
|
||||
public function addWords( $words, $tag = '' ) {
|
||||
if ( $tag != $this->tag ) {
|
||||
$this->flushGroup( $tag );
|
||||
}
|
||||
|
||||
foreach ( $words as $word ) {
|
||||
// new-line should only come as first char of word.
|
||||
if ( $word == '' ) {
|
||||
continue;
|
||||
}
|
||||
if ( $word[0] == "\n" ) {
|
||||
$this->flushLine( $tag );
|
||||
$word = substr( $word, 1 );
|
||||
}
|
||||
assert( !strstr( $word, "\n" ) );
|
||||
$this->group .= $word;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLines() {
|
||||
$this->flushLine( '~done' );
|
||||
|
||||
return $this->lines;
|
||||
}
|
||||
}
|
||||
141
includes/diff/WordLevelDiff.php
Normal file
141
includes/diff/WordLevelDiff.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright © 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
|
||||
* 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
|
||||
* @defgroup DifferenceEngine DifferenceEngine
|
||||
*/
|
||||
|
||||
use MediaWiki\Diff\WordAccumulator;
|
||||
|
||||
/**
|
||||
* Performs a word-level diff on several lines
|
||||
*
|
||||
* @ingroup DifferenceEngine
|
||||
*/
|
||||
class WordLevelDiff extends \Diff {
|
||||
const MAX_LINE_LENGTH = 10000;
|
||||
|
||||
/**
|
||||
* @param string[] $linesBefore
|
||||
* @param string[] $linesAfter
|
||||
*/
|
||||
public function __construct( $linesBefore, $linesAfter ) {
|
||||
|
||||
list( $wordsBefore, $wordsBeforeStripped ) = $this->split( $linesBefore );
|
||||
list( $wordsAfter, $wordsAfterStripped ) = $this->split( $linesAfter );
|
||||
|
||||
parent::__construct( $wordsBeforeStripped, $wordsAfterStripped );
|
||||
|
||||
$xi = $yi = 0;
|
||||
$editCount = count( $this->edits );
|
||||
for ( $i = 0; $i < $editCount; $i++ ) {
|
||||
$orig = &$this->edits[$i]->orig;
|
||||
if ( is_array( $orig ) ) {
|
||||
$orig = array_slice( $wordsBefore, $xi, count( $orig ) );
|
||||
$xi += count( $orig );
|
||||
}
|
||||
|
||||
$closing = &$this->edits[$i]->closing;
|
||||
if ( is_array( $closing ) ) {
|
||||
$closing = array_slice( $wordsAfter, $yi, count( $closing ) );
|
||||
$yi += count( $closing );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $lines
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
private function split( $lines ) {
|
||||
|
||||
$words = [];
|
||||
$stripped = [];
|
||||
$first = true;
|
||||
foreach ( $lines as $line ) {
|
||||
# If the line is too long, just pretend the entire line is one big word
|
||||
# This prevents resource exhaustion problems
|
||||
if ( $first ) {
|
||||
$first = false;
|
||||
} else {
|
||||
$words[] = "\n";
|
||||
$stripped[] = "\n";
|
||||
}
|
||||
if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
|
||||
$words[] = $line;
|
||||
$stripped[] = $line;
|
||||
} else {
|
||||
$m = [];
|
||||
if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
|
||||
$line, $m )
|
||||
) {
|
||||
foreach ( $m[0] as $word ) {
|
||||
$words[] = $word;
|
||||
}
|
||||
foreach ( $m[1] as $stripped_word ) {
|
||||
$stripped[] = $stripped_word;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ $words, $stripped ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function orig() {
|
||||
$orig = new WordAccumulator;
|
||||
|
||||
foreach ( $this->edits as $edit ) {
|
||||
if ( $edit->type == 'copy' ) {
|
||||
$orig->addWords( $edit->orig );
|
||||
} elseif ( $edit->orig ) {
|
||||
$orig->addWords( $edit->orig, 'del' );
|
||||
}
|
||||
}
|
||||
$lines = $orig->getLines();
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function closing() {
|
||||
$closing = new WordAccumulator;
|
||||
|
||||
foreach ( $this->edits as $edit ) {
|
||||
if ( $edit->type == 'copy' ) {
|
||||
$closing->addWords( $edit->closing );
|
||||
} elseif ( $edit->closing ) {
|
||||
$closing->addWords( $edit->closing, 'ins' );
|
||||
}
|
||||
}
|
||||
$lines = $closing->getLines();
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue