MediaWiki master
TableDiffFormatter.php
Go to the documentation of this file.
1<?php
27namespace Wikimedia\Diff;
28
29use InvalidArgumentException;
30
38
42 private const SIDE_DELETED = 'deleted';
43 private const SIDE_ADDED = 'added';
44 private const SIDE_CLASSES = [
45 self::SIDE_DELETED => 'diff-side-deleted',
46 self::SIDE_ADDED => 'diff-side-added'
47 ];
48
49 public function __construct() {
50 $this->leadingContextLines = 2;
51 $this->trailingContextLines = 2;
52 }
53
59 public static function escapeWhiteSpace( $msg ) {
60 $msg = preg_replace( '/^ /m', "\u{00A0} ", $msg );
61 $msg = preg_replace( '/ $/m', " \u{00A0}", $msg );
62 $msg = preg_replace( '/ /', "\u{00A0} ", $msg );
63
64 return $msg;
65 }
66
75 protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) {
76 // '<!--LINE \d+ -->' get replaced by a localised line number
77 // in BaseTextDiffer::localizeLineNumbers
78 return $this->rawElement(
79 'tr',
80 [],
81 $this->rawElement(
82 'td',
83 [ 'colspan' => '2', 'class' => 'diff-lineno', 'id' => 'mw-diff-left-l' . $xbeg ],
84 '<!--LINE ' . $xbeg . '-->'
85 ) .
86 "\n" .
87 $this->rawElement(
88 'td',
89 [ 'colspan' => '2', 'class' => 'diff-lineno' ],
90 '<!--LINE ' . $ybeg . '-->'
91 )
92 ) . "\n";
93 }
94
96 protected function startBlock( $header ) {
97 $this->writeOutput( $header );
98 }
99
101 protected function endBlock() {
102 }
103
109 protected function lines( $lines, $prefix = ' ', $color = 'white' ) {
110 }
111
119 protected function addedLine( $line ) {
120 return $this->wrapLine( '+', [ 'diff-addedline', $this->getClassForSide( self::SIDE_ADDED ) ], $line );
121 }
122
130 protected function deletedLine( $line ) {
131 return $this->wrapLine( '−', [ 'diff-deletedline', $this->getClassForSide( self::SIDE_DELETED ) ], $line );
132 }
133
142 protected function contextLine( $line, string $side ) {
143 return $this->wrapLine( '', [ 'diff-context', $this->getClassForSide( $side ) ], $line );
144 }
145
153 protected function wrapLine( $marker, $class, $line ) {
154 if ( $line !== '' ) {
155 // The <div> wrapper is needed for 'overflow: auto' style to scroll properly
156 $line = $this->rawElement( 'div', [], $this->escapeWhiteSpace( $line ) );
157 } else {
158 $line = '<br>';
159 }
160
161 $markerAttrs = [ 'class' => 'diff-marker' ];
162 if ( $marker ) {
163 $markerAttrs['data-marker'] = $marker;
164 }
165
166 if ( is_array( $class ) ) {
167 $class = implode( ' ', $class );
168 }
169
170 return $this->element( 'td', $markerAttrs ) .
171 $this->rawElement( 'td', [ 'class' => $class ], $line );
172 }
173
178 protected function emptyLine( string $side ) {
179 return $this->element( 'td', [ 'colspan' => '2', 'class' => $this->getClassForSide( $side ) ] );
180 }
181
187 protected function added( $lines ) {
188 foreach ( $lines as $line ) {
189 $this->writeOutput(
190 $this->rawElement(
191 'tr',
192 [],
193 $this->emptyLine( self::SIDE_DELETED ) .
194 $this->addedLine(
195 $this->element(
196 'ins',
197 [ 'class' => 'diffchange' ],
198 $line
199 )
200 )
201 ) .
202 "\n"
203 );
204 }
205 }
206
212 protected function deleted( $lines ) {
213 foreach ( $lines as $line ) {
214 $this->writeOutput(
215 $this->rawElement(
216 'tr',
217 [],
218 $this->deletedLine(
219 $this->element(
220 'del',
221 [ 'class' => 'diffchange' ],
222 $line
223 )
224 ) .
225 $this->emptyLine( self::SIDE_ADDED )
226 ) .
227 "\n"
228 );
229 }
230 }
231
237 protected function context( $lines ) {
238 foreach ( $lines as $line ) {
239 $this->writeOutput(
240 $this->rawElement(
241 'tr',
242 [],
243 $this->contextLine( htmlspecialchars( $line ), self::SIDE_DELETED ) .
244 $this->contextLine( htmlspecialchars( $line ), self::SIDE_ADDED )
245 ) .
246 "\n"
247 );
248 }
249 }
250
257 protected function changed( $orig, $closing ) {
258 $diff = new WordLevelDiff( $orig, $closing );
259 $del = $diff->orig();
260 $add = $diff->closing();
261
262 # Notice that WordLevelDiff returns HTML-escaped output.
263 # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
264
265 $ndel = count( $del );
266 $nadd = count( $add );
267 $n = max( $ndel, $nadd );
268 for ( $i = 0; $i < $n; $i++ ) {
269 $delLine = $i < $ndel ? $this->deletedLine( $del[$i] ) : $this->emptyLine( self::SIDE_DELETED );
270 $addLine = $i < $nadd ? $this->addedLine( $add[$i] ) : $this->emptyLine( self::SIDE_ADDED );
271 $this->writeOutput(
272 $this->rawElement(
273 'tr',
274 [],
275 $delLine . $addLine
276 ) .
277 "\n"
278 );
279 }
280 }
281
289 private function getClassForSide( string $side ): string {
290 if ( !isset( self::SIDE_CLASSES[$side] ) ) {
291 throw new InvalidArgumentException( "Invalid diff side: $side" );
292 }
293 return self::SIDE_CLASSES[$side];
294 }
295
304 private function rawElement( $element, $attribs = [], $contents = '' ) {
305 $ret = "<$element";
306 foreach ( $attribs as $name => $value ) {
307 $ret .= " $name=\"" . htmlspecialchars( $value, ENT_QUOTES ) . '"';
308 }
309 $ret .= ">$contents</$element>";
310 return $ret;
311 }
312
321 private function element( $element, $attribs = [], $contents = '' ) {
322 return $this->rawElement( $element, $attribs, htmlspecialchars( $contents, ENT_NOQUOTES ) );
323 }
324}
325
327class_alias( TableDiffFormatter::class, 'TableDiffFormatter' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
Base class for diff formatters.
writeOutput( $text)
Writes a string to the output buffer.
MediaWiki default table style diff formatter.
blockHeader( $xbeg, $xlen, $ybeg, $ylen)
deleted( $lines)
Writes all lines to the output buffer, each enclosed in .
addedLine( $line)
HTML-escape parameter before calling this.
endBlock()
Called at the end of a block of connected edits.This default implementation does nothing.
contextLine( $line, string $side)
HTML-escape parameter before calling this.
startBlock( $header)
Called at the start of a block of connected edits.This default implementation writes the header and a...
lines( $lines, $prefix=' ', $color='white')
added( $lines)
Writes all lines to the output buffer, each enclosed in .
deletedLine( $line)
HTML-escape parameter before calling this.
changed( $orig, $closing)
Writes the two sets of lines to the output buffer, each enclosed in .
context( $lines)
Writes all lines to the output buffer, each enclosed in .
Performs a word-level diff on several lines.
element(SerializerNode $parent, SerializerNode $node, $contents)
if(!file_exists( $CREDITS)) $lines
$header