42 $mem = ini_get(
'memory_limit' );
43 $this->memoryLimit =
false;
44 if ( strval( $mem ) !==
'' && $mem != -1 ) {
45 if ( preg_match(
'/^\d+$/', $mem ) ) {
46 $this->memoryLimit = $mem;
47 } elseif ( preg_match(
'/^(\d+)M$/i', $mem, $m ) ) {
48 $this->memoryLimit = $m[1] * 1048576;
77 foreach ( $values
as $k => $val ) {
79 $xml .=
"<part><name index=\"$k\"/><value>"
80 . htmlspecialchars( $val ) .
"</value></part>";
82 $xml .=
"<part><name>" . htmlspecialchars( $k )
83 .
"</name>=<value>" . htmlspecialchars( $val ) .
"</value></part>";
89 $dom =
new DOMDocument();
90 MediaWiki\suppressWarnings();
91 $result = $dom->loadXML( $xml );
92 MediaWiki\restoreWarnings();
95 $xml = UtfNormal\Validator::cleanUp( $xml );
98 $result = $dom->loadXML( $xml, 1 << 19 );
102 throw new MWException(
'Parameters passed to ' . __METHOD__ .
' result in invalid XML' );
105 $root = $dom->documentElement;
115 if ( $this->memoryLimit ===
false ) {
118 $usage = memory_get_usage();
119 if ( $usage > $this->memoryLimit * 0.9 ) {
120 $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 );
121 throw new MWException(
"Preprocessor hit 90% memory limit ($limit MB)" );
123 return $usage <= $this->memoryLimit * 0.8;
153 if ( $xml ===
false ) {
160 $this->
parser->mGeneratedPPNodeCount += substr_count( $xml,
'<' );
161 $max = $this->
parser->mOptions->getMaxGeneratedPPNodeCount();
162 if ( $this->
parser->mGeneratedPPNodeCount > $max ) {
164 throw new MWException( __METHOD__ .
': generated node count limit exceeded' );
167 $dom =
new DOMDocument;
168 MediaWiki\suppressWarnings();
169 $result = $dom->loadXML( $xml );
170 MediaWiki\restoreWarnings();
173 $xml = UtfNormal\Validator::cleanUp( $xml );
176 $result = $dom->loadXML( $xml, 1 << 19 );
179 $obj =
new PPNode_DOM( $dom->documentElement );
185 throw new MWException( __METHOD__ .
' generated invalid XML' );
196 global $wgDisableLangConversion;
198 $forInclusion =
$flags & Parser::PTD_FOR_INCLUSION;
200 $xmlishElements = $this->
parser->getStripList();
201 $xmlishAllowMissingEndTag = [
'includeonly',
'noinclude',
'onlyinclude' ];
202 $enableOnlyinclude =
false;
203 if ( $forInclusion ) {
204 $ignoredTags = [
'includeonly',
'/includeonly' ];
205 $ignoredElements = [
'noinclude' ];
206 $xmlishElements[] =
'noinclude';
207 if ( strpos( $text,
'<onlyinclude>' ) !==
false
208 && strpos( $text,
'</onlyinclude>' ) !==
false
210 $enableOnlyinclude =
true;
213 $ignoredTags = [
'noinclude',
'/noinclude',
'onlyinclude',
'/onlyinclude' ];
214 $ignoredElements = [
'includeonly' ];
215 $xmlishElements[] =
'includeonly';
217 $xmlishRegex = implode(
'|', array_merge( $xmlishElements, $ignoredTags ) );
220 $elementsRegex =
"~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
224 $searchBase =
"[{<\n"; # }
225 if ( !$wgDisableLangConversion ) {
231 $revText = strrev( $text );
232 $lengthText = strlen( $text );
237 $accum =& $stack->getAccum();
249 $noMoreClosingTag = [];
251 $findOnlyinclude = $enableOnlyinclude;
253 $fakeLineStart =
true;
258 if ( $findOnlyinclude ) {
260 $startPos = strpos( $text,
'<onlyinclude>', $i );
261 if ( $startPos ===
false ) {
263 $accum .=
'<ignore>' . htmlspecialchars( substr( $text, $i ) ) .
'</ignore>';
266 $tagEndPos = $startPos + strlen(
'<onlyinclude>' );
267 $accum .=
'<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i ) ) .
'</ignore>';
269 $findOnlyinclude =
false;
272 if ( $fakeLineStart ) {
273 $found =
'line-start';
276 # Find next opening brace, closing brace or pipe
277 $search = $searchBase;
278 if ( $stack->top ===
false ) {
279 $currentClosing =
'';
281 $currentClosing = $stack->top->close;
282 $search .= $currentClosing;
292 # Output literal section, advance input counter
293 $literalLength = strcspn( $text, $search, $i );
294 if ( $literalLength > 0 ) {
295 $accum .= htmlspecialchars( substr( $text, $i, $literalLength ) );
296 $i += $literalLength;
298 if ( $i >= $lengthText ) {
299 if ( $currentClosing ==
"\n" ) {
308 $curChar = $curTwoChar = $text[$i];
309 if ( ( $i + 1 ) < $lengthText ) {
310 $curTwoChar .= $text[$i + 1];
312 if ( $curChar ==
'|' ) {
314 } elseif ( $curChar ==
'=' ) {
316 } elseif ( $curChar ==
'<' ) {
318 } elseif ( $curChar ==
"\n" ) {
322 $found =
'line-start';
324 } elseif ( $curTwoChar == $currentClosing ) {
326 $curChar = $curTwoChar;
327 } elseif ( $curChar == $currentClosing ) {
329 } elseif ( isset( $this->rules[$curTwoChar] ) ) {
330 $curChar = $curTwoChar;
332 $rule = $this->rules[$curChar];
333 } elseif ( isset( $this->rules[$curChar] ) ) {
335 $rule = $this->rules[$curChar];
336 } elseif ( $curChar ==
'-' ) {
339 # Some versions of PHP have a strcspn which stops on null characters
340 # Ignore and continue
347 if ( $found ==
'angle' ) {
350 if ( $enableOnlyinclude
351 && substr( $text, $i, strlen(
'</onlyinclude>' ) ) ==
'</onlyinclude>'
353 $findOnlyinclude =
true;
358 if ( !preg_match( $elementsRegex, $text,
$matches, 0, $i + 1 ) ) {
373 $endPos = strpos( $text,
'-->', $i + 4 );
374 if ( $endPos ===
false ) {
376 $inner = substr( $text, $i );
377 $accum .=
'<comment>' . htmlspecialchars( $inner ) .
'</comment>';
381 $wsStart = $i ? ( $i - strspn( $revText,
" \t", $lengthText - $i ) ) : 0;
385 $wsEnd = $endPos + 2 + strspn( $text,
" \t", $endPos + 3 );
389 $comments = [ [ $wsStart, $wsEnd ] ];
390 while ( substr( $text, $wsEnd + 1, 4 ) ==
'<!--' ) {
391 $c = strpos( $text,
'-->', $wsEnd + 4 );
392 if ( $c ===
false ) {
395 $c = $c + 2 + strspn( $text,
" \t", $c + 3 );
396 $comments[] = [ $wsEnd + 1, $c ];
404 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) ==
"\n"
405 && substr( $text, $wsEnd + 1, 1 ) ==
"\n"
409 $wsLength = $i - $wsStart;
411 && strspn( $accum,
" \t", -$wsLength ) === $wsLength
413 $accum = substr( $accum, 0, -$wsLength );
417 foreach ( $comments
as $j => $com ) {
419 $endPos = $com[1] + 1;
420 if ( $j == (
count( $comments ) - 1 ) ) {
423 $inner = substr( $text, $startPos, $endPos - $startPos );
424 $accum .=
'<comment>' . htmlspecialchars( $inner ) .
'</comment>';
428 $fakeLineStart =
true;
436 $part = $stack->top->getCurrentPart();
437 if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
438 $part->visualEnd = $wsStart;
441 $part->commentEnd = $endPos;
444 $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
445 $accum .=
'<comment>' . htmlspecialchars( $inner ) .
'</comment>';
450 $lowerName = strtolower(
$name );
451 $attrStart = $i + strlen(
$name ) + 1;
454 $tagEndPos = $noMoreGT ?
false : strpos( $text,
'>', $attrStart );
455 if ( $tagEndPos ===
false ) {
465 if ( in_array( $lowerName, $ignoredTags ) ) {
467 . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) )
474 if ( $text[$tagEndPos - 1] ==
'/' ) {
475 $attrEnd = $tagEndPos - 1;
480 $attrEnd = $tagEndPos;
483 !isset( $noMoreClosingTag[
$name] ) &&
484 preg_match(
"/<\/" . preg_quote(
$name,
'/' ) .
"\s*>/i",
485 $text,
$matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
487 $inner = substr( $text, $tagEndPos + 1,
$matches[0][1] - $tagEndPos - 1 );
489 $close =
'<close>' . htmlspecialchars(
$matches[0][0] ) .
'</close>';
492 if ( in_array(
$name, $xmlishAllowMissingEndTag ) ) {
494 $inner = substr( $text, $tagEndPos + 1 );
500 $accum .= htmlspecialchars( substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
502 $noMoreClosingTag[
$name] =
true;
508 if ( in_array( $lowerName, $ignoredElements ) ) {
509 $accum .=
'<ignore>' . htmlspecialchars( substr( $text, $tagStartPos, $i - $tagStartPos ) )
515 if ( $attrEnd <= $attrStart ) {
518 $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
520 $accum .=
'<name>' . htmlspecialchars(
$name ) .
'</name>' .
523 '<attr>' . htmlspecialchars( $attr ) .
'</attr>';
524 if ( $inner !==
null ) {
525 $accum .=
'<inner>' . htmlspecialchars( $inner ) .
'</inner>';
527 $accum .= $close .
'</ext>';
528 } elseif ( $found ==
'line-start' ) {
531 if ( $fakeLineStart ) {
532 $fakeLineStart =
false;
538 $count = strspn( $text,
'=', $i, 6 );
539 if ( $count == 1 && $findEquals ) {
545 } elseif ( $count > 0 ) {
549 'parts' => [
new PPDPart( str_repeat(
'=', $count ) ) ],
552 $stack->push( $piece );
553 $accum =& $stack->getAccum();
554 $flags = $stack->getFlags();
558 } elseif ( $found ==
'line-end' ) {
559 $piece = $stack->top;
561 assert( $piece->open ===
"\n" );
562 $part = $piece->getCurrentPart();
566 $wsLength = strspn( $revText,
" \t", $lengthText - $i );
567 $searchStart = $i - $wsLength;
568 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
571 $searchStart = $part->visualEnd;
572 $searchStart -= strspn( $revText,
" \t", $lengthText - $searchStart );
574 $count = $piece->count;
575 $equalsLength = strspn( $revText,
'=', $lengthText - $searchStart );
576 if ( $equalsLength > 0 ) {
577 if ( $searchStart - $equalsLength == $piece->startPos ) {
581 $count = $equalsLength;
585 $count = min( 6, intval( ( $count - 1 ) / 2 ) );
588 $count = min( $equalsLength, $count );
592 $element =
"<h level=\"$count\" i=\"$headingIndex\">$accum</h>";
604 $accum =& $stack->getAccum();
605 $flags = $stack->getFlags();
615 } elseif ( $found ==
'open' ) {
616 # count opening brace characters
617 $curLen = strlen( $curChar );
618 $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i );
620 # we need to add to stack only if opening brace count is enough for one of the rules
621 if ( $count >= $rule[
'min'] ) {
622 # Add it to the stack
625 'close' => $rule[
'end'],
627 'lineStart' => ( $i > 0 && $text[$i - 1] ==
"\n" ),
630 $stack->push( $piece );
631 $accum =& $stack->getAccum();
632 $flags = $stack->getFlags();
635 # Add literal brace(s)
636 $accum .= htmlspecialchars( str_repeat( $curChar, $count ) );
638 $i += $curLen * $count;
639 } elseif ( $found ==
'close' ) {
640 $piece = $stack->top;
641 # lets check if there are enough characters for closing brace
642 $maxCount = $piece->count;
643 $curLen = strlen( $curChar );
644 $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i, $maxCount );
646 # check for maximum matching characters (if there are 5 closing
647 # characters, we will probably need only 3 - depending on the rules)
648 $rule = $this->rules[$piece->open];
649 if ( $count > $rule[
'max'] ) {
650 # The specified maximum exists in the callback array, unless the caller
652 $matchingCount = $rule[
'max'];
654 # Count is less than the maximum
655 # Skip any gaps in the callback array to find the true largest match
656 # Need to use array_key_exists not isset because the callback can be null
657 $matchingCount = $count;
658 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule[
'names'] ) ) {
663 if ( $matchingCount <= 0 ) {
664 # No matching element found in callback array
665 # Output a literal closing brace and continue
666 $accum .= htmlspecialchars( str_repeat( $curChar, $count ) );
667 $i += $curLen * $count;
670 $name = $rule[
'names'][$matchingCount];
671 if (
$name ===
null ) {
673 $element = $piece->breakSyntax( $matchingCount ) . str_repeat( $rule[
'end'], $matchingCount );
676 # Note: $parts is already XML, does not need to be encoded further
677 $parts = $piece->parts;
681 # The invocation is at the start of the line if lineStart is set in
682 # the stack, and all opening brackets are used up.
683 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
684 $attr =
' lineStart="1"';
689 $element =
"<$name$attr>";
690 $element .=
"<title>$title</title>";
692 foreach ( $parts
as $part ) {
693 if ( isset( $part->eqpos ) ) {
694 $argName = substr( $part->out, 0, $part->eqpos );
695 $argValue = substr( $part->out, $part->eqpos + 1 );
696 $element .=
"<part><name>$argName</name>=<value>$argValue</value></part>";
698 $element .=
"<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>";
702 $element .=
"</$name>";
705 # Advance input pointer
706 $i += $curLen * $matchingCount;
710 $accum =& $stack->getAccum();
712 # Re-add the old stack element if it still has unmatched opening characters remaining
713 if ( $matchingCount < $piece->
count ) {
714 $piece->parts = [
new PPDPart ];
715 $piece->count -= $matchingCount;
716 # do we still qualify for any callback with remaining count?
717 $min = $this->rules[$piece->open][
'min'];
718 if ( $piece->count >= $min ) {
719 $stack->push( $piece );
720 $accum =& $stack->getAccum();
722 $accum .= str_repeat( $piece->open, $piece->count );
725 $flags = $stack->getFlags();
728 # Add XML element to the enclosing accumulator
730 } elseif ( $found ==
'pipe' ) {
733 $accum =& $stack->getAccum();
735 } elseif ( $found ==
'equals' ) {
737 $stack->getCurrentPart()->eqpos = strlen( $accum );
740 } elseif ( $found ==
'dash' ) {
746 # Output any remaining unclosed brackets
747 foreach ( $stack->stack
as $piece ) {
748 $stack->rootAccum .= $piece->breakSyntax();
750 $stack->rootAccum .=
'</root>';
751 $xml = $stack->rootAccum;
776 $this->rootAccum =
'';
784 return count( $this->stack );
792 if ( $this->top ===
false ) {
795 return $this->top->getCurrentPart();
799 public function push( $data ) {
800 if ( $data instanceof $this->elementClass ) {
801 $this->stack[] = $data;
804 $this->stack[] =
new $class( $data );
806 $this->top = $this->stack[
count( $this->stack ) - 1];
807 $this->accum =& $this->top->getAccum();
811 if ( !
count( $this->stack ) ) {
812 throw new MWException( __METHOD__ .
': no elements remaining' );
814 $temp = array_pop( $this->stack );
816 if (
count( $this->stack ) ) {
817 $this->top = $this->stack[
count( $this->stack ) - 1];
818 $this->accum =& $this->top->getAccum();
827 $this->top->addPart(
$s );
828 $this->accum =& $this->top->getAccum();
835 if ( !
count( $this->stack ) ) {
837 'findEquals' =>
false,
839 'inHeading' =>
false,
842 return $this->top->getFlags();
881 $this->parts = [
new $class ];
889 return $this->parts[
count( $this->parts ) - 1]->out;
894 $this->parts[] =
new $class(
$s );
898 return $this->parts[
count( $this->parts ) - 1];
905 $partCount =
count( $this->parts );
906 $findPipe = $this->
open !=
"\n" && $this->
open !=
'[';
908 'findPipe' => $findPipe,
909 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
910 'inHeading' => $this->
open ==
"\n",
921 if ( $this->
open ==
"\n" ) {
922 $s = $this->parts[0]->out;
924 if ( $openingCount ===
false ) {
927 $s = str_repeat( $this->
open, $openingCount );
929 foreach ( $this->parts
as $part ) {
1013 $this->titleCache = [ $this->
title ? $this->
title->getPrefixedDBkey() :
false ];
1014 $this->loopCheckHash = [];
1016 $this->childExpansionCache = [];
1031 if (
$title ===
false ) {
1034 if (
$args !==
false ) {
1040 if ( $arg instanceof
PPNode ) {
1043 if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
1044 $xpath =
new DOMXPath( $arg->ownerDocument );
1047 $nameNodes = $xpath->query(
'name', $arg );
1048 $value = $xpath->query(
'value', $arg );
1049 if ( $nameNodes->item( 0 )->hasAttributes() ) {
1051 $index = $nameNodes->item( 0 )->attributes->getNamedItem(
'index' )->textContent;
1052 $index = $index - $indexOffset;
1053 if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
1054 $this->
parser->getOutput()->addWarning(
wfMessage(
'duplicate-args-warning',
1058 $this->
parser->addTrackingCategory(
'duplicate-args-category' );
1060 $numberedArgs[$index] =
$value->item( 0 );
1061 unset( $namedArgs[$index] );
1065 if ( isset( $namedArgs[
$name] ) || isset( $numberedArgs[
$name] ) ) {
1066 $this->
parser->getOutput()->addWarning(
wfMessage(
'duplicate-args-warning',
1070 $this->
parser->addTrackingCategory(
'duplicate-args-category' );
1073 unset( $numberedArgs[
$name] );
1099 static $expansionDepth = 0;
1100 if ( is_string( $root ) ) {
1104 if ( ++$this->
parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
1105 $this->
parser->limitationWarn(
'node-count-exceeded',
1106 $this->
parser->mPPNodeCount,
1107 $this->parser->mOptions->getMaxPPNodeCount()
1109 return '<span class="error">Node-count limit exceeded</span>';
1112 if ( $expansionDepth > $this->
parser->mOptions->getMaxPPExpandDepth() ) {
1113 $this->
parser->limitationWarn(
'expansion-depth-exceeded',
1115 $this->
parser->mOptions->getMaxPPExpandDepth()
1117 return '<span class="error">Expansion depth limit exceeded</span>';
1120 if ( $expansionDepth > $this->
parser->mHighestExpansionDepth ) {
1121 $this->
parser->mHighestExpansionDepth = $expansionDepth;
1125 $root = $root->node;
1127 if ( $root instanceof DOMDocument ) {
1128 $root = $root->documentElement;
1131 $outStack = [
'',
'' ];
1132 $iteratorStack = [
false, $root ];
1133 $indexStack = [ 0, 0 ];
1135 while (
count( $iteratorStack ) > 1 ) {
1136 $level =
count( $outStack ) - 1;
1137 $iteratorNode =& $iteratorStack[$level];
1138 $out =& $outStack[$level];
1139 $index =& $indexStack[$level];
1142 $iteratorNode = $iteratorNode->node;
1145 if ( is_array( $iteratorNode ) ) {
1146 if ( $index >=
count( $iteratorNode ) ) {
1148 $iteratorStack[$level] =
false;
1149 $contextNode =
false;
1151 $contextNode = $iteratorNode[$index];
1154 } elseif ( $iteratorNode instanceof DOMNodeList ) {
1155 if ( $index >= $iteratorNode->length ) {
1157 $iteratorStack[$level] =
false;
1158 $contextNode =
false;
1160 $contextNode = $iteratorNode->item( $index );
1166 $contextNode = $iteratorStack[$level];
1167 $iteratorStack[$level] =
false;
1171 $contextNode = $contextNode->node;
1174 $newIterator =
false;
1176 if ( $contextNode ===
false ) {
1178 } elseif ( is_string( $contextNode ) ) {
1179 $out .= $contextNode;
1180 } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) {
1181 $newIterator = $contextNode;
1182 } elseif ( $contextNode instanceof DOMNode ) {
1183 if ( $contextNode->nodeType == XML_TEXT_NODE ) {
1184 $out .= $contextNode->nodeValue;
1185 } elseif ( $contextNode->nodeName ==
'template' ) {
1186 # Double-brace expansion
1187 $xpath =
new DOMXPath( $contextNode->ownerDocument );
1188 $titles = $xpath->query(
'title', $contextNode );
1190 $parts = $xpath->query(
'part', $contextNode );
1194 $lineStart = $contextNode->getAttribute(
'lineStart' );
1198 'lineStart' => $lineStart ];
1200 if ( isset(
$ret[
'object'] ) ) {
1201 $newIterator =
$ret[
'object'];
1206 } elseif ( $contextNode->nodeName ==
'tplarg' ) {
1207 # Triple-brace expansion
1208 $xpath =
new DOMXPath( $contextNode->ownerDocument );
1209 $titles = $xpath->query(
'title', $contextNode );
1211 $parts = $xpath->query(
'part', $contextNode );
1219 if ( isset(
$ret[
'object'] ) ) {
1220 $newIterator =
$ret[
'object'];
1225 } elseif ( $contextNode->nodeName ==
'comment' ) {
1226 # HTML-style comment
1227 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
1228 # Not in RECOVER_COMMENTS mode (msgnw) though.
1229 if ( ( $this->
parser->ot[
'html']
1230 || ( $this->parser->ot[
'pre'] && $this->parser->mOptions->getRemoveComments() )
1235 } elseif ( $this->
parser->ot[
'wiki'] && !(
$flags & PPFrame::RECOVER_COMMENTS ) ) {
1236 # Add a strip marker in PST mode so that pstPass2() can
1237 # run some old-fashioned regexes on the result.
1238 # Not in RECOVER_COMMENTS mode (extractSections) though.
1239 $out .= $this->
parser->insertStripItem( $contextNode->textContent );
1241 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
1242 $out .= $contextNode->textContent;
1244 } elseif ( $contextNode->nodeName ==
'ignore' ) {
1245 # Output suppression used by <includeonly> etc.
1246 # OT_WIKI will only respect <ignore> in substed templates.
1247 # The other output types respect it unless NO_IGNORE is set.
1248 # extractSections() sets NO_IGNORE and so never respects it.
1249 if ( ( !isset( $this->parent ) && $this->
parser->ot[
'wiki'] )
1252 $out .= $contextNode->textContent;
1256 } elseif ( $contextNode->nodeName ==
'ext' ) {
1258 $xpath =
new DOMXPath( $contextNode->ownerDocument );
1259 $names = $xpath->query(
'name', $contextNode );
1260 $attrs = $xpath->query(
'attr', $contextNode );
1261 $inners = $xpath->query(
'inner', $contextNode );
1262 $closes = $xpath->query(
'close', $contextNode );
1265 if ( $attrs->length > 0 ) {
1268 if ( $inners->length > 0 ) {
1270 if ( $closes->length > 0 ) {
1279 'name' =>
new PPNode_DOM( $names->item( 0 ) ),
1280 'attr' => $attrs->length > 0 ?
new PPNode_DOM( $attrs->item( 0 ) ) :
null,
1281 'inner' => $inners->length > 0 ?
new PPNode_DOM( $inners->item( 0 ) ) :
null,
1282 'close' => $closes->length > 0 ?
new PPNode_DOM( $closes->item( 0 ) ) :
null,
1286 } elseif ( $contextNode->nodeName ==
'h' ) {
1290 # Insert a heading marker only for <h> children of <root>
1291 # This is to stop extractSections from going over multiple tree levels
1292 if ( $contextNode->parentNode->nodeName ==
'root' && $this->parser->ot[
'html'] ) {
1293 # Insert heading index marker
1294 $headingIndex = $contextNode->getAttribute(
'i' );
1295 $titleText = $this->
title->getPrefixedDBkey();
1296 $this->
parser->mHeadings[] = [ $titleText, $headingIndex ];
1298 $marker = Parser::MARKER_PREFIX .
"-h-$serial-" . Parser::MARKER_SUFFIX;
1299 $count = $contextNode->getAttribute(
'level' );
1300 $s = substr(
$s, 0, $count ) . $marker . substr(
$s, $count );
1301 $this->
parser->mStripState->addGeneral( $marker,
'' );
1305 # Generic recursive expansion
1306 $newIterator = $contextNode->childNodes;
1309 throw new MWException( __METHOD__ .
': Invalid parameter type' );
1312 if ( $newIterator !==
false ) {
1314 $newIterator = $newIterator->node;
1317 $iteratorStack[] = $newIterator;
1319 } elseif ( $iteratorStack[$level] ===
false ) {
1322 while ( $iteratorStack[$level] ===
false && $level > 0 ) {
1323 $outStack[$level - 1] .=
$out;
1324 array_pop( $outStack );
1325 array_pop( $iteratorStack );
1326 array_pop( $indexStack );
1332 return $outStack[0];
1342 $args = array_slice( func_get_args(), 2 );
1348 $root = $root->node;
1350 if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1353 foreach ( $root
as $node ) {
1374 $args = array_slice( func_get_args(), 1 );
1380 $root = $root->node;
1382 if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1385 foreach ( $root
as $node ) {
1406 $args = array_slice( func_get_args(), 1 );
1412 $root = $root->node;
1414 if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1417 foreach ( $root
as $node ) {
1438 $args = array_slice( func_get_args(), 3 );
1444 $root = $root->node;
1446 if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
1449 foreach ( $root
as $node ) {
1467 if ( $level ===
false ) {
1468 return $this->
title->getPrefixedDBkey();
1470 return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] :
false;
1546 $this->
volatile = $flag;
1564 if (
$ttl !==
null && ( $this->ttl ===
null || $ttl < $this->ttl ) ) {
1612 $this->titleCache =
$parent->titleCache;
1613 $this->titleCache[] = $pdbk;
1614 $this->loopCheckHash =
$parent->loopCheckHash;
1615 if ( $pdbk !==
false ) {
1616 $this->loopCheckHash[$pdbk] =
true;
1618 $this->depth =
$parent->depth + 1;
1619 $this->numberedExpansionCache = $this->namedExpansionCache = [];
1632 $s .=
"\"$name\":\"" .
1633 str_replace(
'"',
'\\"',
$value->ownerDocument->saveXML(
$value ) ) .
'"';
1647 if ( isset( $this->parent->childExpansionCache[$key] ) ) {
1648 return $this->parent->childExpansionCache[$key];
1652 $this->parent->childExpansionCache[$key] =
$retval;
1663 return !
count( $this->numberedArgs ) && !
count( $this->namedArgs );
1668 foreach ( array_merge(
1669 array_keys( $this->numberedArgs ),
1670 array_keys( $this->namedArgs ) )
as $key ) {
1678 foreach ( array_keys( $this->numberedArgs )
as $key ) {
1686 foreach ( array_keys( $this->namedArgs )
as $key ) {
1697 if ( !isset( $this->numberedArgs[$index] ) ) {
1700 if ( !isset( $this->numberedExpansionCache[$index] ) ) {
1701 # No trimming for unnamed arguments
1702 $this->numberedExpansionCache[$index] = $this->parent->expand(
1703 $this->numberedArgs[$index],
1707 return $this->numberedExpansionCache[$index];
1715 if ( !isset( $this->namedArgs[
$name] ) ) {
1718 if ( !isset( $this->namedExpansionCache[
$name] ) ) {
1719 # Trim named arguments post-expand, for backwards compatibility
1720 $this->namedExpansionCache[
$name] = trim(
1723 return $this->namedExpansionCache[
$name];
1732 if ( $text ===
false ) {
1748 parent::setVolatile( $flag );
1749 $this->parent->setVolatile( $flag );
1753 parent::setTTL(
$ttl );
1754 $this->parent->setTTL(
$ttl );
1782 $s .=
"\"$name\":\"" .
1783 str_replace(
'"',
'\\"',
$value->__toString() ) .
'"';
1801 if ( !isset( $this->
args[$index] ) ) {
1804 return $this->
args[$index];
1826 $this->node =
$node;
1833 if ( $this->xpath ===
null ) {
1834 $this->xpath =
new DOMXPath( $this->node->ownerDocument );
1840 if ( $this->node instanceof DOMNodeList ) {
1842 foreach ( $this->node
as $node ) {
1846 $s = $this->node->ownerDocument->saveXML( $this->node );
1855 return $this->node->childNodes ?
new self( $this->node->childNodes ) :
false;
1862 return $this->node->firstChild ?
new self( $this->node->firstChild ) :
false;
1869 return $this->node->nextSibling ?
new self( $this->node->nextSibling ) :
false;
1878 return new self( $this->
getXPath()->query(
$type, $this->node ) );
1885 if ( $this->node instanceof DOMNodeList ) {
1886 return $this->node->length;
1897 $item = $this->node->item( $i );
1898 return $item ?
new self( $item ) :
false;
1905 if ( $this->node instanceof DOMNodeList ) {
1908 return $this->node->nodeName;
1923 $names =
$xpath->query(
'name', $this->node );
1924 $values =
$xpath->query(
'value', $this->node );
1925 if ( !$names->length || !$values->length ) {
1926 throw new MWException(
'Invalid brace node passed to ' . __METHOD__ );
1928 $name = $names->item( 0 );
1929 $index =
$name->getAttribute(
'index' );
1931 'name' =>
new self(
$name ),
1933 'value' =>
new self( $values->item( 0 ) ) ];
1945 $names =
$xpath->query(
'name', $this->node );
1946 $attrs =
$xpath->query(
'attr', $this->node );
1947 $inners =
$xpath->query(
'inner', $this->node );
1948 $closes =
$xpath->query(
'close', $this->node );
1949 if ( !$names->length || !$attrs->length ) {
1950 throw new MWException(
'Invalid ext node passed to ' . __METHOD__ );
1953 'name' =>
new self( $names->item( 0 ) ),
1954 'attr' =>
new self( $attrs->item( 0 ) ) ];
1955 if ( $inners->length ) {
1956 $parts[
'inner'] =
new self( $inners->item( 0 ) );
1958 if ( $closes->length ) {
1959 $parts[
'close'] =
new self( $closes->item( 0 ) );
1970 if ( $this->
getName() !==
'h' ) {
1971 throw new MWException(
'Invalid h node passed to ' . __METHOD__ );
1974 'i' => $this->node->getAttribute(
'i' ),
1975 'level' => $this->node->getAttribute(
'level' ),