* changes in an incompatible way, so the parser cache
* can automatically discard old data.
*/
- const VERSION = '1.6.2';
+ const VERSION = '1.6.4';
# Flags for Parser::setFunctionHook
# Also available as global constants from Defines.php
# Constants needed for external link processing
# Everything except bracket, space, or control characters
const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F]';
- const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+)\\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/S';
+ const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+)
+ \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sx';
// State constants for the definition list colon extraction
const COLON_STATE_TEXT = 0;
const COLON_STATE_COMMENTDASH = 6;
const COLON_STATE_COMMENTDASHDASH = 7;
+ // Flags for preprocessToDom
+ const PTD_FOR_INCLUSION = 1;
+
/**#@+
* @private
*/
global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
$this->setHook( 'pre', array( $this, 'renderPreTag' ) );
-
- $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH );
- $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH );
- $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH );
- $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH );
- $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH );
- $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH );
- $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH );
- $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH );
- $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH );
- $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH );
- $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH );
- $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH );
- $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH );
- $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH );
- $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH );
- $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH );
+
+ # Syntax for arguments (see self::setFunctionHook):
+ # "name for lookup in localized magic words array",
+ # function callback,
+ # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}
+ # instead of {{#int:...}})
+ $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH );
$this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH );
- $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
- $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
- $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH );
- $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
- $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH );
- $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH );
- $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH );
- $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) );
- $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH );
- $this->setFunctionHook( 'filepath', array( 'CoreParserFunctions', 'filepath' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) );
+ $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'filepath', array( 'CoreParserFunctions', 'filepath' ), SFH_NO_HASH );
if ( $wgAllowDisplayTitle ) {
$this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH );
* @public
*/
function uniqPrefix() {
+ if( !isset( $this->mUniqPrefix ) ) {
+ // @fixme this is probably *horribly wrong*
+ // LanguageConverter seems to want $wgParser's uniqPrefix, however
+ // if this is called for a parser cache hit, the parser may not
+ // have ever been initialized in the first place.
+ // Not really sure what the heck is supposed to be going on here.
+ return '';
+ //throw new MWException( "Accessing uninitialized mUniqPrefix" );
+ }
return $this->mUniqPrefix;
}
wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
# Information on include size limits, for the benefit of users who try to skirt them
- if ( max( $this->mIncludeSizes ) > 1000 ) {
+ if ( $this->mOptions->getEnableLimitReport() ) {
$max = $this->mOptions->getMaxIncludeSize();
- $text .= "<!-- \n" .
- "Preprocessor node count: {$this->mPPNodeCount}\n" .
- "Post-expand include size: {$this->mIncludeSizes['post-expand']} bytes\n" .
- "Template argument size: {$this->mIncludeSizes['arg']} bytes\n" .
- "Maximum: $max bytes\n" .
- "-->\n";
+ $limitReport =
+ "Preprocessor node count: {$this->mPPNodeCount}/{$this->mOptions->mMaxPPNodeCount}\n" .
+ "Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" .
+ "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n";
+ wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
+ $text .= "\n<!-- \n$limitReport-->\n";
}
$this->mOutput->setText( $text );
$this->mRevisionId = $oldRevisionId;
function insertStripItem( $text ) {
static $n = 0;
$rnd = "{$this->mUniqPrefix}-item-$n-{$this->mMarkerSuffix}";
+ ++$n;
$this->mStripState->general->setPair( $rnd, $text );
return $rnd;
}
* @static
*/
function internalTidy( $text ) {
- global $wgTidyConf, $IP;
+ global $wgTidyConf, $IP, $wgDebugTidy;
$fname = 'Parser::internalTidy';
wfProfileIn( $fname );
} else {
$cleansource = tidy_get_output( $tidy );
}
+ if ( $wgDebugTidy && $tidy->getStatus() > 0 ) {
+ $cleansource .= "<!--\nTidy reports:\n" .
+ str_replace( '-->', '-->', $tidy->errorBuffer ) .
+ "\n-->";
+ }
+
wfProfileOut( $fname );
return $cleansource;
}
return $text ;
}
- # Remove <noinclude> tags and <includeonly> sections
- $text = strtr( $text, array( '<onlyinclude>' => '' , '</onlyinclude>' => '' ) );
- $text = strtr( $text, array( '<noinclude>' => '', '</noinclude>' => '') );
- $text = StringUtils::delimiterReplace( '<includeonly>', '</includeonly>', '', $text );
-
$text = $this->replaceVariables( $text );
$text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) );
wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision...\n" );
return $this->mRevisionId;
case 'revisionday':
+ // Let the edit saving system know we should parse the page
+ // *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
return intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
case 'revisionday2':
+ // Let the edit saving system know we should parse the page
+ // *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
return substr( $this->getRevisionTimestamp(), 6, 2 );
case 'revisionmonth':
+ // Let the edit saving system know we should parse the page
+ // *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
return intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
case 'revisionyear':
+ // Let the edit saving system know we should parse the page
+ // *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
return substr( $this->getRevisionTimestamp(), 0, 4 );
case 'revisiontimestamp':
+ // Let the edit saving system know we should parse the page
+ // *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
return $this->getRevisionTimestamp();
case 'namespace':
return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) );
}
/**
- * Parse any parentheses in format ((title|part|part)} and return the document tree
+ * Preprocess some wikitext and return the document tree.
* This is the ghost of replace_variables().
*
* @param string $text The text to parse
+ * @param integer flags Bitwise combination of:
+ * self::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
+ * included. Default is to assume a direct page view.
+ *
+ * The generated DOM tree must depend only on the input text, the flags, and $this->ot['msg'].
+ * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
+ *
+ * Any flag added to the $flags parameter here, or any other parameter liable to cause a
+ * change in the DOM tree for a given text, must be passed through the section identifier
+ * in the section edit link and thus back to extractSections().
+ *
+ * The output of this function is currently only cached in process memory, but a persistent
+ * cache may be implemented at a later date which takes further advantage of these strict
+ * dependency requirements.
+ *
* @private
*/
- function preprocessToDom ( $text ) {
+ function preprocessToDom ( $text, $flags = 0 ) {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__.'-makexml' );
- static $msgRules, $normalRules;
+ static $msgRules, $normalRules, $inclusionSupertags, $nonInclusionSupertags;
if ( !$msgRules ) {
$msgRules = array(
'{' => array(
} else {
$rules = $normalRules;
}
+ $forInclusion = $flags & self::PTD_FOR_INCLUSION;
+
+ $xmlishElements = $this->getStripList();
+ $enableOnlyinclude = false;
+ if ( $forInclusion ) {
+ $ignoredTags = array( 'includeonly', '/includeonly' );
+ $ignoredElements = array( 'noinclude' );
+ $xmlishElements[] = 'noinclude';
+ if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
+ $enableOnlyinclude = true;
+ }
+ } else {
+ $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' );
+ $ignoredElements = array( 'includeonly' );
+ $xmlishElements[] = 'includeonly';
+ }
+ $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
- $extElements = implode( '|', $this->getStripList() );
// Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
- $extElementsRegex = "/($extElements)(?:\s|\/>|>)|(!--)/iA";
+ $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
$stack = array(); # Stack of unclosed parentheses
$stackIndex = -1; # Stack read pointer
$searchBase = implode( '', array_keys( $rules ) ) . '<';
+ $revText = strrev( $text ); // For fast reverse searches
$i = -1; # Input pointer, starts out pointing to a pseudo-newline before the start
$topAccum = '<root>'; # Top level text accumulator
$findPipe = false; # True to take notice of pipe characters
$headingIndex = 1;
$noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
+ $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
+
+ if ( $enableOnlyinclude ) {
+ $i = 0;
+ }
+
+ while ( true ) {
+ if ( $findOnlyinclude ) {
+ // Ignore all input up to the next <onlyinclude>
+ $startPos = strpos( $text, '<onlyinclude>', $i );
+ if ( $startPos === false ) {
+ // Ignored section runs to the end
+ $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i ) ) . '</ignore>';
+ break;
+ }
+ $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
+ $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i ) ) . '</ignore>';
+ $i = $tagEndPos;
+ $findOnlyinclude = false;
+ }
- while ( $i < strlen( $text ) ) {
if ( $i == -1 ) {
$found = 'line-start';
$curChar = '';
if ( $found == 'angle' ) {
$matches = false;
+ // Handle </onlyinclude>
+ if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) {
+ $findOnlyinclude = true;
+ continue;
+ }
+
// Determine element name
- if ( !preg_match( $extElementsRegex, $text, $matches, 0, $i + 1 ) ) {
+ if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
// Element name missing or not listed
$accum .= '<';
++$i;
}
// Handle comments
if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
- // HTML comment, scan to end
- $endpos = strpos( $text, '-->', $i + 4 );
- if ( $endpos === false ) {
+ // To avoid leaving blank lines, when a comment is both preceded
+ // and followed by a newline (ignoring spaces), trim leading and
+ // trailing spaces and one of the newlines.
+
+ // Find the end
+ $endPos = strpos( $text, '-->', $i + 4 );
+ if ( $endPos === false ) {
// Unclosed comment in input, runs to end
- $accum .= htmlspecialchars( substr( $text, $i ) );
- if ( $this->ot['html'] ) {
- // Close it so later stripping can remove it
- $accum .= htmlspecialchars( '-->' );
- }
+ $inner = substr( $text, $i );
+ $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
$i = strlen( $text );
- continue;
+ } else {
+ // Search backwards for leading whitespace
+ $wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0;
+ // Search forwards for trailing whitespace
+ // $wsEnd will be the position of the last space
+ $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 );
+ // Eat the line if possible
+ if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
+ && substr( $text, $wsEnd + 1, 1 ) == "\n" )
+ {
+ $startPos = $wsStart;
+ $endPos = $wsEnd + 1;
+ // Remove leading whitespace from the end of the accumulator
+ // Sanity check first though
+ $wsLength = $i - $wsStart;
+ if ( $wsLength > 0 && substr( $accum, -$wsLength ) === str_repeat( ' ', $wsLength ) ) {
+ $accum = substr( $accum, 0, -$wsLength );
+ }
+ } else {
+ // No line to eat, just take the comment itself
+ $startPos = $i;
+ $endPos += 2;
+ }
+
+ $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
+ $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
+ $i = $endPos + 1;
}
- $accum .= htmlspecialchars( substr( $text, $i, $endpos - $i + 3 ) );
- #$inner = substr( $text, $i + 4, $endpos - $i - 4 );
- #$accum .= '<ext><name>!--</name><inner>' . htmlspecialchars( $inner ) . '</inner></ext>';
- $i = $endpos + 3;
continue;
}
$name = $matches[1];
++$i;
continue;
}
+
+ // Handle ignored tags
+ if ( in_array( $name, $ignoredTags ) ) {
+ $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) ) . '</ignore>';
+ $i = $tagEndPos + 1;
+ continue;
+ }
+
+ $tagStartPos = $i;
if ( $text[$tagEndPos-1] == '/' ) {
$attrEnd = $tagEndPos - 1;
$inner = null;
$close = '';
}
}
+ // <includeonly> and <noinclude> just become <ignore> tags
+ if ( in_array( $name, $ignoredElements ) ) {
+ $accum .= '<ignore>' . htmlspecialchars( substr( $text, $tagStartPos, $i - $tagStartPos ) )
+ . '</ignore>';
+ continue;
+ }
+
$accum .= '<ext>';
if ( $attrEnd <= $attrStart ) {
$attr = '';
// A heading must be open, otherwise \n wouldn't have been in the search list
assert( $piece['open'] == "\n" );
assert( $stackIndex == 0 );
- // Search back through the accumulator to see if it has a proper close
- // No efficient way to do this in PHP AFAICT: strrev, PCRE search with $ anchor
- // and rtrim are all O(N) in total size. Optimal would be O(N) in trailing
- // whitespace size only.
+ // Search back through the input to see if it has a proper close
+ // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient
$m = false;
$count = $piece['count'];
- if ( preg_match( "/(={{$count}})\s*$/", $accum, $m, 0, $count ) ) {
+ if ( preg_match( "/\s*(={{$count}})/A", $revText, $m, 0, strlen( $text ) - $i ) ) {
// Found match, output <h>
$count = min( strlen( $m[1] ), $count );
$element = "<h level=\"$count\" i=\"$headingIndex\">$accum</h>";
wfProfileOut( __METHOD__.'-makexml' );
wfProfileIn( __METHOD__.'-loadXML' );
$dom = new DOMDocument;
- if ( !$dom->loadXML( $topAccum ) ) {
- throw new MWException( __METHOD__.' generated invalid XML' );
+ wfSuppressWarnings();
+ $result = $dom->loadXML( $topAccum );
+ wfRestoreWarnings();
+ if ( !$result ) {
+ // Try running the XML through UtfNormal to get rid of invalid characters
+ $topAccum = UtfNormal::cleanUp( $topAccum );
+ $result = $dom->loadXML( $topAccum );
+ if ( !$result ) {
+ throw new MWException( __METHOD__.' generated invalid XML' );
+ }
}
wfProfileOut( __METHOD__.'-loadXML' );
wfProfileOut( __METHOD__ );
$dom = $this->preprocessToDom( $text );
$flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
- $text = $frame->expand( $dom, 0, $flags );
+ $text = $frame->expand( $dom, $flags );
wfProfileOut( $fname );
return $text;
# Flags
$found = false; # $text has been filled
$nowiki = false; # wiki markup in $text should be escaped
- $noparse = false; # Unsafe HTML tags should not be stripped, etc.
- $noargs = false; # Don't replace triple-brace arguments in $text
$isHTML = false; # $text is HTML, armour it against wikitext transformation
$forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered
$isDOM = false; # $text is a DOM node needing expansion
# In either case, return without further processing
$text = '{{' . $frame->implode( '|', $titleWithSpaces, $args ) . '}}';
$found = true;
- $noparse = true;
- $noargs = true;
}
}
$text = $this->getVariableValue( $id );
$this->mOutput->mContainsOldMagic = true;
$found = true;
- $noparse = true;
- $noargs = true;
}
}
$allArgs = array_merge( $initialArgs, $funcArgs );
}
- if (! is_callable($callback)) {
- if (is_array($callback))
- $callback = $callback[0];
- else
- die ("\nInvalid callback for $function: '$callback' ($flags)\n");
- }
-
$result = call_user_func_array( $callback, $allArgs );
$found = true;
- // The text is usually already parsed, doesn't need triple-brace tags expanded, etc.
- $noargs = true;
- $noparse = true;
-
if ( is_array( $result ) ) {
if ( isset( $result[0] ) ) {
$text = $result[0];
}
// Extract flags into the local scope
- // This allows callers to set flags such as nowiki, noparse, found, etc.
+ // This allows callers to set flags such as nowiki, found, etc.
extract( $result );
} else {
$text = $result;
}
# Do infinite loop check
if ( isset( $this->mTemplatePath[$titleText] ) ) {
- $noparse = true;
- $noargs = true;
$found = true;
$text = "[[$part1]]" . $this->insertStripItem( '<!-- WARNING: template loop detected -->' );
wfDebug( __METHOD__.": template loop broken at '$titleText'\n" );
$text = SpecialPage::capturePath( $title );
if ( is_string( $text ) ) {
$found = true;
- $noparse = true;
- $noargs = true;
$isHTML = true;
$this->disableCache();
}
if ( $this->ot['html'] && !$forceRawInterwiki ) {
$text = $this->interwikiTransclude( $title, 'render' );
$isHTML = true;
- $noparse = true;
} else {
$text = $this->interwikiTransclude( $title, 'raw' );
+ // Preprocess it like a template
+ $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
+ $isDOM = true;
}
$found = true;
}
wfProfileOut( __METHOD__ . '-loadtpl' );
}
- # Recursive parsing, escaping and link table handling
- # Only for HTML output
- if ( $nowiki && $found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
- if ( $isDOM ) {
- $text = $frame->expand( $text );
- }
- $text = wfEscapeWikiText( $text );
- } elseif ( !$this->ot['msg'] && $found ) {
- if ( $noargs ) {
- $newFrame = $frame->newChild();
- } else {
- # Clean up argument array
- $newFrame = $frame->newChild( $args, $title );
- # Add a new element to the templace recursion path
- $this->mTemplatePath[$titleText] = 1;
- }
-
- if ( !$noparse ) {
- if ( $isDOM ) {
- if ( $titleText !== false && count( $newFrame->args ) == 0 ) {
- # Expansion is eligible for the empty-frame cache
- if ( isset( $this->mTplExpandCache[$titleText] ) ) {
- $text = $this->mTplExpandCache[$titleText];
- } else {
- $text = $newFrame->expand( $text );
- $this->mTplExpandCache[$titleText] = $text;
- }
- } else {
- $text = $newFrame->expand( $text );
- }
- } else {
- $text = $this->replaceVariables( $text, $newFrame );
- }
+ # If we haven't found text to substitute by now, we're done
+ # Recover the source wikitext and return it
+ if ( !$found ) {
+ $text = '{{' . $frame->implode( '|', $titleWithSpaces, $args ) . '}}';
+ # Prune lower levels off the recursion check path
+ $this->mTemplatePath = $lastPathLevel;
+ wfProfileOut( $fname );
+ return $text;
+ }
- # strip woz 'ere 2004-07
+ # Expand DOM-style return values in a child frame
+ if ( $isDOM ) {
+ # Clean up argument array
+ $newFrame = $frame->newChild( $args, $title );
+ # Add a new element to the templace recursion path
+ $this->mTemplatePath[$titleText] = 1;
- # Bug 529: if the template begins with a table or block-level
- # element, it should be treated as beginning a new line.
- # This behaviour is somewhat controversial.
- if (!$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{
- $text = "\n" . $text;
- }
- } elseif ( !$noargs ) {
- # $noparse and !$noargs
- # Just replace the arguments, not any double-brace items
- # This is used for rendered interwiki transclusion
- if ( $isDOM ) {
- $text = $newFrame->expand( $text, 0, PPFrame::NO_TEMPLATES );
+ if ( $titleText !== false && count( $newFrame->args ) == 0 ) {
+ # Expansion is eligible for the empty-frame cache
+ if ( isset( $this->mTplExpandCache[$titleText] ) ) {
+ $text = $this->mTplExpandCache[$titleText];
} else {
- $text = $this->replaceVariables( $text, $newFrame, true );
+ $text = $newFrame->expand( $text );
+ $this->mTplExpandCache[$titleText] = $text;
}
- } elseif ( $isDOM ) {
- $text = $frame->expand( $text );
+ } else {
+ $text = $newFrame->expand( $text );
}
- } elseif ( $isDOM ) {
- $text = $frame->expand( $text, 0, PPFrame::NO_TEMPLATES | PPFrame::NO_ARGS );
}
+ # Replace raw HTML by a placeholder
+ # Add a blank line preceding, to prevent it from mucking up
+ # immediately preceding headings
+ if ( $isHTML ) {
+ $text = "\n\n" . $this->insertStripItem( $text );
+ }
+ # Escape nowiki-style return values
+ elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
+ $text = wfEscapeWikiText( $text );
+ }
+ # Bug 529: if the template begins with a table or block-level
+ # element, it should be treated as beginning a new line.
+ # This behaviour is somewhat controversial.
+ elseif ( !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{
+ $text = "\n" . $text;
+ }
+
# Prune lower levels off the recursion check path
$this->mTemplatePath = $lastPathLevel;
- if ( $found && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
+ if ( !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
# Error, oversize inclusion
$text = "[[$originalTitle]]" .
$this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' );
- $noparse = true;
- $noargs = true;
- }
-
- if ( !$found ) {
- wfProfileOut( $fname );
- return '{{' . $frame->implode( '|', $titleWithSpaces, $args ) . '}}';
- } else {
- wfProfileIn( __METHOD__ . '-placeholders' );
- if ( $isHTML ) {
- # Replace raw HTML by a placeholder
- # Add a blank line preceding, to prevent it from mucking up
- # immediately preceding headings
- $text = "\n\n" . $this->insertStripItem( $text );
- }
- wfProfileOut( __METHOD__ . '-placeholders' );
}
- # Prune lower levels off the recursion check path
- $this->mTemplatePath = $lastPathLevel;
-
- if ( !$found ) {
- wfProfileOut( $fname );
- return '{{' . $frame->implode( '|', $titleWithSpaces, $args ) . '}}';
- } else {
- wfProfileOut( $fname );
- return $text;
- }
+ wfProfileOut( $fname );
+ return $text;
}
/**
* and its redirect destination title. Cached.
*/
function getTemplateDom( $title ) {
+ $cacheTitle = $title;
$titleText = $title->getPrefixedDBkey();
if ( isset( $this->mTplRedirCache[$titleText] ) ) {
return array( false, $title );
}
- # If there are any <onlyinclude> tags, only include them
- if ( !$this->ot['msg'] ) {
- if ( in_string( '<onlyinclude>', $text ) && in_string( '</onlyinclude>', $text ) ) {
- $replacer = new OnlyIncludeReplacer;
- StringUtils::delimiterReplaceCallback( '<onlyinclude>', '</onlyinclude>',
- array( &$replacer, 'replace' ), $text );
- $text = $replacer->output;
- }
- # Remove <noinclude> sections and <includeonly> tags
- $text = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $text );
- $text = strtr( $text, array( '<includeonly>' => '' , '</includeonly>' => '' ) );
+ $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
+ $this->mTplDomCache[ $titleText ] = $dom;
+
+ if (! $title->equals($cacheTitle)) {
+ $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
+ array( $title->getNamespace(),$cdb = $title->getDBkey() );
}
- $dom = $this->preprocessToDom( $text );
- $this->mTplDomCache[$titleText] = $dom;
return array( $dom, $title );
}
$marker = "{$this->mUniqPrefix}-$name-" . sprintf('%08X', $n++) . $this->mMarkerSuffix;
if ( $this->ot['html'] ) {
- if ( $name == '!--' ) {
- return '';
- }
$name = strtolower( $name );
$params = Sanitizer::decodeTagAttributes( $attrText );
}
}
} else {
- if ( $name == '!--' ) {
- $output = '<!--' . $content . '-->';
+ if ( $content === null ) {
+ $output = "<$name$attrText/>";
} else {
- if ( $content === null ) {
- $output = "<$name$attrText/>";
- } else {
- $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
- $output = "<$name$attrText>$content$close";
- }
+ $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
+ $output = "<$name$attrText>$content$close";
}
}
$prevtoclevel = 0;
$markerRegex = "{$this->mUniqPrefix}-h-(\d+)-{$this->mMarkerSuffix}";
$baseTitleText = $this->mTitle->getPrefixedDBkey();
+ $tocraw = array();
foreach( $matches[3] as $headline ) {
$isTemplate = false;
}
if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) {
$toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel);
+ $tocraw[] = array( 'toclevel' => $toclevel, 'level' => $level, 'line' => $tocline, 'number' => $numbering );
}
# give headline the correct <h#> tag
if( $showEditLink && $sectionIndex !== false ) {
- if( $isTemplate )
- $editlink = $sk->editSectionLinkForOther($titleText, $sectionIndex);
- else
+ if( $isTemplate ) {
+ # Put a T flag in the section identifier, to indicate to extractSections()
+ # that sections inside <includeonly> should be counted.
+ $editlink = $sk->editSectionLinkForOther($titleText, "T-$sectionIndex");
+ } else {
$editlink = $sk->editSectionLink($this->mTitle, $sectionIndex, $headlineHint);
+ }
} else {
$editlink = '';
}
$headlineCount++;
}
+ $this->mOutput->setSections( $tocraw );
+
# Never ever show TOC if no headers
if( $numVisible < 1 ) {
$enoughToc = false;
$userText = wfEscapeWikiText( $username );
$nickText = wfEscapeWikiText( $nickname );
if ( $user->isAnon() ) {
- return wfMsgForContent( 'signature-anon', $userText, $nickText );
+ return wfMsgExt( 'signature-anon', array( 'content', 'parsemag' ), $userText, $nickText );
} else {
- return wfMsgForContent( 'signature', $userText, $nickText );
+ return wfMsgExt( 'signature', array( 'content', 'parsemag' ), $userText, $nickText );
}
}
* @return string Signature text
*/
function cleanSig( $text, $parsing = false ) {
- global $wgTitle;
- $this->startExternalParse( $wgTitle, new ParserOptions(), $parsing ? OT_WIKI : OT_MSG );
+ if ( !$parsing ) {
+ global $wgTitle;
+ $this->startExternalParse( $wgTitle, new ParserOptions(), OT_MSG );
+ }
+ # FIXME: regex doesn't respect extension tags or nowiki
+ # => Move this logic to braceSubstitution()
$substWord = MagicWord::get( 'subst' );
$substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
$substText = '{{' . $substWord->getSynonym( 0 );
$text = preg_replace( $substRegex, $substText, $text );
$text = $this->cleanSigInSig( $text );
- $text = $this->replaceVariables( $text );
+ $dom = $this->preprocessToDom( $text );
+ $frame = new PPFrame( $this );
+ $text = $frame->expand( $dom->documentElement );
+
+ if ( !$parsing ) {
+ $text = $this->mStripState->unstripBoth( $text );
+ }
- $this->clearState();
return $text;
}
$this->setOutputType( OT_MSG );
$this->clearState();
$text = $this->replaceVariables( $text );
+ $text = $this->mStripState->unstripBoth( $text );
$executing = false;
wfProfileOut($fname);
/**
* Replace <!--LINK--> link placeholders with actual links, in the buffer
* Placeholders created in Skin::makeLinkObj()
- * Returns an array of links found, indexed by PDBK:
- * 0 - broken
- * 1 - normal link
- * 2 - stub
+ * Returns an array of link CSS classes, indexed by PDBK.
* $options is a bit field, RLH_FOR_UPDATE to select for update
*/
function replaceLinkHolders( &$text, $options = 0 ) {
$pdbks = array();
$colours = array();
+ $linkcolour_ids = array();
$sk = $this->mOptions->getSkin();
$linkCache =& LinkCache::singleton();
# Check if it's a static known link, e.g. interwiki
if ( $title->isAlwaysKnown() ) {
- $colours[$pdbk] = 1;
+ $colours[$pdbk] = '';
} elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
- $colours[$pdbk] = 1;
+ $colours[$pdbk] = '';
$this->mOutput->addLink( $title, $id );
} elseif ( $linkCache->isBadLink( $pdbk ) ) {
- $colours[$pdbk] = 0;
+ $colours[$pdbk] = 'new';
} elseif ( $title->getNamespace() == NS_SPECIAL && !SpecialPage::exists( $pdbk ) ) {
- $colours[$pdbk] = 0;
+ $colours[$pdbk] = 'new';
} else {
# Not in the link cache, add it to the query
if ( !isset( $current ) ) {
# Fetch data and form into an associative array
# non-existent = broken
- # 1 = known
- # 2 = stub
while ( $s = $dbr->fetchObject($res) ) {
$title = Title::makeTitle( $s->page_namespace, $s->page_title );
$pdbk = $title->getPrefixedDBkey();
$linkCache->addGoodLinkObj( $s->page_id, $title );
$this->mOutput->addLink( $title, $s->page_id );
-
- $colours[$pdbk] = ( $threshold == 0 || (
- $s->page_len >= $threshold || # always true if $threshold <= 0
- $s->page_is_redirect ||
- !Namespace::isContent( $s->page_namespace ) )
- ? 1 : 2 );
+ $colours[$pdbk] = $sk->getLinkColour( $s, $threshold );
+ //add id to the extension todolist
+ $linkcolour_ids[$s->page_id] = $pdbk;
}
+ //pass an array of page_ids to an extension
+ wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
}
wfProfileOut( $fname.'-check' );
// set pdbk and colour
$pdbks[$key] = $varPdbk;
- if ( $threshold > 0 ) {
- $size = $s->page_len;
- if ( $s->page_is_redirect || $s->page_namespace != 0 || $size >= $threshold ) {
- $colours[$varPdbk] = 1;
- } else {
- $colours[$varPdbk] = 2;
- }
- }
- else {
- $colours[$varPdbk] = 1;
- }
+ $colours[$varPdbk] = $sk->getLinkColour( $s, $threshold );
+ $linkcolour_ids[$s->page_id] = $pdbk;
}
+ wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
}
// check if the object is a variant of a category
$pdbk = $pdbks[$key];
$searchkey = "<!--LINK $key-->";
$title = $this->mLinkHolders['titles'][$key];
- if ( empty( $colours[$pdbk] ) ) {
+ if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) {
$linkCache->addBadLinkObj( $title );
- $colours[$pdbk] = 0;
+ $colours[$pdbk] = 'new';
$this->mOutput->addLink( $title, 0 );
$replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
$this->mLinkHolders['texts'][$key],
$this->mLinkHolders['queries'][$key] );
- } elseif ( $colours[$pdbk] == 1 ) {
- $replacePairs[$searchkey] = $sk->makeKnownLinkObj( $title,
- $this->mLinkHolders['texts'][$key],
- $this->mLinkHolders['queries'][$key] );
- } elseif ( $colours[$pdbk] == 2 ) {
- $replacePairs[$searchkey] = $sk->makeStubLinkObj( $title,
+ } else {
+ $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk],
$this->mLinkHolders['texts'][$key],
$this->mLinkHolders['queries'][$key] );
}
*
* External callers should use the getSection and replaceSection methods.
*
- * @param $text Page wikitext
- * @param $section Numbered section. 0 pulls the text before the first
- * heading; other numbers will pull the given section
- * along with its lower-level subsections. If the section is
- * not found, $mode=get will return $newtext, and
- * $mode=replace will return $text.
- * @param $mode One of "get" or "replace"
- * @param $newText Replacement text for section data.
+ * @param string $text Page wikitext
+ * @param string $section A section identifier string of the form:
+ * <flag1> - <flag2> - ... - <section number>
+ *
+ * Currently the only recognised flag is "T", which means the target section number
+ * was derived during a template inclusion parse, in other words this is a template
+ * section edit link. If no flags are given, it was an ordinary section edit link.
+ * This flag is required to avoid a section numbering mismatch when a section is
+ * enclosed by <includeonly> (bug 6563).
+ *
+ * The section number 0 pulls the text before the first heading; other numbers will
+ * pull the given section along with its lower-level subsections. If the section is
+ * not found, $mode=get will return $newtext, and $mode=replace will return $text.
+ *
+ * @param string $mode One of "get" or "replace"
+ * @param string $newText Replacement text for section data.
* @return string for "get", the extracted section text.
* for "replace", the whole page with the section replaced.
*/
private function extractSections( $text, $section, $mode, $newText='' ) {
+ global $wgTitle;
$this->clearState();
+ $this->mTitle = $wgTitle; // not generally used but removes an ugly failure mode
$this->mOptions = new ParserOptions;
$this->setOutputType( OT_WIKI );
$curIndex = 0;
$outText = '';
$frame = new PPFrame( $this );
+ // Process section extraction flags
+ $flags = 0;
+ $sectionParts = explode( '-', $section );
+ $sectionIndex = array_pop( $sectionParts );
+ foreach ( $sectionParts as $part ) {
+ if ( $part == 'T' ) {
+ $flags |= self::PTD_FOR_INCLUSION;
+ }
+ }
// Preprocess the text
- $dom = $this->preprocessToDom( $text );
+ $dom = $this->preprocessToDom( $text, $flags );
$root = $dom->documentElement;
// <h> nodes indicate section breaks
$node = $root->firstChild;
// Find the target section
- if ( $section == 0 ) {
+ if ( $sectionIndex == 0 ) {
// Section zero doesn't nest, level=big
$targetLevel = 1000;
} else {
while ( $node ) {
if ( $node->nodeName == 'h' ) {
- if ( $curIndex + 1 == $section ) {
+ if ( $curIndex + 1 == $sectionIndex ) {
break;
}
$curIndex++;
}
if ( $mode == 'replace' ) {
- $outText .= $frame->expand( $node );
+ $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
}
$node = $node->nextSibling;
}
if ( $node->nodeName == 'h' ) {
$curIndex++;
$curLevel = $node->getAttribute( 'level' );
- if ( $curIndex != $section && $curLevel <= $targetLevel ) {
+ if ( $curIndex != $sectionIndex && $curLevel <= $targetLevel ) {
break;
}
}
if ( $mode == 'get' ) {
- $outText .= $frame->expand( $node );
+ $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
}
$node = $node->nextSibling;
} while ( $node );
// stripped by the editor, so we need both newlines to restore the paragraph gap
$outText .= $newText . "\n\n";
while ( $node ) {
- $outText .= $frame->expand( $node );
+ $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
$node = $node->nextSibling;
}
}
*
* If a section contains subsections, these are also returned.
*
- * @param $text String: text to look in
- * @param $section Integer: section number
- * @param $deftext: default to return if section is not found
+ * @param string $text text to look in
+ * @param string $section section identifier
+ * @param string $deftext default to return if section is not found
* @return string text of the requested section
*/
public function getSection( $text, $section, $deftext='' ) {
function srvus( $text ) {
$text = $this->replaceVariables( $text );
$text = $this->mStripState->unstripBoth( $text );
+ $text = Sanitizer::removeHTMLtags( $text );
return $text;
}
}
*/
class PPFrame {
var $parser, $title;
+ var $titleCache;
const NO_ARGS = 1;
const NO_TEMPLATES = 2;
+ const STRIP_COMMENTS = 4;
+ const NO_IGNORE = 8;
+
+ const RECOVER_ORIG = 11;
/**
* Construct a new preprocessor frame.
function __construct( $parser ) {
$this->parser = $parser;
$this->title = $parser->mTitle;
+ $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
}
/**
$name = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
} else {
// Named parameter
- $name = $this->expand( $nameNodes->item( 0 ) );
+ $name = $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS );
}
$value = $xpath->query( 'value', $arg );
* using the current context
* @param $root the node
*/
- function expand( $root, $shallowFlags = 0, $deepFlags = 0 ) {
+ function expand( $root, $flags = 0 ) {
if ( is_string( $root ) ) {
return $root;
}
{
return $this->parser->insertStripItem( '<!-- node-count limit exceeded -->' );
}
- $flags = $shallowFlags | $deepFlags;
if ( is_array( $root ) ) {
$s = '';
foreach ( $root as $node ) {
- $s .= $this->expand( $node, 0, $deepFlags );
+ $s .= $this->expand( $node, $flags );
}
} elseif ( $root instanceof DOMNodeList ) {
$s = '';
foreach ( $root as $node ) {
- $s .= $this->expand( $node, 0, $deepFlags );
+ $s .= $this->expand( $node, $flags );
}
} elseif ( $root instanceof DOMNode ) {
if ( $root->nodeType == XML_TEXT_NODE ) {
$title = $titles->item( 0 );
$parts = $xpath->query( 'part', $root );
if ( $flags & self::NO_TEMPLATES ) {
- $s = '{{' . $this->implodeWithFlags( '|', 0, $deepFlags, $title, $parts ) . '}}';
+ $s = '{{' . $this->implodeWithFlags( '|', $flags, $title, $parts ) . '}}';
} else {
$lineStart = $root->getAttribute( 'lineStart' );
$params = array(
$title = $titles->item( 0 );
$parts = $xpath->query( 'part', $root );
if ( $flags & self::NO_ARGS || $this->parser->ot['msg'] ) {
- $s = '{{{' . $this->implode( '|', 0, $deepFlags, $title, $parts ) . '}}}';
+ $s = '{{{' . $this->implodeWithFlags( '|', $flags, $title, $parts ) . '}}}';
} else {
$params = array( 'title' => $title, 'parts' => $parts, 'text' => 'FIXME' );
$s = $this->parser->argSubstitution( $params, $this );
}
+ } elseif ( $root->nodeName == 'comment' ) {
+ # HTML-style comment
+ if ( $this->parser->ot['html']
+ || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
+ || ( $flags & self::STRIP_COMMENTS ) )
+ {
+ $s = '';
+ } else {
+ $s = $root->textContent;
+ }
+ } elseif ( $root->nodeName == 'ignore' ) {
+ # Output suppression used by <includeonly> etc.
+ # OT_WIKI will only respect <ignore> in substed templates.
+ # The other output types respect it unless NO_IGNORE is set.
+ # extractSections() sets NO_IGNORE and so never respects it.
+ if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) {
+ $s = $root->textContent;
+ } else {
+ $s = '';
+ }
} elseif ( $root->nodeName == 'ext' ) {
# Extension tag
$xpath = new DOMXPath( $root->ownerDocument );
$s = $this->parser->extensionSubstitution( $params, $this );
} elseif ( $root->nodeName == 'h' ) {
# Heading
- $s = $this->expand( $root->childNodes, 0, $deepFlags );
+ $s = $this->expand( $root->childNodes, $flags );
if ( $this->parser->ot['html'] ) {
# Insert heading index marker
$serial = count( $this->parser->mHeadings ) - 1;
$marker = "{$this->parser->mUniqPrefix}-h-$serial-{$this->parser->mMarkerSuffix}";
$count = $root->getAttribute( 'level' );
-
- // FIXME: bug-for-bug with old parser
- // Lose whitespace for no apparent reason
- // Remove this after differential testing is done
- if ( true ) {
- // Good version
- $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
- } else {
- // Bad version
- if ( preg_match( '/^(={1,6})(.*?)(={1,6})\s*?$/', $s, $m ) ) {
- if ( $m[2] != '' ) {
- $s = $m[1] . $marker . $m[2] . $m[3];
- }
- }
- }
+ $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
$this->parser->mStripState->general->setPair( $marker, '' );
}
} else {
if ( $node->nodeType == XML_TEXT_NODE ) {
$s .= $node->nodeValue;
} elseif ( $node->nodeType == XML_ELEMENT_NODE ) {
- $s .= $this->expand( $node, 0, $deepFlags );
+ $s .= $this->expand( $node, $flags );
}
}
}
return $s;
}
- function implodeWithFlags( $sep, $shallowFlags, $deepFlags /*, ... */ ) {
- $args = array_slice( func_get_args(), 3 );
+ function implodeWithFlags( $sep, $flags /*, ... */ ) {
+ $args = array_slice( func_get_args(), 2 );
$first = true;
$s = '';
} else {
$s .= $sep;
}
- $s .= $this->expand( $node, $shallowFlags, $deepFlags );
+ $s .= $this->expand( $node, $flags );
}
}
return $s;
function implode( $sep /*, ... */ ) {
$args = func_get_args();
- $args = array_merge( array_slice( $args, 0, 1 ), array( 0, 0 ), array_slice( $args, 1 ) );
+ $args = array_merge( array_slice( $args, 0, 1 ), array( 0 ), array_slice( $args, 1 ) );
return call_user_func_array( array( $this, 'implodeWithFlags' ), $args );
}
+ /**
+ * Split an <arg> or <template> node into a three-element array:
+ * DOMNode name, string index and DOMNode value
+ */
+ function splitBraceNode( $node ) {
+ $xpath = new DOMXPath( $node->ownerDocument );
+ $names = $xpath->query( 'name', $node );
+ $values = $xpath->query( 'value', $node );
+ if ( !$names->length || !$values->length ) {
+ throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
+ }
+ $name = $names->item( 0 );
+ $index = $name->getAttribute( 'index' );
+ return array( $name, $index, $values->item( 0 ) );
+ }
+
+ /**
+ * Split an <ext> node into an associative array containing name, attr, inner and close
+ * All values in the resulting array are DOMNodes. Inner and close are optional.
+ */
+ function splitExtNode( $node ) {
+ $xpath = new DOMXPath( $node->ownerDocument );
+ $names = $xpath->query( 'name', $node );
+ $attrs = $xpath->query( 'attr', $node );
+ $inners = $xpath->query( 'inner', $node );
+ $closes = $xpath->query( 'close', $node );
+ if ( !$names->length || !$attrs->length ) {
+ throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
+ }
+ $parts = array(
+ 'name' => $names->item( 0 ),
+ 'attr' => $attrs->item( 0 ) );
+ if ( $inners->length ) {
+ $parts['inner'] = $inners->item( 0 );
+ }
+ if ( $closes->length ) {
+ $parts['close'] = $closes->item( 0 );
+ }
+ return $parts;
+ }
+
function __toString() {
return 'frame{}';
}
+
+ function getPDBK( $level = false ) {
+ if ( $level === false ) {
+ return $this->title->getPrefixedDBkey();
+ } else {
+ return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
+ }
+ }
}
/**
* Expansion frame with template arguments
*/
class PPTemplateFrame extends PPFrame {
- public $parser, $args, $parent, $serial;
+ var $parser, $args, $parent;
+ var $titleCache;
function __construct( $parser, $parent = false, $args = array(), $title = false ) {
$this->parser = $parser;
$this->parent = $parent;
$this->args = $args;
$this->title = $title;
+ $this->titleCache = $parent->titleCache;
+ $this->titleCache[] = $title ? $title->getPrefixedDBkey() : false;
}
function __toString() {