/** * @throws MWException * @param string|PPNode_DOM|DOMDocument $root * @param int $flags * @return string */ public function expand($root, $flags = 0) { static $expansionDepth = 0; if (is_string($root)) { return $root; } if (++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount()) { $this->parser->limitationWarn('node-count-exceeded', $this->parser->mPPNodeCount, $this->parser->mOptions->getMaxPPNodeCount()); return '<span class="error">Node-count limit exceeded</span>'; } if ($expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth()) { $this->parser->limitationWarn('expansion-depth-exceeded', $expansionDepth, $this->parser->mOptions->getMaxPPExpandDepth()); return '<span class="error">Expansion depth limit exceeded</span>'; } ++$expansionDepth; if ($expansionDepth > $this->parser->mHighestExpansionDepth) { $this->parser->mHighestExpansionDepth = $expansionDepth; } if ($root instanceof PPNode_DOM) { $root = $root->node; } if ($root instanceof DOMDocument) { $root = $root->documentElement; } $outStack = array('', ''); $iteratorStack = array(false, $root); $indexStack = array(0, 0); while (count($iteratorStack) > 1) { $level = count($outStack) - 1; $iteratorNode =& $iteratorStack[$level]; $out =& $outStack[$level]; $index =& $indexStack[$level]; if ($iteratorNode instanceof PPNode_DOM) { $iteratorNode = $iteratorNode->node; } if (is_array($iteratorNode)) { if ($index >= count($iteratorNode)) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode[$index]; $index++; } } elseif ($iteratorNode instanceof DOMNodeList) { if ($index >= $iteratorNode->length) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode->item($index); $index++; } } else { // Copy to $contextNode and then delete from iterator stack, // because this is not an iterator but we do have to execute it once $contextNode = $iteratorStack[$level]; $iteratorStack[$level] = false; } if ($contextNode instanceof PPNode_DOM) { $contextNode = $contextNode->node; } $newIterator = false; if ($contextNode === false) { // nothing to do } elseif (is_string($contextNode)) { $out .= $contextNode; } elseif (is_array($contextNode) || $contextNode instanceof DOMNodeList) { $newIterator = $contextNode; } elseif ($contextNode instanceof DOMNode) { if ($contextNode->nodeType == XML_TEXT_NODE) { $out .= $contextNode->nodeValue; } elseif ($contextNode->nodeName == 'template') { # Double-brace expansion $xpath = new DOMXPath($contextNode->ownerDocument); $titles = $xpath->query('title', $contextNode); $title = $titles->item(0); $parts = $xpath->query('part', $contextNode); if ($flags & PPFrame::NO_TEMPLATES) { $newIterator = $this->virtualBracketedImplode('{{', '|', '}}', $title, $parts); } else { $lineStart = $contextNode->getAttribute('lineStart'); $params = array('title' => new PPNode_DOM($title), 'parts' => new PPNode_DOM($parts), 'lineStart' => $lineStart); $ret = $this->parser->braceSubstitution($params, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } elseif ($contextNode->nodeName == 'tplarg') { # Triple-brace expansion $xpath = new DOMXPath($contextNode->ownerDocument); $titles = $xpath->query('title', $contextNode); $title = $titles->item(0); $parts = $xpath->query('part', $contextNode); if ($flags & PPFrame::NO_ARGS) { $newIterator = $this->virtualBracketedImplode('{{{', '|', '}}}', $title, $parts); } else { $params = array('title' => new PPNode_DOM($title), 'parts' => new PPNode_DOM($parts)); $ret = $this->parser->argSubstitution($params, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } elseif ($contextNode->nodeName == 'comment') { # HTML-style comment # Remove it in HTML, pre+remove and STRIP_COMMENTS modes # Not in RECOVER_COMMENTS mode (msgnw) though. if (($this->parser->ot['html'] || $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() || $flags & PPFrame::STRIP_COMMENTS) && !($flags & PPFrame::RECOVER_COMMENTS)) { $out .= ''; } elseif ($this->parser->ot['wiki'] && !($flags & PPFrame::RECOVER_COMMENTS)) { # Add a strip marker in PST mode so that pstPass2() can # run some old-fashioned regexes on the result. # Not in RECOVER_COMMENTS mode (extractSections) though. $out .= $this->parser->insertStripItem($contextNode->textContent); } else { # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove $out .= $contextNode->textContent; } } elseif ($contextNode->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 & PPFrame::NO_IGNORE) { $out .= $contextNode->textContent; } else { $out .= ''; } } elseif ($contextNode->nodeName == 'ext') { # Extension tag $xpath = new DOMXPath($contextNode->ownerDocument); $names = $xpath->query('name', $contextNode); $attrs = $xpath->query('attr', $contextNode); $inners = $xpath->query('inner', $contextNode); $closes = $xpath->query('close', $contextNode); if ($flags & PPFrame::NO_TAGS) { $s = '<' . $this->expand($names->item(0), $flags); if ($attrs->length > 0) { $s .= $this->expand($attrs->item(0), $flags); } if ($inners->length > 0) { $s .= '>' . $this->expand($inners->item(0), $flags); if ($closes->length > 0) { $s .= $this->expand($closes->item(0), $flags); } } else { $s .= '/>'; } $out .= $s; } else { $params = array('name' => new PPNode_DOM($names->item(0)), 'attr' => $attrs->length > 0 ? new PPNode_DOM($attrs->item(0)) : null, 'inner' => $inners->length > 0 ? new PPNode_DOM($inners->item(0)) : null, 'close' => $closes->length > 0 ? new PPNode_DOM($closes->item(0)) : null); $out .= $this->parser->extensionSubstitution($params, $this); } } elseif ($contextNode->nodeName == 'h') { # Heading $s = $this->expand($contextNode->childNodes, $flags); # Insert a heading marker only for <h> children of <root> # This is to stop extractSections from going over multiple tree levels if ($contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html']) { # Insert heading index marker $headingIndex = $contextNode->getAttribute('i'); $titleText = $this->title->getPrefixedDBkey(); $this->parser->mHeadings[] = array($titleText, $headingIndex); $serial = count($this->parser->mHeadings) - 1; $marker = Parser::MARKER_PREFIX . "-h-{$serial}-" . Parser::MARKER_SUFFIX; $count = $contextNode->getAttribute('level'); $s = substr($s, 0, $count) . $marker . substr($s, $count); $this->parser->mStripState->addGeneral($marker, ''); } $out .= $s; } else { # Generic recursive expansion $newIterator = $contextNode->childNodes; } } else { throw new MWException(__METHOD__ . ': Invalid parameter type'); } if ($newIterator !== false) { if ($newIterator instanceof PPNode_DOM) { $newIterator = $newIterator->node; } $outStack[] = ''; $iteratorStack[] = $newIterator; $indexStack[] = 0; } elseif ($iteratorStack[$level] === false) { // Return accumulated value to parent // With tail recursion while ($iteratorStack[$level] === false && $level > 0) { $outStack[$level - 1] .= $out; array_pop($outStack); array_pop($iteratorStack); array_pop($indexStack); $level--; } } } --$expansionDepth; return $outStack[0]; }
/** * Parser function to extension tag adaptor * @param Parser $parser * @param PPFrame $frame * @param PPNode[] $args * @return string */ public static function tagObj($parser, $frame, $args) { if (!count($args)) { return ''; } $tagName = strtolower(trim($frame->expand(array_shift($args)))); if (count($args)) { $inner = $frame->expand(array_shift($args)); } else { $inner = null; } $attributes = []; foreach ($args as $arg) { $bits = $arg->splitArg(); if (strval($bits['index']) === '') { $name = trim($frame->expand($bits['name'], PPFrame::STRIP_COMMENTS)); $value = trim($frame->expand($bits['value'])); if (preg_match('/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m)) { $value = isset($m[1]) ? $m[1] : ''; } $attributes[$name] = $value; } } $stripList = $parser->getStripList(); if (!in_array($tagName, $stripList)) { // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag $attrText = ''; foreach ($attributes as $name => $value) { $attrText .= ' ' . htmlspecialchars($name) . '="' . htmlspecialchars($value) . '"'; } if ($inner === null) { return "<{$tagName}{$attrText}/>"; } return "<{$tagName}{$attrText}>{$inner}</{$tagName}>"; } $params = ['name' => $tagName, 'inner' => $inner, 'attributes' => $attributes, 'close' => "</{$tagName}>"]; return $parser->extensionSubstitution($params, $frame); }
/** * Parser function to extension tag adaptor * @param Parser $parser * @param PPFrame $frame * @param array $args * @return string */ public static function tagObj($parser, $frame, $args) { if (!count($args)) { return ''; } $tagName = strtolower(trim($frame->expand(array_shift($args)))); if (count($args)) { $inner = $frame->expand(array_shift($args)); } else { $inner = null; } $stripList = $parser->getStripList(); if (!in_array($tagName, $stripList)) { return '<span class="error">' . wfMessage('unknown_extension_tag', $tagName)->inContentLanguage()->text() . '</span>'; } $attributes = array(); foreach ($args as $arg) { $bits = $arg->splitArg(); if (strval($bits['index']) === '') { $name = trim($frame->expand($bits['name'], PPFrame::STRIP_COMMENTS)); $value = trim($frame->expand($bits['value'])); if (preg_match('/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m)) { $value = isset($m[1]) ? $m[1] : ''; } $attributes[$name] = $value; } } $params = array('name' => $tagName, 'inner' => $inner, 'attributes' => $attributes, 'close' => "</{$tagName}>"); return $parser->extensionSubstitution($params, $frame); }
/** * @throws MWException * @param string|PPNode $root * @param int $flags * @return string */ public function expand($root, $flags = 0) { static $expansionDepth = 0; if (is_string($root)) { return $root; } if (++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount()) { $this->parser->limitationWarn('node-count-exceeded', $this->parser->mPPNodeCount, $this->parser->mOptions->getMaxPPNodeCount()); return '<span class="error">Node-count limit exceeded</span>'; } if ($expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth()) { $this->parser->limitationWarn('expansion-depth-exceeded', $expansionDepth, $this->parser->mOptions->getMaxPPExpandDepth()); return '<span class="error">Expansion depth limit exceeded</span>'; } ++$expansionDepth; if ($expansionDepth > $this->parser->mHighestExpansionDepth) { $this->parser->mHighestExpansionDepth = $expansionDepth; } $outStack = array('', ''); $iteratorStack = array(false, $root); $indexStack = array(0, 0); while (count($iteratorStack) > 1) { $level = count($outStack) - 1; $iteratorNode =& $iteratorStack[$level]; $out =& $outStack[$level]; $index =& $indexStack[$level]; if (is_array($iteratorNode)) { if ($index >= count($iteratorNode)) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode[$index]; $index++; } } elseif ($iteratorNode instanceof PPNode_Hash_Array) { if ($index >= $iteratorNode->getLength()) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode->item($index); $index++; } } else { // Copy to $contextNode and then delete from iterator stack, // because this is not an iterator but we do have to execute it once $contextNode = $iteratorStack[$level]; $iteratorStack[$level] = false; } $newIterator = false; if ($contextNode === false) { // nothing to do } elseif (is_string($contextNode)) { $out .= $contextNode; } elseif (is_array($contextNode) || $contextNode instanceof PPNode_Hash_Array) { $newIterator = $contextNode; } elseif ($contextNode instanceof PPNode_Hash_Attr) { // No output } elseif ($contextNode instanceof PPNode_Hash_Text) { $out .= $contextNode->value; } elseif ($contextNode instanceof PPNode_Hash_Tree) { if ($contextNode->name == 'template') { # Double-brace expansion $bits = $contextNode->splitTemplate(); if ($flags & PPFrame::NO_TEMPLATES) { $newIterator = $this->virtualBracketedImplode('{{', '|', '}}', $bits['title'], $bits['parts']); } else { $ret = $this->parser->braceSubstitution($bits, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } elseif ($contextNode->name == 'tplarg') { # Triple-brace expansion $bits = $contextNode->splitTemplate(); if ($flags & PPFrame::NO_ARGS) { $newIterator = $this->virtualBracketedImplode('{{{', '|', '}}}', $bits['title'], $bits['parts']); } else { $ret = $this->parser->argSubstitution($bits, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } elseif ($contextNode->name == 'comment') { # HTML-style comment # Remove it in HTML, pre+remove and STRIP_COMMENTS modes # Not in RECOVER_COMMENTS mode (msgnw) though. if (($this->parser->ot['html'] || $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() || $flags & PPFrame::STRIP_COMMENTS) && !($flags & PPFrame::RECOVER_COMMENTS)) { $out .= ''; } elseif ($this->parser->ot['wiki'] && !($flags & PPFrame::RECOVER_COMMENTS)) { # Add a strip marker in PST mode so that pstPass2() can # run some old-fashioned regexes on the result. # Not in RECOVER_COMMENTS mode (extractSections) though. $out .= $this->parser->insertStripItem($contextNode->firstChild->value); } else { # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove $out .= $contextNode->firstChild->value; } } elseif ($contextNode->name == '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 & PPFrame::NO_IGNORE) { $out .= $contextNode->firstChild->value; } else { // $out .= ''; } } elseif ($contextNode->name == 'ext') { # Extension tag $bits = $contextNode->splitExt() + array('attr' => null, 'inner' => null, 'close' => null); if ($flags & PPFrame::NO_TAGS) { $s = '<' . $bits['name']->firstChild->value; if ($bits['attr']) { $s .= $bits['attr']->firstChild->value; } if ($bits['inner']) { $s .= '>' . $bits['inner']->firstChild->value; if ($bits['close']) { $s .= $bits['close']->firstChild->value; } } else { $s .= '/>'; } $out .= $s; } else { $out .= $this->parser->extensionSubstitution($bits, $this); } } elseif ($contextNode->name == 'h') { # Heading if ($this->parser->ot['html']) { # Expand immediately and insert heading index marker $s = ''; for ($node = $contextNode->firstChild; $node; $node = $node->nextSibling) { $s .= $this->expand($node, $flags); } $bits = $contextNode->splitHeading(); $titleText = $this->title->getPrefixedDBkey(); $this->parser->mHeadings[] = array($titleText, $bits['i']); $serial = count($this->parser->mHeadings) - 1; $marker = Parser::MARKER_PREFIX . "-h-{$serial}-" . Parser::MARKER_SUFFIX; $s = substr($s, 0, $bits['level']) . $marker . substr($s, $bits['level']); $this->parser->mStripState->addGeneral($marker, ''); $out .= $s; } else { # Expand in virtual stack $newIterator = $contextNode->getChildren(); } } else { # Generic recursive expansion $newIterator = $contextNode->getChildren(); } } else { throw new MWException(__METHOD__ . ': Invalid parameter type'); } if ($newIterator !== false) { $outStack[] = ''; $iteratorStack[] = $newIterator; $indexStack[] = 0; } elseif ($iteratorStack[$level] === false) { // Return accumulated value to parent // With tail recursion while ($iteratorStack[$level] === false && $level > 0) { $outStack[$level - 1] .= $out; array_pop($outStack); array_pop($iteratorStack); array_pop($indexStack); $level--; } } } --$expansionDepth; return $outStack[0]; }
/** * @throws MWException * @param $root * @param $flags int * @return string */ function expand($root, $flags = 0) { static $expansionDepth = 0; if (is_string($root)) { return $root; } if (++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount()) { return '<span class="error">Node-count limit exceeded</span>'; } if ($expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth()) { return '<span class="error">Expansion depth limit exceeded</span>'; } wfProfileIn(__METHOD__); ++$expansionDepth; if ($root instanceof PPNode_DOM) { $root = $root->node; } if ($root instanceof DOMDocument) { $root = $root->documentElement; } $outStack = array('', ''); $iteratorStack = array(false, $root); $indexStack = array(0, 0); $RTEext_1 = false; $RTEext_2 = false; while (count($iteratorStack) > 1) { if ($RTEext_1) { $RTEext_1 = false; $RTEext_2 = true; } $level = count($outStack) - 1; $iteratorNode =& $iteratorStack[$level]; $out =& $outStack[$level]; $index =& $indexStack[$level]; if ($iteratorNode instanceof PPNode_DOM) { $iteratorNode = $iteratorNode->node; } if (is_array($iteratorNode)) { if ($index >= count($iteratorNode)) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode[$index]; $index++; } } elseif ($iteratorNode instanceof DOMNodeList) { if ($index >= $iteratorNode->length) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode->item($index); $index++; } } else { // Copy to $contextNode and then delete from iterator stack, // because this is not an iterator but we do have to execute it once $contextNode = $iteratorStack[$level]; $iteratorStack[$level] = false; } if ($contextNode instanceof PPNode_DOM) { $contextNode = $contextNode->node; } $newIterator = false; if ($contextNode === false) { // nothing to do } elseif (is_string($contextNode)) { $out .= $contextNode; } elseif (is_array($contextNode) || $contextNode instanceof DOMNodeList) { $newIterator = $contextNode; } elseif ($contextNode instanceof DOMNode) { if ($contextNode->nodeType == XML_TEXT_NODE) { # RTE (Rich Text Editor) - begin global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { if ($RTEext_2) { if (strpos($contextNode->nodeValue, 'table') !== false) { RTE::$edgeCases[] = 'COMPLEX.11'; } } } # RTE - end $out .= $contextNode->nodeValue; } elseif ($contextNode->nodeName == 'template') { # Double-brace expansion $xpath = new DOMXPath($contextNode->ownerDocument); $titles = $xpath->query('title', $contextNode); $title = $titles->item(0); $parts = $xpath->query('part', $contextNode); # RTE (Rich Text Editor) - begin # @author: Inez Korczyński global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { $dataIdx = RTEData::put('placeholder', array('type' => 'double-brackets', 'wikitextIdx' => $contextNode->getAttribute('_rte_wikitextidx'), 'lineStart' => $contextNode->getAttribute('lineStart'), 'title' => $title->textContent)); $out .= RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx); } else { if ($flags & PPFrame::NO_TEMPLATES) { $newIterator = $this->virtualBracketedImplode('{{', '|', '}}', $title, $parts); } else { $lineStart = $contextNode->getAttribute('lineStart'); $params = array('title' => new PPNode_DOM($title), 'parts' => new PPNode_DOM($parts), 'lineStart' => $lineStart); $ret = $this->parser->braceSubstitution($params, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } # RTE - end } elseif ($contextNode->nodeName == 'tplarg') { # Triple-brace expansion $xpath = new DOMXPath($contextNode->ownerDocument); $titles = $xpath->query('title', $contextNode); $title = $titles->item(0); $parts = $xpath->query('part', $contextNode); # RTE (Rich Text Editor) - begin # @author: Wladyslaw Bodzek global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { //var_dump($contextNode->getAttribute('_rte_wikitextidx')); $dataIdx = RTEData::put('placeholder', array('type' => 'tplarg', 'wikitextIdx' => $contextNode->getAttribute('_rte_wikitextidx'), 'lineStart' => $contextNode->getAttribute('lineStart'), 'title' => $title->textContent)); $out .= RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx); } else { if ($flags & PPFrame::NO_ARGS) { $newIterator = $this->virtualBracketedImplode('{{{', '|', '}}}', $title, $parts); } else { $params = array('title' => new PPNode_DOM($title), 'parts' => new PPNode_DOM($parts)); $ret = $this->parser->argSubstitution($params, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } # RTE - end } elseif ($contextNode->nodeName == 'comment') { # HTML-style comment # Remove it in HTML, pre+remove and STRIP_COMMENTS modes if ($this->parser->ot['html'] || $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() || $flags & PPFrame::STRIP_COMMENTS) { # RTE (Rich Text Editor) - begin # @author: Inez Korczyński global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { if (strlen($out) === 0 || substr($out, -1) == "\n") { if (substr($contextNode->textContent, -1) == "\n") { $add = "\n"; $text = substr($contextNode->textContent, 0, -1); } else { $add = ""; $text = $contextNode->textContent; } $dataIdx = RTEData::put('placeholder', array('type' => 'comment', 'wikitext' => $text)); $out .= RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx) . $add; } else { RTE::$edgeCases[] = 'COMMENT'; $out .= ''; } } else { $out .= ''; } # RTE - end } elseif ($this->parser->ot['wiki'] && !($flags & PPFrame::RECOVER_COMMENTS)) { $out .= $this->parser->insertStripItem($contextNode->textContent); } else { $out .= $contextNode->textContent; } } elseif ($contextNode->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 & PPFrame::NO_IGNORE) { $out .= $contextNode->textContent; } else { $out .= ''; } } elseif ($contextNode->nodeName == 'ext') { # Extension tag $xpath = new DOMXPath($contextNode->ownerDocument); $names = $xpath->query('name', $contextNode); $attrs = $xpath->query('attr', $contextNode); $inners = $xpath->query('inner', $contextNode); $closes = $xpath->query('close', $contextNode); $params = array('name' => new PPNode_DOM($names->item(0)), 'attr' => $attrs->length > 0 ? new PPNode_DOM($attrs->item(0)) : null, 'inner' => $inners->length > 0 ? new PPNode_DOM($inners->item(0)) : null, 'close' => $closes->length > 0 ? new PPNode_DOM($closes->item(0)) : null); $out .= $this->parser->extensionSubstitution($params, $this); $RTEext_1 = true; } elseif ($contextNode->nodeName == 'h') { # Heading $s = $this->expand($contextNode->childNodes, $flags); # Insert a heading marker only for <h> children of <root> # This is to stop extractSections from going over multiple tree levels if ($contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html']) { # Insert heading index marker $headingIndex = $contextNode->getAttribute('i'); $titleText = $this->title->getPrefixedDBkey(); $this->parser->mHeadings[] = array($titleText, $headingIndex); $serial = count($this->parser->mHeadings) - 1; $marker = "{$this->parser->mUniqPrefix}-h-{$serial}-" . Parser::MARKER_SUFFIX; $count = $contextNode->getAttribute('level'); $s = substr($s, 0, $count) . $marker . substr($s, $count); $this->parser->mStripState->addGeneral($marker, ''); } $out .= $s; } else { # Generic recursive expansion $newIterator = $contextNode->childNodes; } } else { wfProfileOut(__METHOD__); throw new MWException(__METHOD__ . ': Invalid parameter type'); } if ($newIterator !== false) { if ($newIterator instanceof PPNode_DOM) { $newIterator = $newIterator->node; } $outStack[] = ''; $iteratorStack[] = $newIterator; $indexStack[] = 0; } elseif ($iteratorStack[$level] === false) { // Return accumulated value to parent // With tail recursion while ($iteratorStack[$level] === false && $level > 0) { $outStack[$level - 1] .= $out; array_pop($outStack); array_pop($iteratorStack); array_pop($indexStack); $level--; } } $RTEext_2 = false; } --$expansionDepth; wfProfileOut(__METHOD__); return $outStack[0]; }