39 $this->mTitle = $titleObj;
40 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'\n");
42 if (
'prev' ===
$new ) {
43 # Show diff between revision $old and the previous one. 44 # Get previous one from DB. 46 $this->mNewid = intval(
$old);
48 $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid );
50 } elseif (
'next' ===
$new ) {
51 # Show diff between revision $old and the previous one. 52 # Get previous one from DB. 54 $this->mOldid = intval(
$old);
55 $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid );
56 if (
false === $this->mNewid ) {
57 # if no result, NewId points to the newest old revision. The only newer 58 # revision is cur, which is "0". 63 $this->mOldid = intval(
$old);
64 $this->mNewid = intval(
$new);
66 $this->mRcidMarkPatrolled = intval($rcid); # force it
to be an integer
70 global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
71 $fname =
'DifferenceEngine::showDiffPage';
74 # If external diffs are enabled both globally and for the user, 75 # we'll use the application/x-external-editor interface to call 76 # an external diff tool like kompare, kdiff3, etc. 77 if($wgUseExternalEditor && $wgUser->getOption(
'externaldiff')) {
78 global $wgInputEncoding,$wgServer,$wgScript,$wgLang;
80 header (
"Content-type: application/x-external-editor; charset=".$wgInputEncoding );
81 $url1=$this->mTitle->getFullURL(
"action=raw&oldid=".$this->mOldid);
82 $url2=$this->mTitle->getFullURL(
"action=raw&oldid=".$this->mNewid);
83 $special=$wgLang->getNsText(NS_SPECIAL);
88 Script={$wgServer}{$wgScript}
89 Special
namespace={$special}
103 $wgOut->setArticleFlag(
false );
105 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, {$this->mNewid})";
106 $mtext =
wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>" );
107 $wgOut->setPagetitle(
wfMsg(
'errorpagetitle' ) );
108 $wgOut->addWikitext( $mtext );
113 wfRunHooks(
'DiffViewHeader',
array( $this, $this->mOldRev, $this->mNewRev ) );
115 if ( $this->mNewRev->isCurrent() ) {
116 $wgOut->setArticleFlag(
true );
119 # mOldid is false if the difference engine is called with a "vague" query for 120 # a diff between a version V and its previous version V' AND the version V 121 # is the first version of that article. In that case, V' does not exist. 122 if ( $this->mOldid ===
false ) {
129 $wgOut->suppressQuickbar();
131 $oldTitle = $this->mOldPage->getPrefixedText();
132 $newTitle = $this->mNewPage->getPrefixedText();
133 if( $oldTitle == $newTitle ) {
134 $wgOut->setPageTitle( $newTitle );
136 $wgOut->setPageTitle( $oldTitle .
', ' . $newTitle );
138 $wgOut->setSubtitle(
wfMsg(
'difference' ) );
139 $wgOut->setRobotpolicy(
'noindex,nofollow' );
141 if ( !( $this->mOldPage->userCanRead() && $this->mNewPage->userCanRead() ) ) {
142 $wgOut->loginToUse();
148 $sk = $wgUser->getSkin();
150 if ( $this->mNewRev->isCurrent() && $wgUser->isAllowed(
'rollback') ) {
151 $rollback =
' ' . $sk->generateRollback( $this->mNewRev );
155 if( $wgUseRCPatrol && $this->mRcidMarkPatrolled != 0 && $wgUser->isAllowed(
'patrol' ) ) {
156 $patrol =
' [' . $sk->makeKnownLinkObj( $this->mTitle,
wfMsg(
'markaspatrolleddiff' ),
"action=markpatrolled&rcid={$this->mRcidMarkPatrolled}" ) .
']';
161 $prevlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml(
'previousdiff' ),
162 'diff=prev&oldid='.$this->mOldid,
'',
'',
'id="differences-prevlink"' );
163 if ( $this->mNewRev->isCurrent() ) {
164 $nextlink =
' ';
166 $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml(
'nextdiff' ),
167 'diff=next&oldid='.$this->mNewid,
'',
'',
'id="differences-nextlink"' );
173 if ($this->mOldRev->mMinorEdit == 1) {
174 $oldminor = wfElement(
'span',
array(
'class' =>
'minor' ),
175 wfMsg(
'minoreditletter') ) .
' ';
178 if ($this->mNewRev->mMinorEdit == 1) {
179 $newminor = wfElement(
'span',
array(
'class' =>
'minor' ),
180 wfMsg(
'minoreditletter') ) .
' ';
183 $oldHeader =
"<strong>{$this->mOldtitle}</strong><br />" .
184 $sk->revUserTools( $this->mOldRev ) .
"<br />" .
185 $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) .
"<br />" .
187 $newHeader =
"<strong>{$this->mNewtitle}</strong><br />" .
188 $sk->revUserTools( $this->mNewRev ) .
" $rollback<br />" .
189 $newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) .
"<br />" .
192 $this->
showDiff( $oldHeader, $newHeader );
205 $fname =
'DifferenceEngine::renderNewRevision';
208 $wgOut->addHTML(
"<hr /><h2>{$this->mPagetitle}</h2>\n" );
209 #add deleted rev tag if needed 210 if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
211 $wgOut->addWikiText(
wfMsg(
'rev-deleted-text-permission' ) );
214 if( !$this->mNewRev->isCurrent() ) {
215 $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection(
false );
219 if( is_object( $this->mNewRev ) ) {
220 $wgOut->setRevisionId( $this->mNewRev->getId() );
223 $wgOut->addWikiTextTidy( $this->mNewtext );
225 if( !$this->mNewRev->isCurrent() ) {
226 $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
237 global $wgOut, $wgUser;
239 $fname =
'DifferenceEngine::showFirstRevision';
242 # Get article text from the DB 245 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, " .
247 $mtext =
wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>" );
248 $wgOut->setPagetitle(
wfMsg(
'errorpagetitle' ) );
249 $wgOut->addWikitext( $mtext );
253 if ( $this->mNewRev->isCurrent() ) {
254 $wgOut->setArticleFlag(
true );
257 # Check if user is allowed to look at this page. If not, bail out. 259 if ( !( $this->mTitle->userCanRead() ) ) {
260 $wgOut->loginToUse();
266 # Prepare the header box 268 $sk = $wgUser->getSkin();
270 $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml(
'nextdiff' ),
'diff=next&oldid='.$this->mNewid,
'',
'',
'id="differences-nextlink"' );
271 $header =
"<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />" .
272 $sk->revUserTools( $this->mNewRev ) .
"<br />" .
273 $sk->revComment( $this->mNewRev ) .
"<br />" .
274 $nextlink .
"</div>\n";
278 $wgOut->setSubtitle(
wfMsg(
'difference' ) );
279 $wgOut->setRobotpolicy(
'noindex,nofollow' );
290 $diff = $this->
getDiff( $otitle, $ntitle );
291 if ( $diff ===
false ) {
292 $wgOut->addWikitext(
wfMsg(
'missingarticle',
"<nowiki>(fixme, bug)</nowiki>" ) );
295 $wgOut->addHTML( $diff );
307 if ( $body ===
false ) {
311 return $this->
addHeader( $body, $otitle, $ntitle, $multi );
322 $fname =
'DifferenceEngine::getDiffBody';
327 if ( $this->mOldid && $this->mNewid ) {
329 $key = wfMemcKey(
'diff',
'oldid', $this->mOldid,
'newid', $this->mNewid );
330 $difftext = $wgMemc->get( $key );
332 wfIncrStats(
'diff_cache_hit' );
334 $difftext .=
"\n<!-- diff cache key $key -->\n";
340 #loadtext is permission safe, this just clears out the diff 344 }
else if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
346 }
else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
353 if ( $key !==
false && $difftext !==
false ) {
354 wfIncrStats(
'diff_cache_miss' );
355 $wgMemc->set( $key, $difftext, 7*86400 );
357 wfIncrStats(
'diff_uncacheable' );
360 if ( $difftext !==
false ) {
372 global $wgExternalDiffEngine, $wgContLang;
373 $fname =
'DifferenceEngine::generateDiffBody';
375 $otext = str_replace(
"\r\n",
"\n", $otext );
376 $ntext = str_replace(
"\r\n",
"\n", $ntext );
378 if ( $wgExternalDiffEngine ==
'wikidiff' ) {
379 # For historical reasons, external diff engine expects 380 # input text to be HTML-escaped already 381 $otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) );
382 $ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) );
383 if( !function_exists(
'wikidiff_do_diff' ) ) {
384 dl(
'php_wikidiff.so');
386 return $wgContLang->unsegementForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) );
389 if ( $wgExternalDiffEngine ==
'wikidiff2' ) {
390 # Better external diff engine, the 2 may some day be dropped 391 # This one does the escaping and segmenting itself 392 if ( !function_exists(
'wikidiff2_do_diff' ) ) {
394 @dl(
'php_wikidiff2.so');
397 if ( function_exists(
'wikidiff2_do_diff' ) ) {
399 $text = wikidiff2_do_diff( $otext, $ntext, 2 );
404 if ( $wgExternalDiffEngine !==
false ) {
406 global $wgTmpDirectory;
407 $tempName1 = tempnam( $wgTmpDirectory,
'diff_' );
408 $tempName2 = tempnam( $wgTmpDirectory,
'diff_' );
410 $tempFile1 = fopen( $tempName1,
"w" );
415 $tempFile2 = fopen( $tempName2,
"w" );
420 fwrite( $tempFile1, $otext );
421 fwrite( $tempFile2, $ntext );
422 fclose( $tempFile1 );
423 fclose( $tempFile2 );
424 $cmd = wfEscapeShellArg( $wgExternalDiffEngine, $tempName1, $tempName2 );
426 $difftext = wfShellExec(
$cmd );
428 unlink( $tempName1 );
429 unlink( $tempName2 );
434 $ota = explode(
"\n", $wgContLang->segmentForDiff( $otext ) );
435 $nta = explode(
"\n", $wgContLang->segmentForDiff( $ntext ) );
436 $diffs =
new Diff( $ota, $nta );
438 return $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) );
446 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
447 array( &$this,
'localiseLineNumbersCb' ),
$text );
452 return wfMsgExt(
'lineno',
array(
'parseinline'), $wgLang->formatNum( $matches[1] ) );
460 if ( !is_object($this->mOldRev) || !is_object($this->mNewRev) )
463 if( !$this->mOldPage->equals( $this->mNewPage ) ) {
468 $oldid = $this->mOldRev->getId();
469 $newid = $this->mNewRev->getId();
470 if ( $oldid > $newid ) {
471 $tmp = $oldid; $oldid = $newid; $newid = $tmp;
474 $n = $this->mTitle->countRevisionsBetween( $oldid, $newid );
478 return wfMsgExt(
'diff-multi',
array(
'parseinline' ),
$n );
485 function addHeader( $diff, $otitle, $ntitle, $multi =
'' ) {
488 if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
489 $otitle =
'<span class="history-deleted">'.$otitle.
'</span>';
491 if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
492 $ntitle =
'<span class="history-deleted">'.$ntitle.
'</span>';
495 <table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'> 497 <td colspan='2' width='50%' align='center' class='diff-otitle'>{$otitle}</td> 498 <td colspan='2' width='50%' align='center' class='diff-ntitle'>{$ntitle}</td> 503 $header .=
"<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>";
505 return $header . $diff .
"</table>";
512 $this->mOldtext = $oldText;
513 $this->mNewtext = $newText;
514 $this->mTextLoaded = 2;
529 if ( $this->mRevisionsLoaded ) {
533 $this->mRevisionsLoaded =
true;
537 if( $this->mNewid ) {
538 $this->mNewRev = Revision::newFromId( $this->mNewid );
540 $this->mNewRev = Revision::newFromTitle( $this->mTitle );
543 if( is_null( $this->mNewRev ) ) {
548 $timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true );
549 $this->mNewPage = $this->mNewRev->getTitle();
550 if( $this->mNewRev->isCurrent() ) {
551 $newLink = $this->mNewPage->escapeLocalUrl();
552 $this->mPagetitle = htmlspecialchars(
wfMsg(
'currentrev' ) );
553 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit' );
555 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)" 556 .
" (<a href='$newEdit'>" . htmlspecialchars(
wfMsg(
'editold' ) ) .
"</a>)";
559 $newLink = $this->mNewPage->escapeLocalUrl(
'oldid=' . $this->mNewid );
560 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mNewid );
561 $this->mPagetitle = htmlspecialchars(
wfMsg(
'revisionasof',
$timestamp ) );
563 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a>" 564 .
" (<a href='$newEdit'>" . htmlspecialchars(
wfMsg(
'editold' ) ) .
"</a>)";
568 $this->mOldRev =
false;
569 if( $this->mOldid ) {
570 $this->mOldRev = Revision::newFromId( $this->mOldid );
571 } elseif ( $this->mOldid === 0 ) {
572 $rev = $this->mNewRev->getPrevious();
574 $this->mOldid = $rev->getId();
575 $this->mOldRev = $rev;
578 $this->mOldid =
false;
579 $this->mOldRev =
false;
583 if( is_null( $this->mOldRev ) ) {
587 if ( $this->mOldRev ) {
588 $this->mOldPage = $this->mOldRev->getTitle();
590 $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
591 $oldLink = $this->mOldPage->escapeLocalUrl(
'oldid=' . $this->mOldid );
592 $oldEdit = $this->mOldPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mOldid );
593 $this->mOldtitle =
"<a href='$oldLink'>" . htmlspecialchars(
wfMsg(
'revisionasof',
$t ) )
594 .
"</a> (<a href='$oldEdit'>" . htmlspecialchars(
wfMsg(
'editold' ) ) .
"</a>)";
596 $newUndo = $this->mNewPage->escapeLocalUrl(
'action=edit&undoafter=' . $this->mOldid .
'&undo=' . $this->mNewid);
597 $this->mNewtitle .=
" (<a href='$newUndo'>" . htmlspecialchars(
wfMsg(
'editundo' ) ) .
"</a>)";
607 if ( $this->mTextLoaded == 2 ) {
611 $this->mTextLoaded = 2;
617 if ( $this->mOldRev ) {
619 $this->mOldtext = $this->mOldRev->revText();
620 if ( $this->mOldtext ===
false ) {
624 if ( $this->mNewRev ) {
625 $this->mNewtext = $this->mNewRev->revText();
626 if ( $this->mNewtext ===
false ) {
637 if ( $this->mTextLoaded >= 1 ) {
640 $this->mTextLoaded = 1;
645 $this->mNewtext = $this->mNewRev->getText();
658 define(
'USE_ASSERTS', function_exists(
'assert'));
671 trigger_error(
'pure virtual', E_USER_ERROR);
678 function nclosing() {
679 return $this->closing ?
sizeof($this->closing) : 0;
692 if (!is_array($closing))
695 $this->closing = $closing;
709 var $type =
'delete';
712 $this->orig = $lines;
713 $this->closing =
false;
730 $this->closing = $lines;
745 var $type =
'change';
749 $this->closing = $closing;
783 const MAX_XREF_LENGTH = 10000;
785 function diff ($from_lines, $to_lines) {
786 $fname =
'_DiffEngine::diff';
789 $n_from =
sizeof($from_lines);
790 $n_to =
sizeof($to_lines);
792 $this->xchanged = $this->ychanged =
array();
793 $this->xv = $this->yv =
array();
794 $this->xind = $this->yind =
array();
796 unset($this->in_seq);
800 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
801 if ($from_lines[$skip] !== $to_lines[$skip])
803 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
806 $xi = $n_from; $yi = $n_to;
807 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
808 if ($from_lines[$xi] !== $to_lines[$yi])
810 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
814 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
815 $xhash[$this->_line_hash($from_lines[$xi])] = 1;
818 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
819 $line = $to_lines[$yi];
820 if ( ($this->ychanged[$yi] = empty($xhash[$this->_line_hash($line)])) )
822 $yhash[$this->_line_hash($line)] = 1;
826 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
827 $line = $from_lines[$xi];
828 if ( ($this->xchanged[$xi] = empty($yhash[$this->_line_hash($line)])) )
835 $this->_compareseq(0,
sizeof($this->xv), 0,
sizeof($this->yv));
838 $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
839 $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
844 while ($xi < $n_from || $yi < $n_to) {
845 USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
846 USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
850 while ( $xi < $n_from && $yi < $n_to
851 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
852 $copy[] = $from_lines[$xi++];
860 while ($xi < $n_from && $this->xchanged[$xi])
861 $delete[] = $from_lines[$xi++];
864 while ($yi < $n_to && $this->ychanged[$yi])
865 $add[] = $to_lines[$yi++];
881 function _line_hash( $line ) {
882 if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
906 function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
907 $fname =
'_DiffEngine::_diag';
911 if ($xlim - $xoff > $ylim - $yoff) {
915 list ($xoff, $xlim, $yoff, $ylim)
916 =
array( $yoff, $ylim, $xoff, $xlim);
920 for ($i = $ylim - 1; $i >= $yoff; $i--)
921 $ymatches[$this->xv[$i]][] = $i;
923 for ($i = $ylim - 1; $i >= $yoff; $i--)
924 $ymatches[$this->yv[$i]][] = $i;
927 $this->seq[0]= $yoff - 1;
928 $this->in_seq =
array();
931 $numer = $xlim - $xoff + $nchunks - 1;
933 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
936 for ($i = 0; $i <= $this->lcs; $i++)
937 $ymids[$i][$chunk-1] = $this->seq[$i];
939 $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
940 for ( ;
$x < $x1;
$x++) {
941 $line = $flip ? $this->yv[
$x] : $this->xv[
$x];
942 if (empty($ymatches[$line]))
944 $matches = $ymatches[$line];
946 while (list ($junk,
$y) = each($matches))
947 if (empty($this->in_seq[
$y])) {
948 $k = $this->_lcs_pos($y);
950 $ymids[$k] = $ymids[$k-1];
953 while (list ( , $y) = each($matches)) {
954 if ($y > $this->seq[$k-1]) {
958 $this->in_seq[$this->seq[$k]] =
false;
960 $this->in_seq[
$y] = 1;
961 }
else if (empty($this->in_seq[$y])) {
962 $k = $this->_lcs_pos($y);
964 $ymids[$k] = $ymids[$k-1];
971 $seps[] = $flip ?
array($yoff, $xoff) : array($xoff, $yoff);
972 $ymid = $ymids[$this->lcs];
973 for (
$n = 0;
$n < $nchunks - 1;
$n++) {
974 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) *
$n) / $nchunks);
976 $seps[] = $flip ?
array($y1, $x1) :
array($x1, $y1);
978 $seps[] = $flip ?
array($ylim, $xlim) :
array($xlim, $ylim);
981 return array($this->lcs, $seps);
984 function _lcs_pos ($ypos) {
985 $fname =
'_DiffEngine::_lcs_pos';
989 if ($end == 0 || $ypos > $this->seq[$end]) {
990 $this->seq[++$this->lcs] = $ypos;
991 $this->in_seq[$ypos] = 1;
997 while ($beg < $end) {
998 $mid = (int)(($beg + $end) / 2);
999 if ( $ypos > $this->seq[$mid] )
1007 $this->in_seq[$this->seq[$end]] =
false;
1008 $this->seq[$end] = $ypos;
1009 $this->in_seq[$ypos] = 1;
1025 function _compareseq ($xoff, $xlim, $yoff, $ylim) {
1026 $fname =
'_DiffEngine::_compareseq';
1030 while ($xoff < $xlim && $yoff < $ylim
1031 && $this->xv[$xoff] == $this->yv[$yoff]) {
1037 while ($xlim > $xoff && $ylim > $yoff
1038 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
1043 if ($xoff == $xlim || $yoff == $ylim)
1049 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
1051 = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
1057 while ($yoff < $ylim)
1058 $this->ychanged[$this->yind[$yoff++]] = 1;
1059 while ($xoff < $xlim)
1060 $this->xchanged[$this->xind[$xoff++]] = 1;
1065 while ($pt2 = next($seps)) {
1066 $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
1085 function _shift_boundaries ($lines, &
$changed, $other_changed) {
1086 $fname =
'_DiffEngine::_shift_boundaries';
1091 USE_ASSERTS && assert(
'sizeof($lines) == sizeof($changed)');
1092 $len =
sizeof($lines);
1093 $other_len =
sizeof($other_changed);
1107 while ($j < $other_len && $other_changed[$j])
1110 while ($i < $len && !
$changed[$i]) {
1111 USE_ASSERTS && assert(
'$j < $other_len && ! $other_changed[$j]');
1113 while ($j < $other_len && $other_changed[$j])
1123 while (++$i < $len &&
$changed[$i])
1131 $runlength = $i -
$start;
1138 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
1141 while ($start > 0 &&
$changed[$start - 1])
1144 while ($other_changed[--$j])
1146 USE_ASSERTS && assert(
'$j >= 0 && !$other_changed[$j]');
1154 $corresponding = $j < $other_len ? $i : $len;
1163 while ($i < $len && $lines[$start] == $lines[$i]) {
1169 USE_ASSERTS && assert(
'$j < $other_len && ! $other_changed[$j]');
1171 if ($j < $other_len && $other_changed[$j]) {
1172 $corresponding = $i;
1173 while ($j < $other_len && $other_changed[$j])
1177 }
while ($runlength != $i -
$start);
1183 while ($corresponding < $i) {
1187 while ($other_changed[--$j])
1189 USE_ASSERTS && assert(
'$j >= 0 && !$other_changed[$j]');
1216 $this->edits = $eng->
diff($from_lines, $to_lines);
1230 function reverse () {
1232 $rev->edits =
array();
1233 foreach ($this->edits as $edit) {
1234 $rev->edits[] = $edit->reverse();
1244 function isEmpty () {
1245 foreach ($this->edits as $edit) {
1246 if ($edit->type !=
'copy')
1261 foreach ($this->edits as $edit) {
1262 if ($edit->type ==
'copy')
1263 $lcs +=
sizeof($edit->orig);
1279 foreach ($this->edits as $edit) {
1281 array_splice($lines,
sizeof($lines), 0, $edit->orig);
1294 function closing() {
1297 foreach ($this->edits as $edit) {
1299 array_splice($lines,
sizeof($lines), 0, $edit->closing);
1309 function _check ($from_lines, $to_lines) {
1310 $fname =
'Diff::_check';
1312 if (serialize($from_lines) != serialize($this->orig()))
1313 trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
1314 if (serialize($to_lines) != serialize($this->closing()))
1315 trigger_error(
"Reconstructed closing doesn't match", E_USER_ERROR);
1317 $rev = $this->reverse();
1318 if (serialize($to_lines) != serialize($rev->orig()))
1319 trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
1320 if (serialize($from_lines) != serialize($rev->closing()))
1321 trigger_error(
"Reversed closing doesn't match", E_USER_ERROR);
1325 foreach ($this->edits as $edit) {
1326 if ( $prevtype == $edit->type )
1327 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
1328 $prevtype = $edit->type;
1331 $lcs = $this->lcs();
1332 trigger_error(
'Diff okay: LCS = '.$lcs, E_USER_NOTICE);
1368 $mapped_from_lines, $mapped_to_lines) {
1369 $fname =
'MappedDiff::MappedDiff';
1372 assert(
sizeof($from_lines) ==
sizeof($mapped_from_lines));
1373 assert(
sizeof($to_lines) ==
sizeof($mapped_to_lines));
1375 parent::__construct($mapped_from_lines, $mapped_to_lines);
1378 for ($i = 0; $i <
sizeof($this->edits); $i++) {
1379 $orig = &$this->edits[$i]->orig;
1380 if (is_array(
$orig)) {
1381 $orig = array_slice($from_lines, $xi,
sizeof(
$orig));
1382 $xi +=
sizeof(
$orig);
1385 $closing = &$this->edits[$i]->closing;
1386 if (is_array($closing)) {
1387 $closing = array_slice($to_lines, $yi,
sizeof($closing));
1388 $yi +=
sizeof($closing);
1413 var $leading_context_lines = 0;
1421 var $trailing_context_lines = 0;
1430 $fname =
'DiffFormatter::format';
1437 $nlead = $this->leading_context_lines;
1438 $ntrail = $this->trailing_context_lines;
1440 $this->_start_diff();
1442 foreach ($diff->edits as $edit) {
1443 if ($edit->type ==
'copy') {
1444 if (is_array($block)) {
1445 if (
sizeof($edit->orig) <= $nlead + $ntrail) {
1450 $context = array_slice($edit->orig, 0, $ntrail);
1453 $this->_block($x0, $ntrail + $xi - $x0,
1454 $y0, $ntrail + $yi - $y0,
1459 $context = $edit->orig;
1462 if (! is_array($block)) {
1463 $context = array_slice($context,
sizeof($context) - $nlead);
1464 $x0 = $xi -
sizeof($context);
1465 $y0 = $yi -
sizeof($context);
1474 $xi +=
sizeof($edit->orig);
1476 $yi +=
sizeof($edit->closing);
1479 if (is_array($block))
1480 $this->_block($x0, $xi - $x0,
1484 $end = $this->_end_diff();
1489 function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
1490 $fname =
'DiffFormatter::_block';
1492 $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
1493 foreach ($edits as $edit) {
1494 if ($edit->type ==
'copy')
1495 $this->_context($edit->orig);
1496 elseif ($edit->type ==
'add')
1497 $this->_added($edit->closing);
1498 elseif ($edit->type == 'delete')
1499 $this->_deleted($edit->orig);
1500 elseif ($edit->type == 'change')
1501 $this->_changed($edit->orig, $edit->closing);
1503 trigger_error('Unknown edit type', E_USER_ERROR);
1505 $this->_end_block();
1509 function _start_diff() {
1513 function _end_diff() {
1514 $val = ob_get_contents();
1519 function _block_header($xbeg, $xlen, $ybeg, $ylen) {
1521 $xbeg .=
"," . ($xbeg + $xlen - 1);
1523 $ybeg .=
"," . ($ybeg + $ylen - 1);
1525 return $xbeg . ($xlen ? ($ylen ?
'c' :
'd') :
'a') . $ybeg;
1528 function _start_block(
$header) {
1532 function _end_block() {
1535 function _lines($lines, $prefix =
' ') {
1536 foreach ($lines as $line)
1540 function _context($lines) {
1541 $this->_lines($lines);
1544 function _added($lines) {
1545 $this->_lines($lines,
'>');
1547 function _deleted($lines) {
1548 $this->_lines($lines,
'<');
1551 function _changed(
$orig, $closing) {
1552 $this->_deleted(
$orig);
1554 $this->_added($closing);
1564 define(
'NBSP',
' ');
1573 $this->_lines =
array();
1579 function _flushGroup ($new_tag) {
1580 if ($this->_group !==
'') {
1581 if ($this->_tag ==
'ins')
1582 $this->_line .=
'[ilDiffInsStart]' .
1583 htmlspecialchars ( $this->_group ) .
'[ilDiffInsEnd]';
1584 elseif ($this->_tag ==
'del')
1585 $this->_line .=
'[ilDiffDelStart]' .
1586 htmlspecialchars ( $this->_group ) .
'[ilDiffDelEnd]';
1588 $this->_line .= htmlspecialchars ( $this->_group );
1591 $this->_tag = $new_tag;
1594 function _flushLine ($new_tag) {
1595 $this->_flushGroup($new_tag);
1596 if ($this->_line !=
'')
1597 array_push ( $this->_lines, $this->_line );
1599 # make empty lines visible by inserting an NBSP 1600 array_push ( $this->_lines,
NBSP );
1604 function addWords ($words, $tag =
'') {
1605 if ($tag != $this->_tag)
1606 $this->_flushGroup($tag);
1608 foreach ($words as $word) {
1612 if ($word[0] ==
"\n") {
1613 $this->_flushLine($tag);
1614 $word = substr($word, 1);
1616 assert(!strstr($word,
"\n"));
1617 $this->_group .= $word;
1621 function getLines() {
1622 $this->_flushLine(
'~done');
1623 return $this->_lines;
1634 const MAX_LINE_LENGTH = 10000;
1636 function __construct ($orig_lines, $closing_lines) {
1637 $fname =
'WordLevelDiff::WordLevelDiff';
1640 list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
1641 list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
1643 parent::__construct($orig_words, $closing_words,
1644 $orig_stripped, $closing_stripped);
1648 function _split($lines) {
1649 $fname =
'WordLevelDiff::_split';
1653 $stripped =
array();
1655 foreach ( $lines as $line ) {
1656 # If the line is too long, just pretend the entire line is one big word 1657 # This prevents resource exhaustion problems 1664 if ( strlen( $line ) > self::MAX_LINE_LENGTH ) {
1666 $stripped[] = $line;
1669 if (preg_match_all(
'/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1672 $words = array_merge( $words, $m[0] );
1673 $stripped = array_merge( $stripped, $m[1] );
1678 return array($words, $stripped);
1682 $fname =
'WordLevelDiff::orig';
1686 foreach ($this->edits as $edit) {
1687 if ($edit->type ==
'copy')
1688 $orig->addWords($edit->orig);
1689 elseif ($edit->orig)
1690 $orig->addWords($edit->orig,
'del');
1692 $lines =
$orig->getLines();
1697 function closing () {
1698 $fname =
'WordLevelDiff::closing';
1702 foreach ($this->edits as $edit) {
1703 if ($edit->type ==
'copy')
1704 $closing->
addWords($edit->closing);
1705 elseif ($edit->closing)
1706 $closing->addWords($edit->closing, 'ins');
1708 $lines = $closing->getLines();
1723 $this->leading_context_lines = 2;
1724 $this->trailing_context_lines = 2;
1727 function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
1728 $r =
'<tr><td colspan="2" align="left"><strong><!--LINE '.$xbeg.
"--></strong></td>\n" .
1729 '<td colspan="2" align="left"><strong><!--LINE '.$ybeg.
"--></strong></td></tr>\n";
1733 function _start_block(
$header ) {
1737 function _end_block() {
1740 function _lines( $lines, $prefix=
' ', $color=
'white' ) {
1743 # HTML-escape parameter before calling this 1744 function addedLine( $line ) {
1745 return "<td>+</td><td class='diff-addedline'>{$line}</td>";
1748 # HTML-escape parameter before calling this 1749 function deletedLine( $line ) {
1750 return "<td>-</td><td class='diff-deletedline'>{$line}</td>";
1753 # HTML-escape parameter before calling this 1754 function contextLine( $line ) {
1755 return "<td> </td><td class='diff-context'>{$line}</td>";
1758 function emptyLine() {
1759 return '<td colspan="2"> </td>';
1762 function _added( $lines ) {
1763 foreach ($lines as $line) {
1764 echo '<tr>' . $this->emptyLine() .
1765 $this->addedLine( htmlspecialchars ( $line ) ) .
"</tr>\n";
1769 function _deleted($lines) {
1770 foreach ($lines as $line) {
1771 echo '<tr>' . $this->deletedLine( htmlspecialchars ( $line ) ) .
1772 $this->emptyLine() .
"</tr>\n";
1776 function _context( $lines ) {
1777 foreach ($lines as $line) {
1779 $this->contextLine( htmlspecialchars ( $line ) ) .
1780 $this->contextLine( htmlspecialchars ( $line ) ) .
"</tr>\n";
1784 function _changed(
$orig, $closing ) {
1785 $fname =
'TableDiffFormatter::_changed';
1789 $del = $diff->orig();
1790 $add = $diff->closing();
1792 # Notice that WordLevelDiff returns HTML-escaped output. 1793 # Hence, we will be calling addedLine/deletedLine without HTML-escaping. 1795 while ( $line = array_shift( $del ) ) {
1796 $aline = array_shift( $add );
1797 echo '<tr>' . $this->deletedLine( $line ) .
1798 $this->addedLine( $aline ) .
"</tr>\n";
1800 foreach ($add as $line) { # If any leftovers
1801 echo '<tr>' . $this->emptyLine() .
1802 $this->addedLine( $line ) .
"</tr>\n";
getDiffBody()
Get the diff table body, without header Results are cached Returns false on error.
diff($from_lines, $to_lines)
generateDiffBody( $otext, $ntext)
Generate a diff, no caching $otext and $ntext must be already segmented.
setText( $oldText, $newText)
Use specified text instead of loading from the database.
showDiffPage( $diffOnly=false)
getDiff( $otitle, $ntitle)
Get diff table, including header Note that the interface has changed, it's no longer static...
addWords($words, $tag='')
localiseLineNumbers( $text)
Replace line numbers with the text in the user's language.
renderNewRevision()
Show the new revision of the page.
addHeader( $diff, $otitle, $ntitle, $multi='')
Add the header to a diff body.
getMultiNotice()
If there are revisions between the ones being compared, return a note saying so.
Add a drawing to the header
Create styles array
The data for the language used.
foreach($mandatory_scripts as $file) $timestamp
showDiff( $otitle, $ntitle)
Get the diff text, send it to $wgOut Returns false if the diff could not be generated, otherwise returns true.
Write to Excel2007 format
__construct( $titleObj=null, $old=0, $new=0, $rcid=0)
#-
loadNewText()
Load the text of the new revision, not the old one.
showFirstRevision()
Show the first revision of an article.
const NBSP
Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3.
loadRevisionData()
Load revision metadata for the specified articles.
localiseLineNumbersCb( $matches)
loadText()
Load the text of the revisions, as well as revision data.