46 public function __construct($titleObj = null, $old = 0, $new = 0, $rcid = 0)
48 $this->mTitle = $titleObj;
49 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'\n");
51 if (
'prev' === $new) {
52 # Show diff between revision $old and the previous one. 53 # Get previous one from DB. 55 $this->mNewid = intval($old);
57 $this->mOldid = $this->mTitle->getPreviousRevisionID($this->mNewid);
58 } elseif (
'next' === $new) {
59 # Show diff between revision $old and the previous one. 60 # Get previous one from DB. 62 $this->mOldid = intval($old);
63 $this->mNewid = $this->mTitle->getNextRevisionID($this->mOldid);
64 if (
false === $this->mNewid) {
65 # if no result, NewId points to the newest old revision. The only newer 66 # revision is cur, which is "0". 70 $this->mOldid = intval($old);
71 $this->mNewid = intval($new);
73 $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer
78 global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
79 $fname =
'DifferenceEngine::showDiffPage';
82 # If external diffs are enabled both globally and for the user, 83 # we'll use the application/x-external-editor interface to call 84 # an external diff tool like kompare, kdiff3, etc. 85 if ($wgUseExternalEditor && $wgUser->getOption(
'externaldiff')) {
86 global $wgInputEncoding,$wgServer,$wgScript,$wgLang;
88 header(
"Content-type: application/x-external-editor; charset=" . $wgInputEncoding);
89 $url1 = $this->mTitle->getFullURL(
"action=raw&oldid=" . $this->mOldid);
90 $url2 = $this->mTitle->getFullURL(
"action=raw&oldid=" . $this->mNewid);
96 Script={$wgServer}{$wgScript}
97 Special
namespace={$special}
111 $wgOut->setArticleFlag(
false);
113 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, {$this->mNewid})";
114 $mtext =
wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>");
115 $wgOut->setPagetitle(
wfMsg(
'errorpagetitle'));
116 $wgOut->addWikitext($mtext);
121 wfRunHooks(
'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ));
123 if ($this->mNewRev->isCurrent()) {
124 $wgOut->setArticleFlag(
true);
127 # mOldid is false if the difference engine is called with a "vague" query for 128 # a diff between a version V and its previous version V' AND the version V 129 # is the first version of that article. In that case, V' does not exist. 130 if ($this->mOldid ===
false) {
137 $wgOut->suppressQuickbar();
139 $oldTitle = $this->mOldPage->getPrefixedText();
140 $newTitle = $this->mNewPage->getPrefixedText();
141 if ($oldTitle == $newTitle) {
142 $wgOut->setPageTitle($newTitle);
144 $wgOut->setPageTitle($oldTitle .
', ' . $newTitle);
146 $wgOut->setSubtitle(
wfMsg(
'difference'));
147 $wgOut->setRobotpolicy(
'noindex,nofollow');
149 if (!($this->mOldPage->userCanRead() && $this->mNewPage->userCanRead())) {
150 $wgOut->loginToUse();
156 $sk = $wgUser->getSkin();
158 if ($this->mNewRev->isCurrent() && $wgUser->isAllowed(
'rollback')) {
159 $rollback =
' ' . $sk->generateRollback($this->mNewRev);
163 if ($wgUseRCPatrol && $this->mRcidMarkPatrolled != 0 && $wgUser->isAllowed(
'patrol')) {
164 $patrol =
' [' . $sk->makeKnownLinkObj($this->mTitle,
wfMsg(
'markaspatrolleddiff'),
"action=markpatrolled&rcid={$this->mRcidMarkPatrolled}") .
']';
169 $prevlink = $sk->makeKnownLinkObj(
171 wfMsgHtml(
'previousdiff'),
172 'diff=prev&oldid=' . $this->mOldid,
175 'id="differences-prevlink"' 177 if ($this->mNewRev->isCurrent()) {
178 $nextlink =
' ';
180 $nextlink = $sk->makeKnownLinkObj(
182 wfMsgHtml(
'nextdiff'),
183 'diff=next&oldid=' . $this->mNewid,
186 'id="differences-nextlink"' 193 if ($this->mOldRev->mMinorEdit == 1) {
194 $oldminor = wfElement(
196 array(
'class' =>
'minor' ),
197 wfMsg(
'minoreditletter')
201 if ($this->mNewRev->mMinorEdit == 1) {
202 $newminor = wfElement(
204 array(
'class' =>
'minor' ),
205 wfMsg(
'minoreditletter')
209 $oldHeader =
"<strong>{$this->mOldtitle}</strong><br />" .
210 $sk->revUserTools($this->mOldRev) .
"<br />" .
211 $oldminor . $sk->revComment($this->mOldRev, !$diffOnly) .
"<br />" .
213 $newHeader =
"<strong>{$this->mNewtitle}</strong><br />" .
214 $sk->revUserTools($this->mNewRev) .
" $rollback<br />" .
215 $newminor . $sk->revComment($this->mNewRev, !$diffOnly) .
"<br />" .
218 $this->
showDiff($oldHeader, $newHeader);
233 $fname =
'DifferenceEngine::renderNewRevision';
236 $wgOut->addHTML(
"<hr /><h2>{$this->mPagetitle}</h2>\n");
237 #add deleted rev tag if needed 238 if (!$this->mNewRev->userCan(Revision::DELETED_TEXT)) {
239 $wgOut->addWikiText(
wfMsg(
'rev-deleted-text-permission'));
242 if (!$this->mNewRev->isCurrent()) {
243 $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection(
false);
247 if (is_object($this->mNewRev)) {
248 $wgOut->setRevisionId($this->mNewRev->getId());
251 $wgOut->addWikiTextTidy($this->mNewtext);
253 if (!$this->mNewRev->isCurrent()) {
254 $wgOut->parserOptions()->setEditSection($oldEditSectionSetting);
266 global $wgOut, $wgUser;
268 $fname =
'DifferenceEngine::showFirstRevision';
271 # Get article text from the DB 274 $t = $this->mTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, " .
276 $mtext =
wfMsg(
'missingarticle',
"<nowiki>$t</nowiki>");
277 $wgOut->setPagetitle(
wfMsg(
'errorpagetitle'));
278 $wgOut->addWikitext($mtext);
282 if ($this->mNewRev->isCurrent()) {
283 $wgOut->setArticleFlag(
true);
286 # Check if user is allowed to look at this page. If not, bail out. 288 if (!($this->mTitle->userCanRead())) {
289 $wgOut->loginToUse();
295 # Prepare the header box 297 $sk = $wgUser->getSkin();
299 $nextlink = $sk->makeKnownLinkObj($this->mTitle, wfMsgHtml(
'nextdiff'),
'diff=next&oldid=' . $this->mNewid,
'',
'',
'id="differences-nextlink"');
300 $header =
"<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />" .
301 $sk->revUserTools($this->mNewRev) .
"<br />" .
302 $sk->revComment($this->mNewRev) .
"<br />" .
303 $nextlink .
"</div>\n";
307 $wgOut->setSubtitle(
wfMsg(
'difference'));
308 $wgOut->setRobotpolicy(
'noindex,nofollow');
320 $diff = $this->
getDiff($otitle, $ntitle);
321 if ($diff ===
false) {
322 $wgOut->addWikitext(
wfMsg(
'missingarticle',
"<nowiki>(fixme, bug)</nowiki>"));
325 $wgOut->addHTML($diff);
338 if ($body ===
false) {
342 return $this->
addHeader($body, $otitle, $ntitle, $multi);
354 $fname =
'DifferenceEngine::getDiffBody';
359 if ($this->mOldid && $this->mNewid) {
361 $key = wfMemcKey(
'diff',
'oldid', $this->mOldid,
'newid', $this->mNewid);
362 $difftext = $wgMemc->get(
$key);
364 wfIncrStats(
'diff_cache_hit');
366 $difftext .=
"\n<!-- diff cache key $key -->\n";
372 #loadtext is permission safe, this just clears out the diff 376 } elseif ($this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT)) {
378 } elseif ($this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT)) {
385 if (
$key !==
false && $difftext !==
false) {
386 wfIncrStats(
'diff_cache_miss');
387 $wgMemc->set(
$key, $difftext, 7 * 86400);
389 wfIncrStats(
'diff_uncacheable');
392 if ($difftext !==
false) {
405 global $wgExternalDiffEngine, $wgContLang;
406 $fname =
'DifferenceEngine::generateDiffBody';
408 $otext = str_replace(
"\r\n",
"\n", $otext);
409 $ntext = str_replace(
"\r\n",
"\n", $ntext);
411 if ($wgExternalDiffEngine ==
'wikidiff') {
412 # For historical reasons, external diff engine expects 413 # input text to be HTML-escaped already 414 $otext = htmlspecialchars($wgContLang->segmentForDiff($otext));
415 $ntext = htmlspecialchars($wgContLang->segmentForDiff($ntext));
416 if (!function_exists(
'wikidiff_do_diff')) {
417 dl(
'php_wikidiff.so');
419 return $wgContLang->unsegementForDiff(wikidiff_do_diff($otext, $ntext, 2));
422 if ($wgExternalDiffEngine ==
'wikidiff2') {
423 # Better external diff engine, the 2 may some day be dropped 424 # This one does the escaping and segmenting itself 425 if (!function_exists(
'wikidiff2_do_diff')) {
427 @dl(
'php_wikidiff2.so');
430 if (function_exists(
'wikidiff2_do_diff')) {
432 $text = wikidiff2_do_diff($otext, $ntext, 2);
437 if ($wgExternalDiffEngine !==
false) {
439 global $wgTmpDirectory;
440 $tempName1 = tempnam($wgTmpDirectory,
'diff_');
441 $tempName2 = tempnam($wgTmpDirectory,
'diff_');
443 $tempFile1 =
fopen($tempName1,
"w");
448 $tempFile2 =
fopen($tempName2,
"w");
453 fwrite($tempFile1, $otext);
454 fwrite($tempFile2, $ntext);
457 $cmd = wfEscapeShellArg($wgExternalDiffEngine, $tempName1, $tempName2);
459 $difftext = wfShellExec($cmd);
467 $ota = explode(
"\n", $wgContLang->segmentForDiff($otext));
468 $nta = explode(
"\n", $wgContLang->segmentForDiff($ntext));
469 $diffs =
new Diff($ota, $nta);
471 return $wgContLang->unsegmentForDiff($formatter->format($diffs));
480 return preg_replace_callback(
481 '/<!--LINE (\d+)-->/',
482 array( &$this,
'localiseLineNumbersCb' ),
490 return wfMsgExt(
'lineno', array(
'parseinline'), $wgLang->formatNum($matches[1]));
499 if (!is_object($this->mOldRev) || !is_object($this->mNewRev)) {
503 if (!$this->mOldPage->equals($this->mNewPage)) {
508 $oldid = $this->mOldRev->getId();
509 $newid = $this->mNewRev->getId();
510 if ($oldid > $newid) {
516 $n = $this->mTitle->countRevisionsBetween($oldid, $newid);
521 return wfMsgExt(
'diff-multi', array(
'parseinline' ),
$n);
528 public function addHeader($diff, $otitle, $ntitle, $multi =
'')
532 if ($this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT)) {
533 $otitle =
'<span class="history-deleted">' . $otitle .
'</span>';
535 if ($this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT)) {
536 $ntitle =
'<span class="history-deleted">' . $ntitle .
'</span>';
539 <table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'> 541 <td colspan='2' width='50%' align='center' class='diff-otitle'>{$otitle}</td> 542 <td colspan='2' width='50%' align='center' class='diff-ntitle'>{$ntitle}</td> 547 $header .=
"<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>";
550 return $header . $diff .
"</table>";
558 $this->mOldtext = $oldText;
559 $this->mNewtext = $newText;
560 $this->mTextLoaded = 2;
576 if ($this->mRevisionsLoaded) {
580 $this->mRevisionsLoaded =
true;
585 $this->mNewRev = Revision::newFromId($this->mNewid);
587 $this->mNewRev = Revision::newFromTitle($this->mTitle);
590 if (is_null($this->mNewRev)) {
595 $timestamp = $wgLang->timeanddate($this->mNewRev->getTimestamp(),
true);
596 $this->mNewPage = $this->mNewRev->getTitle();
597 if ($this->mNewRev->isCurrent()) {
598 $newLink = $this->mNewPage->escapeLocalUrl();
599 $this->mPagetitle = htmlspecialchars(
wfMsg(
'currentrev'));
600 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit');
602 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)" 603 .
" (<a href='$newEdit'>" . htmlspecialchars(
wfMsg(
'editold')) .
"</a>)";
605 $newLink = $this->mNewPage->escapeLocalUrl(
'oldid=' . $this->mNewid);
606 $newEdit = $this->mNewPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mNewid);
607 $this->mPagetitle = htmlspecialchars(
wfMsg(
'revisionasof',
$timestamp));
609 $this->mNewtitle =
"<a href='$newLink'>{$this->mPagetitle}</a>" 610 .
" (<a href='$newEdit'>" . htmlspecialchars(
wfMsg(
'editold')) .
"</a>)";
614 $this->mOldRev =
false;
616 $this->mOldRev = Revision::newFromId($this->mOldid);
617 } elseif ($this->mOldid === 0) {
618 $rev = $this->mNewRev->getPrevious();
620 $this->mOldid = $rev->getId();
621 $this->mOldRev = $rev;
624 $this->mOldid =
false;
625 $this->mOldRev =
false;
629 if (is_null($this->mOldRev)) {
633 if ($this->mOldRev) {
634 $this->mOldPage = $this->mOldRev->getTitle();
636 $t = $wgLang->timeanddate($this->mOldRev->getTimestamp(),
true);
637 $oldLink = $this->mOldPage->escapeLocalUrl(
'oldid=' . $this->mOldid);
638 $oldEdit = $this->mOldPage->escapeLocalUrl(
'action=edit&oldid=' . $this->mOldid);
639 $this->mOldtitle =
"<a href='$oldLink'>" . htmlspecialchars(
wfMsg(
'revisionasof',
$t))
640 .
"</a> (<a href='$oldEdit'>" . htmlspecialchars(
wfMsg(
'editold')) .
"</a>)";
642 $newUndo = $this->mNewPage->escapeLocalUrl(
'action=edit&undoafter=' . $this->mOldid .
'&undo=' . $this->mNewid);
643 $this->mNewtitle .=
" (<a href='$newUndo'>" . htmlspecialchars(
wfMsg(
'editundo')) .
"</a>)";
654 if ($this->mTextLoaded == 2) {
658 $this->mTextLoaded = 2;
664 if ($this->mOldRev) {
666 $this->mOldtext = $this->mOldRev->revText();
667 if ($this->mOldtext ===
false) {
671 if ($this->mNewRev) {
672 $this->mNewtext = $this->mNewRev->revText();
673 if ($this->mNewtext ===
false) {
685 if ($this->mTextLoaded >= 1) {
688 $this->mTextLoaded = 1;
693 $this->mNewtext = $this->mNewRev->getText();
704 define(
'USE_ASSERTS', function_exists(
'assert'));
717 public function reverse()
719 trigger_error(
'pure virtual', E_USER_ERROR);
722 public function norig()
724 return $this->orig ?
sizeof($this->orig) : 0;
727 public function nclosing()
729 return $this->closing ?
sizeof($this->closing) : 0;
740 public $type =
'copy';
742 public function __construct($orig, $closing =
false)
744 if (!is_array($closing)) {
748 $this->closing = $closing;
751 public function reverse()
764 public $type =
'delete';
768 $this->orig = $lines;
769 $this->closing =
false;
772 public function reverse()
785 public $type =
'add';
789 $this->closing = $lines;
793 public function reverse()
806 public $type =
'change';
811 $this->closing = $closing;
814 public function reverse()
846 const MAX_XREF_LENGTH = 10000;
848 public function diff($from_lines, $to_lines)
850 $fname =
'_DiffEngine::diff';
853 $n_from =
sizeof($from_lines);
854 $n_to =
sizeof($to_lines);
856 $this->xchanged = $this->ychanged = array();
857 $this->xv = $this->yv = array();
858 $this->xind = $this->yind = array();
860 unset($this->in_seq);
864 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
865 if ($from_lines[$skip] !== $to_lines[$skip]) {
868 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
873 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
874 if ($from_lines[$xi] !== $to_lines[$yi]) {
877 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
881 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
882 $xhash[$this->_line_hash($from_lines[$xi])] = 1;
885 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
886 $line = $to_lines[$yi];
887 if (($this->ychanged[$yi] = empty($xhash[$this->_line_hash($line)]))) {
890 $yhash[$this->_line_hash($line)] = 1;
894 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
895 $line = $from_lines[$xi];
896 if (($this->xchanged[$xi] = empty($yhash[$this->_line_hash($line)]))) {
904 $this->_compareseq(0,
sizeof($this->xv), 0,
sizeof($this->yv));
907 $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
908 $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
913 while ($xi < $n_from || $yi < $n_to) {
914 USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
915 USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
919 while ($xi < $n_from && $yi < $n_to
920 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
921 $copy[] = $from_lines[$xi++];
930 while ($xi < $n_from && $this->xchanged[$xi]) {
931 $delete[] = $from_lines[$xi++];
935 while ($yi < $n_to && $this->ychanged[$yi]) {
936 $add[] = $to_lines[$yi++];
939 if ($delete && $add) {
954 public function _line_hash($line)
956 if (strlen($line) > self::MAX_XREF_LENGTH) {
980 public function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)
982 $fname =
'_DiffEngine::_diag';
986 if ($xlim - $xoff > $ylim - $yoff) {
990 list($xoff, $xlim, $yoff, $ylim)
991 = array( $yoff, $ylim, $xoff, $xlim);
995 for (
$i = $ylim - 1;
$i >= $yoff;
$i--) {
996 $ymatches[$this->xv[
$i]][] =
$i;
999 for (
$i = $ylim - 1;
$i >= $yoff;
$i--) {
1000 $ymatches[$this->yv[
$i]][] =
$i;
1005 $this->seq[0] = $yoff - 1;
1006 $this->in_seq = array();
1007 $ymids[0] = array();
1009 $numer = $xlim - $xoff + $nchunks - 1;
1011 for ($chunk = 0; $chunk < $nchunks; $chunk++) {
1014 for (
$i = 0;
$i <= $this->lcs;
$i++) {
1015 $ymids[
$i][$chunk - 1] = $this->seq[
$i];
1019 $x1 = $xoff + (int) (($numer + ($xlim - $xoff) * $chunk) / $nchunks);
1020 for (;
$x < $x1;
$x++) {
1021 $line = $flip ? $this->yv[
$x] : $this->xv[
$x];
1022 if (empty($ymatches[$line])) {
1025 $matches = $ymatches[$line];
1027 while (list($junk,
$y) = each($matches)) {
1028 if (empty($this->in_seq[
$y])) {
1029 $k = $this->_lcs_pos($y);
1031 $ymids[$k] = $ymids[$k - 1];
1035 while (list( ,
$y) = each($matches)) {
1036 if (
$y > $this->seq[$k - 1]) {
1040 $this->in_seq[$this->seq[$k]] =
false;
1041 $this->seq[$k] =
$y;
1042 $this->in_seq[
$y] = 1;
1043 } elseif (empty($this->in_seq[
$y])) {
1044 $k = $this->_lcs_pos($y);
1046 $ymids[$k] = $ymids[$k - 1];
1053 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
1054 $ymid = $ymids[$this->lcs];
1055 for (
$n = 0;
$n < $nchunks - 1;
$n++) {
1056 $x1 = $xoff + (int) (($numer + ($xlim - $xoff) *
$n) / $nchunks);
1057 $y1 = $ymid[
$n] + 1;
1058 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
1060 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
1063 return array($this->lcs, $seps);
1066 public function _lcs_pos($ypos)
1068 $fname =
'_DiffEngine::_lcs_pos';
1072 if (
$end == 0 || $ypos > $this->seq[
$end]) {
1073 $this->seq[++$this->lcs] = $ypos;
1074 $this->in_seq[$ypos] = 1;
1080 while ($beg < $end) {
1081 $mid = (int) (($beg + $end) / 2);
1082 if ($ypos > $this->seq[$mid]) {
1091 $this->in_seq[$this->seq[
$end]] =
false;
1092 $this->seq[
$end] = $ypos;
1093 $this->in_seq[$ypos] = 1;
1109 public function _compareseq($xoff, $xlim, $yoff, $ylim)
1111 $fname =
'_DiffEngine::_compareseq';
1115 while ($xoff < $xlim && $yoff < $ylim
1116 && $this->xv[$xoff] == $this->yv[$yoff]) {
1122 while ($xlim > $xoff && $ylim > $yoff
1123 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
1128 if ($xoff == $xlim || $yoff == $ylim) {
1134 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
1136 = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
1142 while ($yoff < $ylim) {
1143 $this->ychanged[$this->yind[$yoff++]] = 1;
1145 while ($xoff < $xlim) {
1146 $this->xchanged[$this->xind[$xoff++]] = 1;
1152 while ($pt2 = next($seps)) {
1153 $this->_compareseq($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
1172 public function _shift_boundaries($lines, &
$changed, $other_changed)
1174 $fname =
'_DiffEngine::_shift_boundaries';
1180 $len =
sizeof($lines);
1181 $other_len =
sizeof($other_changed);
1195 while ($j < $other_len && $other_changed[$j]) {
1200 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
1203 while ($j < $other_len && $other_changed[$j]) {
1215 while (++$i < $len &&
$changed[$i]) {
1224 $runlength = $i -
$start;
1231 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
1234 while ($start > 0 &&
$changed[$start - 1]) {
1238 while ($other_changed[--$j]) {
1241 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
1249 $corresponding = $j < $other_len ?
$i : $len;
1258 while ($i < $len && $lines[$start] == $lines[$i]) {
1261 while ($i < $len &&
$changed[$i]) {
1265 USE_ASSERTS && assert($j < $other_len && !$other_changed[$j]);
1267 if ($j < $other_len && $other_changed[$j]) {
1268 $corresponding =
$i;
1269 while ($j < $other_len && $other_changed[$j]) {
1274 }
while ($runlength != $i -
$start);
1280 while ($corresponding < $i) {
1284 while ($other_changed[--$j]) {
1287 USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
1312 public function __construct($from_lines, $to_lines)
1315 $this->edits = $eng->
diff($from_lines, $to_lines);
1329 public function reverse()
1332 $rev->edits = array();
1333 foreach ($this->edits as $edit) {
1334 $rev->edits[] = $edit->reverse();
1344 public function isEmpty()
1346 foreach ($this->edits as $edit) {
1347 if ($edit->type !=
'copy') {
1361 public function lcs()
1364 foreach ($this->edits as $edit) {
1365 if ($edit->type ==
'copy') {
1366 $lcs +=
sizeof($edit->orig);
1380 public function orig()
1384 foreach ($this->edits as $edit) {
1386 array_splice($lines,
sizeof($lines), 0, $edit->orig);
1400 public function closing()
1404 foreach ($this->edits as $edit) {
1405 if ($edit->closing) {
1406 array_splice($lines,
sizeof($lines), 0, $edit->closing);
1417 public function _check($from_lines, $to_lines)
1419 $fname =
'Diff::_check';
1421 if (serialize($from_lines) != serialize($this->orig())) {
1422 trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
1424 if (serialize($to_lines) != serialize($this->closing())) {
1425 trigger_error(
"Reconstructed closing doesn't match", E_USER_ERROR);
1428 $rev = $this->reverse();
1429 if (serialize($to_lines) != serialize($rev->orig())) {
1430 trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
1432 if (serialize($from_lines) != serialize($rev->closing())) {
1433 trigger_error(
"Reversed closing doesn't match", E_USER_ERROR);
1438 foreach ($this->edits as $edit) {
1439 if ($prevtype == $edit->type) {
1440 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
1442 $prevtype = $edit->type;
1445 $lcs = $this->lcs();
1446 trigger_error(
'Diff okay: LCS = ' . $lcs, E_USER_NOTICE);
1487 $fname =
'MappedDiff::MappedDiff';
1490 assert(
sizeof($from_lines) ==
sizeof($mapped_from_lines));
1491 assert(
sizeof($to_lines) ==
sizeof($mapped_to_lines));
1493 parent::__construct($mapped_from_lines, $mapped_to_lines);
1496 for (
$i = 0;
$i <
sizeof($this->edits);
$i++) {
1497 $orig = &$this->edits[
$i]->orig;
1498 if (is_array($orig)) {
1499 $orig = array_slice($from_lines, $xi,
sizeof($orig));
1500 $xi +=
sizeof($orig);
1503 $closing = &$this->edits[
$i]->closing;
1504 if (is_array($closing)) {
1505 $closing = array_slice($to_lines, $yi,
sizeof($closing));
1506 $yi +=
sizeof($closing);
1531 public $leading_context_lines = 0;
1539 public $trailing_context_lines = 0;
1547 public function format($diff)
1549 $fname =
'DiffFormatter::format';
1556 $nlead = $this->leading_context_lines;
1557 $ntrail = $this->trailing_context_lines;
1559 $this->_start_diff();
1561 foreach ($diff->edits as $edit) {
1562 if ($edit->type ==
'copy') {
1563 if (is_array($block)) {
1564 if (
sizeof($edit->orig) <= $nlead + $ntrail) {
1568 $context = array_slice($edit->orig, 0, $ntrail);
1573 $ntrail + $xi - $x0,
1575 $ntrail + $yi - $y0,
1583 if (!is_array($block)) {
1596 $xi +=
sizeof($edit->orig);
1598 if ($edit->closing) {
1599 $yi +=
sizeof($edit->closing);
1603 if (is_array($block)) {
1613 $end = $this->_end_diff();
1618 public function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
1620 $fname =
'DiffFormatter::_block';
1622 $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
1623 foreach ($edits as $edit) {
1624 if ($edit->type ==
'copy') {
1625 $this->_context($edit->orig);
1626 } elseif ($edit->type ==
'add') {
1627 $this->_added($edit->closing);
1628 } elseif ($edit->type ==
'delete') {
1629 $this->_deleted($edit->orig);
1630 } elseif ($edit->type ==
'change') {
1631 $this->_changed($edit->orig, $edit->closing);
1633 trigger_error(
'Unknown edit type', E_USER_ERROR);
1636 $this->_end_block();
1640 public function _start_diff()
1645 public function _end_diff()
1647 $val = ob_get_contents();
1652 public function _block_header($xbeg, $xlen, $ybeg, $ylen)
1655 $xbeg .=
"," . ($xbeg + $xlen - 1);
1658 $ybeg .=
"," . ($ybeg + $ylen - 1);
1661 return $xbeg . ($xlen ? ($ylen ?
'c' :
'd') :
'a') . $ybeg;
1664 public function _start_block(
$header)
1669 public function _end_block()
1673 public function _lines($lines, $prefix =
' ')
1675 foreach ($lines as $line) {
1676 echo "$prefix $line\n";
1680 public function _context($lines)
1682 $this->_lines($lines);
1685 public function _added($lines)
1687 $this->_lines($lines,
'>');
1689 public function _deleted($lines)
1691 $this->_lines($lines,
'<');
1694 public function _changed($orig, $closing)
1696 $this->_deleted($orig);
1698 $this->_added($closing);
1708 define(
'NBSP',
' ');
1719 $this->_lines = array();
1725 public function _flushGroup($new_tag)
1727 if ($this->_group !==
'') {
1728 if ($this->_tag ==
'ins') {
1729 $this->_line .=
'[ilDiffInsStart]' .
1730 htmlspecialchars($this->_group) .
'[ilDiffInsEnd]';
1731 } elseif ($this->_tag ==
'del') {
1732 $this->_line .=
'[ilDiffDelStart]' .
1733 htmlspecialchars($this->_group) .
'[ilDiffDelEnd]';
1735 $this->_line .= htmlspecialchars($this->_group);
1739 $this->_tag = $new_tag;
1742 public function _flushLine($new_tag)
1744 $this->_flushGroup($new_tag);
1745 if ($this->_line !=
'') {
1746 array_push($this->_lines, $this->_line);
1748 # make empty lines visible by inserting an NBSP 1749 array_push($this->_lines,
NBSP);
1754 public function addWords($words,
$tag =
'')
1756 if (
$tag != $this->_tag) {
1757 $this->_flushGroup(
$tag);
1760 foreach ($words as $word) {
1765 if ($word[0] ==
"\n") {
1766 $this->_flushLine(
$tag);
1767 $word = substr($word, 1);
1769 assert(!strstr($word,
"\n"));
1770 $this->_group .= $word;
1774 public function getLines()
1776 $this->_flushLine(
'~done');
1777 return $this->_lines;
1788 const MAX_LINE_LENGTH = 10000;
1790 public function __construct($orig_lines, $closing_lines)
1792 $fname =
'WordLevelDiff::WordLevelDiff';
1795 list($orig_words, $orig_stripped) = $this->_split($orig_lines);
1796 list($closing_words, $closing_stripped) = $this->_split($closing_lines);
1798 parent::__construct(
1807 public function _split($lines)
1809 $fname =
'WordLevelDiff::_split';
1813 $stripped = array();
1815 foreach ($lines as $line) {
1816 # If the line is too long, just pretend the entire line is one big word 1817 # This prevents resource exhaustion problems 1824 if (strlen($line) > self::MAX_LINE_LENGTH) {
1826 $stripped[] = $line;
1830 '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
1834 $words = array_merge($words,
$m[0]);
1835 $stripped = array_merge($stripped,
$m[1]);
1840 return array($words, $stripped);
1843 public function orig()
1845 $fname =
'WordLevelDiff::orig';
1849 foreach ($this->edits as $edit) {
1850 if ($edit->type ==
'copy') {
1852 } elseif ($edit->orig) {
1853 $orig->addWords($edit->orig,
'del');
1856 $lines = $orig->getLines();
1861 public function closing()
1863 $fname =
'WordLevelDiff::closing';
1867 foreach ($this->edits as $edit) {
1868 if ($edit->type ==
'copy') {
1869 $closing->
addWords($edit->closing);
1870 } elseif ($edit->closing) {
1871 $closing->addWords($edit->closing,
'ins');
1874 $lines = $closing->getLines();
1890 $this->leading_context_lines = 2;
1891 $this->trailing_context_lines = 2;
1894 public function _block_header($xbeg, $xlen, $ybeg, $ylen)
1896 $r =
'<tr><td colspan="2" align="left"><strong><!--LINE ' . $xbeg .
"--></strong></td>\n" .
1897 '<td colspan="2" align="left"><strong><!--LINE ' . $ybeg .
"--></strong></td></tr>\n";
1906 public function _end_block()
1910 public function _lines($lines, $prefix =
' ', $color =
'white')
1914 # HTML-escape parameter before calling this 1915 public function addedLine($line)
1917 return "<td>+</td><td class='diff-addedline'>{$line}</td>";
1920 # HTML-escape parameter before calling this 1921 public function deletedLine($line)
1923 return "<td>-</td><td class='diff-deletedline'>{$line}</td>";
1926 # HTML-escape parameter before calling this 1927 public function contextLine($line)
1929 return "<td> </td><td class='diff-context'>{$line}</td>";
1932 public function emptyLine()
1934 return '<td colspan="2"> </td>';
1937 public function _added($lines)
1939 foreach ($lines as $line) {
1940 echo '<tr>' . $this->emptyLine() .
1941 $this->addedLine(htmlspecialchars($line)) .
"</tr>\n";
1945 public function _deleted($lines)
1947 foreach ($lines as $line) {
1948 echo '<tr>' . $this->deletedLine(htmlspecialchars($line)) .
1949 $this->emptyLine() .
"</tr>\n";
1953 public function _context($lines)
1955 foreach ($lines as $line) {
1957 $this->contextLine(htmlspecialchars($line)) .
1958 $this->contextLine(htmlspecialchars($line)) .
"</tr>\n";
1962 public function _changed($orig, $closing)
1964 $fname =
'TableDiffFormatter::_changed';
1968 $del = $diff->orig();
1969 $add = $diff->closing();
1971 # Notice that WordLevelDiff returns HTML-escaped output. 1972 # Hence, we will be calling addedLine/deletedLine without HTML-escaping. 1974 while ($line = array_shift($del)) {
1975 $aline = array_shift($add);
1976 echo '<tr>' . $this->deletedLine($line) .
1977 $this->addedLine($aline) .
"</tr>\n";
1979 foreach ($add as $line) { # If any leftovers
1980 echo '<tr>' . $this->emptyLine() .
1981 $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)
showDiff($otitle, $ntitle)
Get the diff text, send it to $wgOut Returns false if the diff could not be generated, otherwise returns true.
localiseLineNumbers($text)
Replace line numbers with the text in the user's language.
generateDiffBody($otext, $ntext)
Generate a diff, no caching $otext and $ntext must be already segmented.
addHeader($diff, $otitle, $ntitle, $multi='')
Add the header to a diff body.
showDiffPage($diffOnly=false)
addWords($words, $tag='')
localiseLineNumbersCb($matches)
renderNewRevision()
Show the new revision of the page.
getMultiNotice()
If there are revisions between the ones being compared, return a note saying so.
__construct($titleObj=null, $old=0, $new=0, $rcid=0)
#-
setText($oldText, $newText)
Use specified text instead of loading from the database.
foreach($mandatory_scripts as $file) $timestamp
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.
if(function_exists('posix_getuid') &&posix_getuid()===0) if(!array_key_exists('t', $options)) $tag
loadText()
Load the text of the revisions, as well as revision data.
getDiff($otitle, $ntitle)
Get diff table, including header Note that the interface has changed, it's no longer static...