* 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
const COLON_STATE_COMMENTDASH = 6;
const COLON_STATE_COMMENTDASHDASH = 7;
+ // Flags for preprocessToDom
+ const PTD_FOR_INCLUSION = 1;
+
/**#@+
* @private
*/
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>";
# 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;
}
}
$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, 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, 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;
}
/**
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 );
-
+ $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
$this->mTplDomCache[ $titleText ] = $dom;
if (! $title->equals($cacheTitle)) {
$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;
*
* 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 ( $node->nodeName == 'h' ) {
$curIndex++;
$curLevel = $node->getAttribute( 'level' );
- if ( $curIndex != $section && $curLevel <= $targetLevel ) {
+ if ( $curIndex != $sectionIndex && $curLevel <= $targetLevel ) {
break;
}
}
*
* 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 RECOVER_ORIG = 3;
+ 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 );
$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 );
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() {