MediaWiki master
TableDiffFormatter.php
Go to the documentation of this file.
1<?php
13namespace Wikimedia\Diff;
14
15use InvalidArgumentException;
16
24
28 private const SIDE_DELETED = 'deleted';
29 private const SIDE_ADDED = 'added';
30 private const SIDE_CLASSES = [
31 self::SIDE_DELETED => 'diff-side-deleted',
32 self::SIDE_ADDED => 'diff-side-added'
33 ];
34
35 public function __construct() {
36 $this->leadingContextLines = 2;
37 $this->trailingContextLines = 2;
38 }
39
45 public static function escapeWhiteSpace( $msg ) {
46 $msg = preg_replace( '/^ /m', "\u{00A0} ", $msg );
47 $msg = preg_replace( '/ $/m', " \u{00A0}", $msg );
48 $msg = preg_replace( '/ /', "\u{00A0} ", $msg );
49
50 return $msg;
51 }
52
61 protected function blockHeader( $xbeg, $xlen, $ybeg, $ylen ) {
62 // '<!--LINE \d+ -->' get replaced by a localised line number
63 // in BaseTextDiffer::localizeLineNumbers
64 return $this->rawElement(
65 'tr',
66 [],
67 $this->rawElement(
68 'td',
69 [ 'colspan' => '2', 'class' => 'diff-lineno', 'id' => 'mw-diff-left-l' . $xbeg ],
70 '<!--LINE ' . $xbeg . '-->'
71 ) .
72 "\n" .
73 $this->rawElement(
74 'td',
75 [ 'colspan' => '2', 'class' => 'diff-lineno' ],
76 '<!--LINE ' . $ybeg . '-->'
77 )
78 ) . "\n";
79 }
80
82 protected function startBlock( $header ) {
83 $this->writeOutput( $header );
84 }
85
87 protected function endBlock() {
88 }
89
95 protected function lines( $lines, $prefix = ' ', $color = 'white' ) {
96 }
97
105 protected function addedLine( $line ) {
106 return $this->wrapLine( '+', [ 'diff-addedline', $this->getClassForSide( self::SIDE_ADDED ) ], $line );
107 }
108
116 protected function deletedLine( $line ) {
117 return $this->wrapLine( '−', [ 'diff-deletedline', $this->getClassForSide( self::SIDE_DELETED ) ], $line );
118 }
119
128 protected function contextLine( $line, string $side ) {
129 return $this->wrapLine( '', [ 'diff-context', $this->getClassForSide( $side ) ], $line );
130 }
131
139 protected function wrapLine( $marker, $class, $line ) {
140 if ( $line !== '' ) {
141 // The <div> wrapper is needed for 'overflow: auto' style to scroll properly
142 $line = $this->rawElement( 'div', [], $this->escapeWhiteSpace( $line ) );
143 } else {
144 $line = '<br>';
145 }
146
147 $markerAttrs = [ 'class' => 'diff-marker' ];
148 if ( $marker ) {
149 $markerAttrs['data-marker'] = $marker;
150 }
151
152 if ( is_array( $class ) ) {
153 $class = implode( ' ', $class );
154 }
155
156 return $this->element( 'td', $markerAttrs ) .
157 $this->rawElement( 'td', [ 'class' => $class ], $line );
158 }
159
164 protected function emptyLine( string $side ) {
165 return $this->element( 'td', [ 'colspan' => '2', 'class' => $this->getClassForSide( $side ) ] );
166 }
167
173 protected function added( $lines ) {
174 foreach ( $lines as $line ) {
175 $this->writeOutput(
176 $this->rawElement(
177 'tr',
178 [],
179 $this->emptyLine( self::SIDE_DELETED ) .
180 $this->addedLine(
181 $this->element(
182 'ins',
183 [ 'class' => 'diffchange' ],
184 $line
185 )
186 )
187 ) .
188 "\n"
189 );
190 }
191 }
192
198 protected function deleted( $lines ) {
199 foreach ( $lines as $line ) {
200 $this->writeOutput(
201 $this->rawElement(
202 'tr',
203 [],
204 $this->deletedLine(
205 $this->element(
206 'del',
207 [ 'class' => 'diffchange' ],
208 $line
209 )
210 ) .
211 $this->emptyLine( self::SIDE_ADDED )
212 ) .
213 "\n"
214 );
215 }
216 }
217
223 protected function context( $lines ) {
224 foreach ( $lines as $line ) {
225 $this->writeOutput(
226 $this->rawElement(
227 'tr',
228 [],
229 $this->contextLine( htmlspecialchars( $line ), self::SIDE_DELETED ) .
230 $this->contextLine( htmlspecialchars( $line ), self::SIDE_ADDED )
231 ) .
232 "\n"
233 );
234 }
235 }
236
243 protected function changed( $orig, $closing ) {
244 $diff = new WordLevelDiff( $orig, $closing );
245 $del = $diff->orig();
246 $add = $diff->closing();
247
248 # Notice that WordLevelDiff returns HTML-escaped output.
249 # Hence, we will be calling addedLine/deletedLine without HTML-escaping.
250
251 $ndel = count( $del );
252 $nadd = count( $add );
253 $n = max( $ndel, $nadd );
254 for ( $i = 0; $i < $n; $i++ ) {
255 $delLine = $i < $ndel ? $this->deletedLine( $del[$i] ) : $this->emptyLine( self::SIDE_DELETED );
256 $addLine = $i < $nadd ? $this->addedLine( $add[$i] ) : $this->emptyLine( self::SIDE_ADDED );
257 $this->writeOutput(
258 $this->rawElement(
259 'tr',
260 [],
261 $delLine . $addLine
262 ) .
263 "\n"
264 );
265 }
266 }
267
275 private function getClassForSide( string $side ): string {
276 if ( !isset( self::SIDE_CLASSES[$side] ) ) {
277 throw new InvalidArgumentException( "Invalid diff side: $side" );
278 }
279 return self::SIDE_CLASSES[$side];
280 }
281
290 private function rawElement( $element, $attribs = [], $contents = '' ) {
291 $ret = "<$element";
292 foreach ( $attribs as $name => $value ) {
293 $ret .= " $name=\"" . htmlspecialchars( $value, ENT_QUOTES ) . '"';
294 }
295 $ret .= ">$contents</$element>";
296 return $ret;
297 }
298
307 private function element( $element, $attribs = [], $contents = '' ) {
308 return $this->rawElement( $element, $attribs, htmlspecialchars( $contents, ENT_NOQUOTES ) );
309 }
310}
311
313class_alias( TableDiffFormatter::class, 'TableDiffFormatter' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
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)