X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FDifferenceEngine.php;h=7d910aa842874ac244a152a51b8fe36b8f2960f3;hb=2104f62734f5f16f9f6d78e9782db2375c0805ad;hp=ffe3b6f47204a2926eb6a424a67cf334f1d499cd;hpb=c0e96b915273d1796a1b5180d23fa83b0739f08d;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php index ffe3b6f472..7d910aa842 100644 --- a/includes/DifferenceEngine.php +++ b/includes/DifferenceEngine.php @@ -1,49 +1,98 @@ mOldid = $old; - $this->mNewid = $new; + global $wgTitle; + if ( 'prev' == $new ) { + # Show diff between revision $old and the previous one. + # Get previous one from DB. + # + $this->mNewid = intval($old); + + $this->mOldid = $wgTitle->getPreviousRevisionID( $this->mNewid ); + + } elseif ( 'next' == $new ) { + + # Show diff between revision $old and the previous one. + # Get previous one from DB. + # + $this->mOldid = intval($old); + $this->mNewid = $wgTitle->getNextRevisionID( $this->mOldid ); + if ( false === $this->mNewid ) { + # if no result, NewId points to the newest old revision. The only newer + # revision is cur, which is "0". + $this->mNewid = 0; + } + + } else { + + $this->mOldid = intval($old); + $this->mNewid = intval($new); + } + $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer } function showDiffPage() { - global $wgUser, $wgTitle, $wgOut, $wgLang; - $fname = "DifferenceEngine::showDiffPage"; + global $wgUser, $wgTitle, $wgOut, $wgContLang, $wgOnlySysopsCanPatrol, $wgUseRCPatrol; + $fname = 'DifferenceEngine::showDiffPage'; wfProfileIn( $fname ); - + + # mOldid is false if the difference engine is called with a "vague" query for + # a diff between a version V and its previous version V' AND the version V + # is the first version of that article. In that case, V' does not exist. + if ( $this->mOldid === false ) { + $this->showFirstRevision(); + wfProfileOut( $fname ); + return; + } + $t = $wgTitle->getPrefixedText() . " (Diff: {$this->mOldid}, " . "{$this->mNewid})"; - $mtext = wfMsg( "missingarticle", $t ); + $mtext = wfMsg( 'missingarticle', $t ); $wgOut->setArticleFlag( false ); if ( ! $this->loadText() ) { - $wgOut->setPagetitle( wfMsg( "errorpagetitle" ) ); + $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) ); $wgOut->addHTML( $mtext ); wfProfileOut( $fname ); return; } $wgOut->suppressQuickbar(); - + $oldTitle = $this->mOldPage->getPrefixedText(); $newTitle = $this->mNewPage->getPrefixedText(); if( $oldTitle == $newTitle ) { $wgOut->setPageTitle( $newTitle ); } else { - $wgOut->setPageTitle( $oldTitle . ", " . $newTitle ); + $wgOut->setPageTitle( $oldTitle . ', ' . $newTitle ); } - $wgOut->setSubtitle( wfMsg( "difference" ) ); - $wgOut->setRobotpolicy( "noindex,follow" ); - + $wgOut->setSubtitle( wfMsg( 'difference' ) ); + $wgOut->setRobotpolicy( 'noindex,follow' ); + if ( !( $this->mOldPage->userCanRead() && $this->mNewPage->userCanRead() ) ) { $wgOut->loginToUse(); $wgOut->output(); @@ -52,63 +101,155 @@ class DifferenceEngine { } $sk = $wgUser->getSkin(); - $talk = $wgLang->getNsText( NS_TALK ); - $contribs = wfMsg( "contribslink" ); - + $talk = $wgContLang->getNsText( NS_TALK ); + $contribs = wfMsg( 'contribslink' ); $this->mOldComment = $sk->formatComment($this->mOldComment); $this->mNewComment = $sk->formatComment($this->mNewComment); + $oldUserLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mOldUser ), $this->mOldUser ); + $newUserLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mNewUser ), $this->mNewUser ); + $oldUTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mOldUser ), $talk ); + $newUTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mNewUser ), $talk ); + $oldContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $contribs, + 'target=' . urlencode($this->mOldUser) ); + $newContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $contribs, + 'target=' . urlencode($this->mNewUser) ); + if ( !$this->mNewid && $wgUser->isAllowed('rollback') ) { + $rollback = '   [' . $sk->makeKnownLinkObj( $wgTitle, wfMsg( 'rollbacklink' ), + 'action=rollback&from=' . urlencode($this->mNewUser) ) . ']'; + } else { + $rollback = ''; + } + if ( $wgUseRCPatrol && $this->mRcidMarkPatrolled != 0 && $wgUser->getID() != 0 && + ( $wgUser->isAllowed('rollback') || !$wgOnlySysopsCanPatrol ) ) + { + $patrol = ' [' . $sk->makeKnownLinkObj( $wgTitle, wfMsg( 'markaspatrolleddiff' ), + "action=markpatrolled&rcid={$this->mRcidMarkPatrolled}" ) . ']'; + } else { + $patrol = ''; + } - $oldUserLink = $sk->makeLinkObj( Title::makeTitle( NS_USER, $this->mOldUser ), $this->mOldUser ); - $newUserLink = $sk->makeLinkObj( Title::makeTitle( NS_USER, $this->mNewUser ), $this->mNewUser ); - $oldUTLink = $sk->makeLinkObj( Title::makeTitle( NS_USER_TALK, $this->mOldUser ), $talk ); - $newUTLink = $sk->makeLinkObj( Title::makeTitle( NS_USER_TALK, $this->mNewUser ), $talk ); - $oldContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, "Contributions" ), $contribs, - "target=" . urlencode($this->mOldUser) ); - $newContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, "Contributions" ), $contribs, - "target=" . urlencode($this->mNewUser) ); - if ( !$this->mNewid && $wgUser->isSysop() ) { - $rollback = "   [" . $sk->makeKnownLinkObj( $wgTitle, wfMsg( "rollbacklink" ), - "action=rollback&from=" . urlencode($this->mNewUser) ) . "]"; + $prevlink = $sk->makeKnownLinkObj( $wgTitle, wfMsg( 'previousdiff' ), 'diff=prev&oldid='.$this->mOldid ); + if ( $this->mNewid == 0 ) { + $nextlink = ''; } else { - $rollback = ""; + $nextlink = $sk->makeKnownLinkObj( $wgTitle, wfMsg( 'nextdiff' ), 'diff=next&oldid='.$this->mNewid ); } - $oldHeader = "{$this->mOldtitle}
$oldUserLink ($oldUTLink | $oldContribs)
". - $this->mOldComment; - $newHeader = "{$this->mNewtitle}
$newUserLink ($newUTLink | $newContribs) $rollback
". - $this->mNewComment; + $oldHeader = "{$this->mOldtitle}
$oldUserLink " . + "($oldUTLink | $oldContribs)
" . $this->mOldComment . + '
' . $prevlink; + $newHeader = "{$this->mNewtitle}
$newUserLink " . + "($newUTLink | $newContribs) $rollback
" . $this->mNewComment . + '
' . $nextlink . $patrol; DifferenceEngine::showDiff( $this->mOldtext, $this->mNewtext, $oldHeader, $newHeader ); - $wgOut->addHTML( "

{$this->mNewtitle}

\n" ); + $wgOut->addHTML( "

{$this->mPagetitle}

\n" ); $wgOut->addWikiText( $this->mNewtext ); - + wfProfileOut( $fname ); } - function showDiff( $otext, $ntext, $otitle, $ntitle ) + # Show the first revision of an article. Uses normal diff headers in contrast to normal + # "old revision" display style. + # + function showFirstRevision() { - global $wgOut; + global $wgOut, $wgTitle, $wgUser, $wgLang; - $ota = explode( "\n", str_replace( "\r\n", "\n", - htmlspecialchars( $otext ) ) ); - $nta = explode( "\n", str_replace( "\r\n", "\n", - htmlspecialchars( $ntext ) ) ); + $fname = 'DifferenceEngine::showFirstRevision'; + wfProfileIn( $fname ); + + + $this->mOldid = $this->mNewid; # hack to make loadText() work. + + # Get article text from the DB + # + if ( ! $this->loadText() ) { + $t = $wgTitle->getPrefixedText() . " (Diff: {$this->mOldid}, " . + "{$this->mNewid})"; + $mtext = wfMsg( 'missingarticle', $t ); + $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) ); + $wgOut->addHTML( $mtext ); + wfProfileOut( $fname ); + return; + } + + # Check if user is allowed to look at this page. If not, bail out. + # + if ( !( $this->mOldPage->userCanRead() ) ) { + $wgOut->loginToUse(); + $wgOut->output(); + wfProfileOut( $fname ); + exit; + } - $wgOut->addHTML( " - - -\n" ); + # Prepare the header box + # + $sk = $wgUser->getSkin(); - $diffs = new Diff( $ota, $nta ); - $formatter = new TableDiffFormatter(); - $formatter->format( $diffs ); - $wgOut->addHTML( "
-{$otitle} -{$ntitle}
\n" ); + $uTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mOldUser ), $wgLang->getNsText( NS_TALK ) ); + $userLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mOldUser ), $this->mOldUser ); + $contribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), wfMsg( 'contribslink' ), + 'target=' . urlencode($this->mOldUser) ); + $nextlink = $sk->makeKnownLinkObj( $wgTitle, wfMsg( 'nextdiff' ), 'diff=next&oldid='.$this->mNewid ); + $header = "
{$this->mOldtitle}
$userLink " . + "($uTLink | $contribs)
" . $this->mOldComment . + '
' . $nextlink. "
\n"; + + $wgOut->addHTML( $header ); + + $wgOut->setSubtitle( wfMsg( 'difference' ) ); + $wgOut->setRobotpolicy( 'noindex,follow' ); + + + # Show current revision + # + $wgOut->addHTML( "

{$this->mPagetitle}

\n" ); + $wgOut->addWikiText( $this->mNewtext ); + + wfProfileOut( $fname ); + } + + function showDiff( $otext, $ntext, $otitle, $ntitle ) + { + global $wgOut; + $wgOut->addHTML( DifferenceEngine::getDiff( $otext, $ntext, $otitle, $ntitle ) ); + } + + function getDiff( $otext, $ntext, $otitle, $ntitle ) { + global $wgUseExternalDiffEngine, $wgContLang; + $out = " + + + + + + "; + $otext = $wgContLang->segmentForDiff($otext); + $ntext = $wgContLang->segmentForDiff($ntext); + $difftext=''; + if ( $wgUseExternalDiffEngine ) { + # For historical reasons, external diff engine expects + # input text to be HTML-escaped already + $otext = str_replace( "\r\n", "\n", htmlspecialchars ( $otext ) ); + $ntext = str_replace( "\r\n", "\n", htmlspecialchars ( $ntext ) ); + if( !function_exists( 'wikidiff_do_diff' ) ) { + dl('php_wikidiff.so'); + } + $difftext = wikidiff_do_diff( $otext, $ntext, 2 ); + } else { + $ota = explode( "\n", str_replace( "\r\n", "\n", $otext ) ); + $nta = explode( "\n", str_replace( "\r\n", "\n", $ntext ) ); + $diffs =& new Diff( $ota, $nta ); + $formatter =& new TableDiffFormatter(); + $difftext = $formatter->format( $diffs ); + } + $difftext = $wgContLang->unsegmentForDiff($difftext); + $out .= $difftext."
{$otitle}{$ntitle}
\n"; + return $out; } # Load the text of the articles to compare. If newid is 0, then compare @@ -118,64 +259,50 @@ cellpadding='0' cellspacing='4px' class='diff'> # function loadText() { - global $wgTitle, $wgOut, $wgLang, $wgIsMySQL, $wgIsPg; - $fname = "DifferenceEngine::loadText"; + global $wgTitle, $wgOut, $wgLang; + $fname = 'DifferenceEngine::loadText'; + + $dbr =& wfGetDB( DB_SLAVE ); + if( $this->mNewid ) { + $this->newRev =& Revision::newFromId( $this->mNewid ); + } else { + $this->newRev =& Revision::newFromTitle( $wgTitle ); + } - $oldtable=wgIsPg?'"old"':'old'; - if ( 0 == $this->mNewid || 0 == $this->mOldid ) { - $wgOut->setArticleFlag( true ); - $this->mNewtitle = wfMsg( "currentrev" ); - $id = $wgTitle->getArticleID(); - - $sql = "SELECT cur_text, cur_user_text, cur_comment FROM cur WHERE cur_id={$id}"; - $res = wfQuery( $sql, DB_READ, $fname ); - if ( 0 == wfNumRows( $res ) ) { return false; } - - $s = wfFetchObject( $res ); - $this->mNewPage = &$wgTitle; - $this->mNewtext = $s->cur_text; - $this->mNewUser = $s->cur_user_text; - $this->mNewComment = $s->cur_comment; + if( $this->newRev->isCurrent() ) { + $this->mPagetitle = htmlspecialchars( wfMsg( 'currentrev' ) ); + $this->mNewPage = $wgTitle; + $newLink = $this->mNewPage->escapeLocalUrl(); + $this->mNewtitle = "{$this->mPagetitle}"; } else { - $sql = "SELECT old_namespace,old_title,old_timestamp,old_text,old_flags,old_user_text,old_comment FROM $oldtable WHERE " . - "old_id={$this->mNewid}"; - - $res = wfQuery( $sql, DB_READ, $fname ); - if ( 0 == wfNumRows( $res ) ) { return false; } - - $s = wfFetchObject( $res ); - $this->mNewtext = Article::getRevisionText( $s ); - - $t = $wgLang->timeanddate( $s->old_timestamp, true ); - $this->mNewPage = Title::MakeTitle( $s->old_namespace, $s->old_title ); - $this->mNewtitle = wfMsg( "revisionasof", $t ); - $this->mNewUser = $s->old_user_text; - $this->mNewComment = $s->old_comment; + $this->mNewPage = $this->newRev->getTitle(); + $newLink = $this->mNewPage->escapeLocalUrl ('oldid=' . $this->mNewid ); + $t = $wgLang->timeanddate( $this->newRev->getTimestamp(), true ); + $this->mPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t ) ); + $this->mNewtitle = "{$this->mPagetitle}"; } - if ( 0 == $this->mOldid ) { - $use_index=$wgIsMySQL?"USE INDEX (name_title_timestamp)":""; - $sql = "SELECT old_namespace,old_title,old_timestamp,old_text,old_flags,old_user_text,old_comment " . - "FROM $oldtable $use_index WHERE " . - "old_namespace=" . $this->mNewPage->getNamespace() . " AND " . - "old_title='" . wfStrencode( $this->mNewPage->getDBkey() ) . - "' ORDER BY inverse_timestamp LIMIT 1"; - $res = wfQuery( $sql, DB_READ, $fname ); + + if( $this->mOldid ) { + $this->oldRev =& Revision::newFromId( $this->mOldid ); } else { - $sql = "SELECT old_namespace,old_title,old_timestamp,old_text,old_flags,old_user_text,old_comment FROM $oldtable WHERE " . - "old_id={$this->mOldid}"; - $res = wfQuery( $sql, DB_READ, $fname ); + $this->oldRev =& $this->newRev->getPrevious(); + $this->mOldid = $this->oldRev->getId(); } - if ( 0 == wfNumRows( $res ) ) { return false; } - - $s = wfFetchObject( $res ); - $this->mOldPage = Title::MakeTitle( $s->old_namespace, $s->old_title ); - $this->mOldtext = Article::getRevisionText( $s ); + + $this->mOldPage = $this->oldRev->getTitle(); - $t = $wgLang->timeanddate( $s->old_timestamp, true ); - $this->mOldtitle = wfMsg( "revisionasof", $t ); - $this->mOldUser = $s->old_user_text; - $this->mOldComment = $s->old_comment; + $t = $wgLang->timeanddate( $this->oldRev->getTimestamp(), true ); + $oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid ); + $this->mOldtitle = "" . htmlspecialchars( wfMsg( 'revisionasof', $t ) ) . ''; + + $this->mNewUser = $this->newRev->getUserText(); + $this->mNewComment = $this->newRev->getComment(); + $this->mNewtext = $this->newRev->getText(); + $this->mOldUser = $this->oldRev->getUserText(); + $this->mOldComment = $this->oldRev->getComment(); + $this->mOldtext = $this->oldRev->getText(); + return true; } } @@ -188,13 +315,19 @@ cellpadding='0' cellspacing='4px' class='diff'> define('USE_ASSERTS', function_exists('assert')); +/** + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine + */ class _DiffOp { var $type; var $orig; var $closing; - + function reverse() { - trigger_error("pure virtual", E_USER_ERROR); + trigger_error('pure virtual', E_USER_ERROR); } function norig() { @@ -206,9 +339,15 @@ class _DiffOp { } } +/** + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine + */ class _DiffOp_Copy extends _DiffOp { var $type = 'copy'; - + function _DiffOp_Copy ($orig, $closing = false) { if (!is_array($closing)) $closing = $orig; @@ -221,9 +360,15 @@ class _DiffOp_Copy extends _DiffOp { } } +/** + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine + */ class _DiffOp_Delete extends _DiffOp { var $type = 'delete'; - + function _DiffOp_Delete ($lines) { $this->orig = $lines; $this->closing = false; @@ -234,9 +379,15 @@ class _DiffOp_Delete extends _DiffOp { } } +/** + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine + */ class _DiffOp_Add extends _DiffOp { var $type = 'add'; - + function _DiffOp_Add ($lines) { $this->closing = $lines; $this->orig = false; @@ -247,9 +398,15 @@ class _DiffOp_Add extends _DiffOp { } } +/** + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine + */ class _DiffOp_Change extends _DiffOp { var $type = 'change'; - + function _DiffOp_Change ($orig, $closing) { $this->orig = $orig; $this->closing = $closing; @@ -259,8 +416,8 @@ class _DiffOp_Change extends _DiffOp { return new _DiffOp_Change($this->closing, $this->orig); } } - - + + /** * Class used internally by Diff to actually compute the diffs. * @@ -280,10 +437,15 @@ class _DiffOp_Change extends _DiffOp { * * @author Geoffrey T. Dairiki * @access private + * @package MediaWiki + * @subpackage DifferenceEngine */ class _DiffEngine { function diff ($from_lines, $to_lines) { + $fname = '_DiffEngine::diff'; + wfProfileIn( $fname ); + $n_from = sizeof($from_lines); $n_to = sizeof($to_lines); @@ -293,7 +455,7 @@ class _DiffEngine unset($this->seq); unset($this->in_seq); unset($this->lcs); - + // Skip leading common lines. for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { if ($from_lines[$skip] != $to_lines[$skip]) @@ -307,7 +469,7 @@ class _DiffEngine break; $this->xchanged[$xi] = $this->ychanged[$yi] = false; } - + // Ignore lines which do not exist in both files. for ($xi = $skip; $xi < $n_from - $endskip; $xi++) $xhash[$from_lines[$xi]] = 1; @@ -359,7 +521,7 @@ class _DiffEngine $add = array(); while ($yi < $n_to && $this->ychanged[$yi]) $add[] = $to_lines[$yi++]; - + if ($delete && $add) $edits[] = new _DiffOp_Change($delete, $add); elseif ($delete) @@ -367,9 +529,10 @@ class _DiffEngine elseif ($add) $edits[] = new _DiffOp_Add($add); } + wfProfileOut( $fname ); return $edits; } - + /* Divide the Largest Common Subsequence (LCS) of the sequences * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally @@ -388,102 +551,111 @@ class _DiffEngine * of the portions it is going to specify. */ function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { - $flip = false; - - if ($xlim - $xoff > $ylim - $yoff) { - // Things seems faster (I'm not sure I understand why) - // when the shortest sequence in X. - $flip = true; - list ($xoff, $xlim, $yoff, $ylim) - = array( $yoff, $ylim, $xoff, $xlim); + $fname = '_DiffEngine::_diag'; + wfProfileIn( $fname ); + $flip = false; + + if ($xlim - $xoff > $ylim - $yoff) { + // Things seems faster (I'm not sure I understand why) + // when the shortest sequence in X. + $flip = true; + list ($xoff, $xlim, $yoff, $ylim) + = array( $yoff, $ylim, $xoff, $xlim); } - if ($flip) - for ($i = $ylim - 1; $i >= $yoff; $i--) - $ymatches[$this->xv[$i]][] = $i; - else - for ($i = $ylim - 1; $i >= $yoff; $i--) - $ymatches[$this->yv[$i]][] = $i; - - $this->lcs = 0; - $this->seq[0]= $yoff - 1; - $this->in_seq = array(); - $ymids[0] = array(); + if ($flip) + for ($i = $ylim - 1; $i >= $yoff; $i--) + $ymatches[$this->xv[$i]][] = $i; + else + for ($i = $ylim - 1; $i >= $yoff; $i--) + $ymatches[$this->yv[$i]][] = $i; - $numer = $xlim - $xoff + $nchunks - 1; - $x = $xoff; - for ($chunk = 0; $chunk < $nchunks; $chunk++) { - if ($chunk > 0) - for ($i = 0; $i <= $this->lcs; $i++) - $ymids[$i][$chunk-1] = $this->seq[$i]; - - $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); - for ( ; $x < $x1; $x++) { + $this->lcs = 0; + $this->seq[0]= $yoff - 1; + $this->in_seq = array(); + $ymids[0] = array(); + + $numer = $xlim - $xoff + $nchunks - 1; + $x = $xoff; + for ($chunk = 0; $chunk < $nchunks; $chunk++) { + wfProfileIn( "$fname-chunk" ); + if ($chunk > 0) + for ($i = 0; $i <= $this->lcs; $i++) + $ymids[$i][$chunk-1] = $this->seq[$i]; + + $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); + for ( ; $x < $x1; $x++) { $line = $flip ? $this->yv[$x] : $this->xv[$x]; - if (empty($ymatches[$line])) - continue; - $matches = $ymatches[$line]; + if (empty($ymatches[$line])) + continue; + $matches = $ymatches[$line]; reset($matches); - while (list ($junk, $y) = each($matches)) - if (empty($this->in_seq[$y])) { - $k = $this->_lcs_pos($y); - USE_ASSERTS && assert($k > 0); - $ymids[$k] = $ymids[$k-1]; - break; + while (list ($junk, $y) = each($matches)) + if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS && assert($k > 0); + $ymids[$k] = $ymids[$k-1]; + break; } - while (list ($junk, $y) = each($matches)) { - if ($y > $this->seq[$k-1]) { - USE_ASSERTS && assert($y < $this->seq[$k]); - // Optimization: this is a common case: - // next match is just replacing previous match. - $this->in_seq[$this->seq[$k]] = false; - $this->seq[$k] = $y; - $this->in_seq[$y] = 1; - } - else if (empty($this->in_seq[$y])) { - $k = $this->_lcs_pos($y); - USE_ASSERTS && assert($k > 0); - $ymids[$k] = $ymids[$k-1]; + while (list ($junk, $y) = each($matches)) { + if ($y > $this->seq[$k-1]) { + USE_ASSERTS && assert($y < $this->seq[$k]); + // Optimization: this is a common case: + // next match is just replacing previous match. + $this->in_seq[$this->seq[$k]] = false; + $this->seq[$k] = $y; + $this->in_seq[$y] = 1; + } else if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS && assert($k > 0); + $ymids[$k] = $ymids[$k-1]; } } } + wfProfileOut( "$fname-chunk" ); } - - $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); - $ymid = $ymids[$this->lcs]; - for ($n = 0; $n < $nchunks - 1; $n++) { - $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); - $y1 = $ymid[$n] + 1; - $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + + $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); + $ymid = $ymids[$this->lcs]; + for ($n = 0; $n < $nchunks - 1; $n++) { + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); } - $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); - - return array($this->lcs, $seps); + $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); + + wfProfileOut( $fname ); + return array($this->lcs, $seps); } function _lcs_pos ($ypos) { - $end = $this->lcs; - if ($end == 0 || $ypos > $this->seq[$end]) { - $this->seq[++$this->lcs] = $ypos; - $this->in_seq[$ypos] = 1; - return $this->lcs; + $fname = '_DiffEngine::_lcs_pos'; + wfProfileIn( $fname ); + + $end = $this->lcs; + if ($end == 0 || $ypos > $this->seq[$end]) { + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + wfProfileOut( $fname ); + return $this->lcs; } - $beg = 1; - while ($beg < $end) { - $mid = (int)(($beg + $end) / 2); - if ( $ypos > $this->seq[$mid] ) - $beg = $mid + 1; - else - $end = $mid; + $beg = 1; + while ($beg < $end) { + $mid = (int)(($beg + $end) / 2); + if ( $ypos > $this->seq[$mid] ) + $beg = $mid + 1; + else + $end = $mid; } - - USE_ASSERTS && assert($ypos != $this->seq[$end]); - - $this->in_seq[$this->seq[$end]] = false; - $this->seq[$end] = $ypos; - $this->in_seq[$ypos] = 1; - return $end; + + USE_ASSERTS && assert($ypos != $this->seq[$end]); + + $this->in_seq[$this->seq[$end]] = false; + $this->seq[$end] = $ypos; + $this->in_seq[$ypos] = 1; + wfProfileOut( $fname ); + return $end; } /* Find LCS of two sequences. @@ -498,48 +670,51 @@ class _DiffEngine * All line numbers are origin-0 and discarded lines are not counted. */ function _compareseq ($xoff, $xlim, $yoff, $ylim) { - // Slide down the bottom initial diagonal. - while ($xoff < $xlim && $yoff < $ylim + $fname = '_DiffEngine::_compareseq'; + wfProfileIn( $fname ); + + // Slide down the bottom initial diagonal. + while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) { - ++$xoff; - ++$yoff; + ++$xoff; + ++$yoff; } - // Slide up the top initial diagonal. - while ($xlim > $xoff && $ylim > $yoff + // Slide up the top initial diagonal. + while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { - --$xlim; - --$ylim; + --$xlim; + --$ylim; } - if ($xoff == $xlim || $yoff == $ylim) - $lcs = 0; - else { - // This is ad hoc but seems to work well. - //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); - //$nchunks = max(2,min(8,(int)$nchunks)); - $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; - list ($lcs, $seps) - = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); + if ($xoff == $xlim || $yoff == $ylim) + $lcs = 0; + else { + // This is ad hoc but seems to work well. + //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); + //$nchunks = max(2,min(8,(int)$nchunks)); + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list ($lcs, $seps) + = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); } - if ($lcs == 0) { - // X and Y sequences have no common subsequence: - // mark all changed. - while ($yoff < $ylim) - $this->ychanged[$this->yind[$yoff++]] = 1; - while ($xoff < $xlim) - $this->xchanged[$this->xind[$xoff++]] = 1; - } - else { - // Use the partitions to split this problem into subproblems. - reset($seps); - $pt1 = $seps[0]; - while ($pt2 = next($seps)) { - $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); - $pt1 = $pt2; + if ($lcs == 0) { + // X and Y sequences have no common subsequence: + // mark all changed. + while ($yoff < $ylim) + $this->ychanged[$this->yind[$yoff++]] = 1; + while ($xoff < $xlim) + $this->xchanged[$this->xind[$xoff++]] = 1; + } else { + // Use the partitions to split this problem into subproblems. + reset($seps); + $pt1 = $seps[0]; + while ($pt2 = next($seps)) { + $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); + $pt1 = $pt2; } } + wfProfileOut( $fname ); } /* Adjust inserts/deletes of identical lines to join changes @@ -555,117 +730,124 @@ class _DiffEngine * This is extracted verbatim from analyze.c (GNU diffutils-2.7). */ function _shift_boundaries ($lines, &$changed, $other_changed) { - $i = 0; - $j = 0; - - USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)'); - $len = sizeof($lines); - $other_len = sizeof($other_changed); - - while (1) { - /* - * Scan forwards to find beginning of another run of changes. - * Also keep track of the corresponding point in the other file. - * - * Throughout this code, $i and $j are adjusted together so that - * the first $i elements of $changed and the first $j elements - * of $other_changed both contain the same number of zeros - * (unchanged lines). - * Furthermore, $j is always kept so that $j == $other_len or - * $other_changed[$j] == false. - */ - while ($j < $other_len && $other_changed[$j]) - $j++; - - while ($i < $len && ! $changed[$i]) { - USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); - $i++; $j++; - while ($j < $other_len && $other_changed[$j]) - $j++; + $fname = '_DiffEngine::_shift_boundaries'; + wfProfileIn( $fname ); + $i = 0; + $j = 0; + + USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)'); + $len = sizeof($lines); + $other_len = sizeof($other_changed); + + while (1) { + /* + * Scan forwards to find beginning of another run of changes. + * Also keep track of the corresponding point in the other file. + * + * Throughout this code, $i and $j are adjusted together so that + * the first $i elements of $changed and the first $j elements + * of $other_changed both contain the same number of zeros + * (unchanged lines). + * Furthermore, $j is always kept so that $j == $other_len or + * $other_changed[$j] == false. + */ + while ($j < $other_len && $other_changed[$j]) + $j++; + + while ($i < $len && ! $changed[$i]) { + USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + $i++; $j++; + while ($j < $other_len && $other_changed[$j]) + $j++; } - if ($i == $len) - break; - - $start = $i; - - // Find the end of this run of changes. - while (++$i < $len && $changed[$i]) - continue; - - do { - /* - * Record the length of this run of changes, so that - * we can later determine whether the run has grown. - */ - $runlength = $i - $start; - - /* - * Move the changed region back, so long as the - * previous unchanged line matches the last changed one. - * This merges with previous changed regions. - */ - while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { - $changed[--$start] = 1; - $changed[--$i] = false; - while ($start > 0 && $changed[$start - 1]) - $start--; - USE_ASSERTS && assert('$j > 0'); - while ($other_changed[--$j]) - continue; - USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + if ($i == $len) + break; + + $start = $i; + + // Find the end of this run of changes. + while (++$i < $len && $changed[$i]) + continue; + + do { + /* + * Record the length of this run of changes, so that + * we can later determine whether the run has grown. + */ + $runlength = $i - $start; + + /* + * Move the changed region back, so long as the + * previous unchanged line matches the last changed one. + * This merges with previous changed regions. + */ + while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { + $changed[--$start] = 1; + $changed[--$i] = false; + while ($start > 0 && $changed[$start - 1]) + $start--; + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); } - - /* - * Set CORRESPONDING to the end of the changed run, at the last - * point where it corresponds to a changed run in the other file. - * CORRESPONDING == LEN means no such point has been found. - */ - $corresponding = $j < $other_len ? $i : $len; - - /* - * Move the changed region forward, so long as the - * first changed line matches the following unchanged one. - * This merges with following changed regions. - * Do this second, so that if there are no merges, - * the changed region is moved forward as far as possible. - */ - while ($i < $len && $lines[$start] == $lines[$i]) { - $changed[$start++] = false; - $changed[$i++] = 1; - while ($i < $len && $changed[$i]) - $i++; - - USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); - $j++; - if ($j < $other_len && $other_changed[$j]) { - $corresponding = $i; - while ($j < $other_len && $other_changed[$j]) - $j++; + + /* + * Set CORRESPONDING to the end of the changed run, at the last + * point where it corresponds to a changed run in the other file. + * CORRESPONDING == LEN means no such point has been found. + */ + $corresponding = $j < $other_len ? $i : $len; + + /* + * Move the changed region forward, so long as the + * first changed line matches the following unchanged one. + * This merges with following changed regions. + * Do this second, so that if there are no merges, + * the changed region is moved forward as far as possible. + */ + while ($i < $len && $lines[$start] == $lines[$i]) { + $changed[$start++] = false; + $changed[$i++] = 1; + while ($i < $len && $changed[$i]) + $i++; + + USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + $j++; + if ($j < $other_len && $other_changed[$j]) { + $corresponding = $i; + while ($j < $other_len && $other_changed[$j]) + $j++; } } } while ($runlength != $i - $start); - - /* - * If possible, move the fully-merged run of changes - * back to a corresponding run in the other file. - */ - while ($corresponding < $i) { - $changed[--$start] = 1; - $changed[--$i] = 0; - USE_ASSERTS && assert('$j > 0'); - while ($other_changed[--$j]) - continue; - USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + + /* + * If possible, move the fully-merged run of changes + * back to a corresponding run in the other file. + */ + while ($corresponding < $i) { + $changed[--$start] = 1; + $changed[--$i] = 0; + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); } } + wfProfileOut( $fname ); } } /** * Class representing a 'diff' between two sequences of strings. + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine */ -class Diff +class Diff { var $edits; @@ -694,12 +876,12 @@ class Diff * original diff. */ function reverse () { - $rev = $this; + $rev = $this; $rev->edits = array(); foreach ($this->edits as $edit) { $rev->edits[] = $edit->reverse(); } - return $rev; + return $rev; } /** @@ -714,7 +896,7 @@ class Diff } return true; } - + /** * Compute the length of the Longest Common Subsequence (LCS). * @@ -723,12 +905,12 @@ class Diff * @return int The length of the LCS. */ function lcs () { - $lcs = 0; + $lcs = 0; foreach ($this->edits as $edit) { if ($edit->type == 'copy') $lcs += sizeof($edit->orig); } - return $lcs; + return $lcs; } /** @@ -741,7 +923,7 @@ class Diff */ function orig() { $lines = array(); - + foreach ($this->edits as $edit) { if ($edit->orig) array_splice($lines, sizeof($lines), 0, $edit->orig); @@ -759,7 +941,7 @@ class Diff */ function closing() { $lines = array(); - + foreach ($this->edits as $edit) { if ($edit->closing) array_splice($lines, sizeof($lines), 0, $edit->closing); @@ -768,11 +950,13 @@ class Diff } /** - * Check a Diff for validity. + * Check a Diff for validity. * * This is here only for debugging purposes. */ function _check ($from_lines, $to_lines) { + $fname = 'Diff::_check'; + wfProfileIn( $fname ); if (serialize($from_lines) != serialize($this->orig())) trigger_error("Reconstructed original doesn't match", E_USER_ERROR); if (serialize($to_lines) != serialize($this->closing())) @@ -793,15 +977,19 @@ class Diff } $lcs = $this->lcs(); - trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); + trigger_error('Diff okay: LCS = '.$lcs, E_USER_NOTICE); + wfProfileOut( $fname ); } } - + /** * FIXME: bad name. + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine */ -class MappedDiff -extends Diff +class MappedDiff extends Diff { /** * Constructor. @@ -828,10 +1016,12 @@ extends Diff */ function MappedDiff($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) { - + $fname = 'MappedDiff::MappedDiff'; + wfProfileIn( $fname ); + assert(sizeof($from_lines) == sizeof($mapped_from_lines)); assert(sizeof($to_lines) == sizeof($mapped_to_lines)); - + $this->Diff($mapped_from_lines, $mapped_to_lines); $xi = $yi = 0; @@ -841,13 +1031,14 @@ extends Diff $orig = array_slice($from_lines, $xi, sizeof($orig)); $xi += sizeof($orig); } - + $closing = &$this->edits[$i]->closing; if (is_array($closing)) { $closing = array_slice($to_lines, $yi, sizeof($closing)); $yi += sizeof($closing); } } + wfProfileOut( $fname ); } } @@ -857,6 +1048,10 @@ extends Diff * This class formats the diff in classic diff format. * It is intended that this class be customized via inheritance, * to obtain fancier outputs. + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine */ class DiffFormatter { @@ -883,6 +1078,8 @@ class DiffFormatter * @return string The formatted output. */ function format($diff) { + $fname = 'DiffFormatter::format'; + wfProfileIn( $fname ); $xi = $yi = 1; $block = false; @@ -935,10 +1132,14 @@ class DiffFormatter $y0, $yi - $y0, $block); - return $this->_end_diff(); + $end = $this->_end_diff(); + wfProfileOut( $fname ); + return $end; } function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { + $fname = 'DiffFormatter::_block'; + wfProfileIn( $fname ); $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); foreach ($edits as $edit) { if ($edit->type == 'copy') @@ -950,9 +1151,10 @@ class DiffFormatter elseif ($edit->type == 'change') $this->_changed($edit->orig, $edit->closing); else - trigger_error("Unknown edit type", E_USER_ERROR); + trigger_error('Unknown edit type', E_USER_ERROR); } $this->_end_block(); + wfProfileOut( $fname ); } function _start_diff() { @@ -973,11 +1175,11 @@ class DiffFormatter return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; } - + function _start_block($header) { echo $header; } - + function _end_block() { } @@ -985,16 +1187,16 @@ class DiffFormatter foreach ($lines as $line) echo "$prefix $line\n"; } - + function _context($lines) { $this->_lines($lines); } function _added($lines) { - $this->_lines($lines, ">"); + $this->_lines($lines, '>'); } function _deleted($lines) { - $this->_lines($lines, "<"); + $this->_lines($lines, '<'); } function _changed($orig, $closing) { @@ -1007,11 +1209,17 @@ class DiffFormatter /** * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 - * + * */ -define('NBSP', "\xA0"); // iso-8859-x non-breaking space. +define('NBSP', ' '); // iso-8859-x non-breaking space. +/** + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine + */ class _HWLDF_WordAccumulator { function _HWLDF_WordAccumulator () { $this->_lines = array(); @@ -1022,22 +1230,26 @@ class _HWLDF_WordAccumulator { function _flushGroup ($new_tag) { if ($this->_group !== '') { - if ($this->_tag == 'mark') - $this->_line .= "$this->_group"; - else - $this->_line .= $this->_group; - } + if ($this->_tag == 'mark') + $this->_line .= '' . + htmlspecialchars ( $this->_group ) . ''; + else + $this->_line .= htmlspecialchars ( $this->_group ); + } $this->_group = ''; $this->_tag = $new_tag; } - + function _flushLine ($new_tag) { $this->_flushGroup($new_tag); if ($this->_line != '') - $this->_lines[] = $this->_line; + array_push ( $this->_lines, $this->_line ); + else + # make empty lines visible by inserting an NBSP + array_push ( $this->_lines, NBSP ); $this->_line = ''; } - + function addWords ($words, $tag = '') { if ($tag != $this->_tag) $this->_flushGroup($tag); @@ -1047,7 +1259,6 @@ class _HWLDF_WordAccumulator { if ($word == '') continue; if ($word[0] == "\n") { - $this->_group .= NBSP; $this->_flushLine($tag); $word = substr($word, 1); } @@ -1062,56 +1273,79 @@ class _HWLDF_WordAccumulator { } } +/** + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine + */ class WordLevelDiff extends MappedDiff { function WordLevelDiff ($orig_lines, $closing_lines) { + $fname = 'WordLevelDiff::WordLevelDiff'; + wfProfileIn( $fname ); + list ($orig_words, $orig_stripped) = $this->_split($orig_lines); list ($closing_words, $closing_stripped) = $this->_split($closing_lines); - + $this->MappedDiff($orig_words, $closing_words, $orig_stripped, $closing_stripped); + wfProfileOut( $fname ); } function _split($lines) { - // FIXME: fix POSIX char class. -# if (!preg_match_all('/ ( [^\S\n]+ | [[:alnum:]]+ | . ) (?: (?!< \n) [^\S\n])? /xs', + $fname = 'WordLevelDiff::_split'; + wfProfileIn( $fname ); if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs', implode("\n", $lines), $m)) { + wfProfileOut( $fname ); return array(array(''), array('')); } + wfProfileOut( $fname ); return array($m[0], $m[1]); } function orig () { + $fname = 'WordLevelDiff::orig'; + wfProfileIn( $fname ); $orig = new _HWLDF_WordAccumulator; - + foreach ($this->edits as $edit) { if ($edit->type == 'copy') $orig->addWords($edit->orig); elseif ($edit->orig) $orig->addWords($edit->orig, 'mark'); } - return $orig->getLines(); + $lines = $orig->getLines(); + wfProfileOut( $fname ); + return $lines; } function closing () { + $fname = 'WordLevelDiff::closing'; + wfProfileIn( $fname ); $closing = new _HWLDF_WordAccumulator; - + foreach ($this->edits as $edit) { if ($edit->type == 'copy') $closing->addWords($edit->closing); elseif ($edit->closing) $closing->addWords($edit->closing, 'mark'); } - return $closing->getLines(); + $lines = $closing->getLines(); + wfProfileOut( $fname ); + return $lines; } } /** * Wikipedia Table style diff formatter. - * + * @todo document + * @access private + * @package MediaWiki + * @subpackage DifferenceEngine */ class TableDiffFormatter extends DiffFormatter { @@ -1119,81 +1353,89 @@ class TableDiffFormatter extends DiffFormatter $this->leading_context_lines = 2; $this->trailing_context_lines = 2; } - + function _block_header( $xbeg, $xlen, $ybeg, $ylen ) { - $l1 = wfMsg( "lineno", $xbeg ); - $l2 = wfMsg( "lineno", $ybeg ); + $l1 = wfMsg( 'lineno', $xbeg ); + $l2 = wfMsg( 'lineno', $ybeg ); - $r = "{$l1}\n" . - "{$l2}\n"; + $r = ''.$l1."\n" . + ''.$l2."\n"; return $r; } function _start_block( $header ) { global $wgOut; - $wgOut->addHTML( $header ); + echo $header; } function _end_block() { } - function _lines( $lines, $prefix=' ', $color="white" ) { + function _lines( $lines, $prefix=' ', $color='white' ) { } + # HTML-escape parameter before calling this function addedLine( $line ) { - return "+" . - "{$line}"; + return "+{$line}"; } + # HTML-escape parameter before calling this function deletedLine( $line ) { - return "-" . - "{$line}"; + return "-{$line}"; } - function emptyLine() { - return " "; + # HTML-escape parameter before calling this + function contextLine( $line ) { + return " {$line}"; } - function contextLine( $line ) { - return " {$line}"; + function emptyLine() { + return ' '; } - - function _added($lines) { - global $wgOut; + + function _added( $lines ) { foreach ($lines as $line) { - $wgOut->addHTML( "" . $this->emptyLine() . - $this->addedLine( $line ) . "\n" ); + echo '' . $this->emptyLine() . + $this->addedLine( htmlspecialchars ( $line ) ) . "\n"; } } function _deleted($lines) { - global $wgOut; foreach ($lines as $line) { - $wgOut->addHTML( "" . $this->deletedLine( $line ) . - $this->emptyLine() . "\n" ); + echo '' . $this->deletedLine( htmlspecialchars ( $line ) ) . + $this->emptyLine() . "\n"; } } function _context( $lines ) { - global $wgOut; foreach ($lines as $line) { - $wgOut->addHTML( "" . $this->contextLine( $line ) . - $this->contextLine( $line ) . "\n" ); + echo '' . + $this->contextLine( htmlspecialchars ( $line ) ) . + $this->contextLine( htmlspecialchars ( $line ) ) . "\n"; } } function _changed( $orig, $closing ) { - global $wgOut; + $fname = 'TableDiffFormatter::_changed'; + wfProfileIn( $fname ); + $diff = new WordLevelDiff( $orig, $closing ); $del = $diff->orig(); $add = $diff->closing(); + # Notice that WordLevelDiff returns HTML-escaped output. + # Hence, we will be calling addedLine/deletedLine without HTML-escaping. + while ( $line = array_shift( $del ) ) { $aline = array_shift( $add ); - $wgOut->addHTML( "" . $this->deletedLine( $line ) . - $this->addedLine( $aline ) . "\n" ); + echo '' . $this->deletedLine( $line ) . + $this->addedLine( $aline ) . "\n"; + } + foreach ($add as $line) { # If any leftovers + echo '' . $this->emptyLine() . + $this->addedLine( $line ) . "\n"; } - $this->_added( $add ); # If any leftovers + wfProfileOut( $fname ); } }