Split parser related files to have one class in one file
[lhc/web/wiklou.git] / includes / parser / Preprocessor_DOM.php
index c27a635..0f0496b 100644 (file)
@@ -823,1231 +823,3 @@ class Preprocessor_DOM extends Preprocessor {
                return $xml;
        }
 }
-
-/**
- * Stack class to help Preprocessor::preprocessToObj()
- * @ingroup Parser
- */
-class PPDStack {
-       public $stack, $rootAccum;
-
-       /**
-        * @var PPDStack
-        */
-       public $top;
-       public $out;
-       public $elementClass = PPDStackElement::class;
-
-       public static $false = false;
-
-       public function __construct() {
-               $this->stack = [];
-               $this->top = false;
-               $this->rootAccum = '';
-               $this->accum =& $this->rootAccum;
-       }
-
-       /**
-        * @return int
-        */
-       public function count() {
-               return count( $this->stack );
-       }
-
-       public function &getAccum() {
-               return $this->accum;
-       }
-
-       /**
-        * @return bool|PPDPart
-        */
-       public function getCurrentPart() {
-               if ( $this->top === false ) {
-                       return false;
-               } else {
-                       return $this->top->getCurrentPart();
-               }
-       }
-
-       public function push( $data ) {
-               if ( $data instanceof $this->elementClass ) {
-                       $this->stack[] = $data;
-               } else {
-                       $class = $this->elementClass;
-                       $this->stack[] = new $class( $data );
-               }
-               $this->top = $this->stack[count( $this->stack ) - 1];
-               $this->accum =& $this->top->getAccum();
-       }
-
-       public function pop() {
-               if ( $this->stack === [] ) {
-                       throw new MWException( __METHOD__ . ': no elements remaining' );
-               }
-               $temp = array_pop( $this->stack );
-
-               if ( count( $this->stack ) ) {
-                       $this->top = $this->stack[count( $this->stack ) - 1];
-                       $this->accum =& $this->top->getAccum();
-               } else {
-                       $this->top = self::$false;
-                       $this->accum =& $this->rootAccum;
-               }
-               return $temp;
-       }
-
-       public function addPart( $s = '' ) {
-               $this->top->addPart( $s );
-               $this->accum =& $this->top->getAccum();
-       }
-
-       /**
-        * @return array
-        */
-       public function getFlags() {
-               if ( $this->stack === [] ) {
-                       return [
-                               'findEquals' => false,
-                               'findPipe' => false,
-                               'inHeading' => false,
-                       ];
-               } else {
-                       return $this->top->getFlags();
-               }
-       }
-}
-
-/**
- * @ingroup Parser
- */
-class PPDStackElement {
-       /**
-        * @var string Opening character (\n for heading)
-        */
-       public $open;
-
-       /**
-        * @var string Matching closing character
-        */
-       public $close;
-
-       /**
-        * @var string Saved prefix that may affect later processing,
-        *  e.g. to differentiate `-{{{{` and `{{{{` after later seeing `}}}`.
-        */
-       public $savedPrefix = '';
-
-       /**
-        * @var int Number of opening characters found (number of "=" for heading)
-        */
-       public $count;
-
-       /**
-        * @var PPDPart[] Array of PPDPart objects describing pipe-separated parts.
-        */
-       public $parts;
-
-       /**
-        * @var bool True if the open char appeared at the start of the input line.
-        *  Not set for headings.
-        */
-       public $lineStart;
-
-       public $partClass = PPDPart::class;
-
-       public function __construct( $data = [] ) {
-               $class = $this->partClass;
-               $this->parts = [ new $class ];
-
-               foreach ( $data as $name => $value ) {
-                       $this->$name = $value;
-               }
-       }
-
-       public function &getAccum() {
-               return $this->parts[count( $this->parts ) - 1]->out;
-       }
-
-       public function addPart( $s = '' ) {
-               $class = $this->partClass;
-               $this->parts[] = new $class( $s );
-       }
-
-       /**
-        * @return PPDPart
-        */
-       public function getCurrentPart() {
-               return $this->parts[count( $this->parts ) - 1];
-       }
-
-       /**
-        * @return array
-        */
-       public function getFlags() {
-               $partCount = count( $this->parts );
-               $findPipe = $this->open != "\n" && $this->open != '[';
-               return [
-                       'findPipe' => $findPipe,
-                       'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
-                       'inHeading' => $this->open == "\n",
-               ];
-       }
-
-       /**
-        * Get the output string that would result if the close is not found.
-        *
-        * @param bool|int $openingCount
-        * @return string
-        */
-       public function breakSyntax( $openingCount = false ) {
-               if ( $this->open == "\n" ) {
-                       $s = $this->savedPrefix . $this->parts[0]->out;
-               } else {
-                       if ( $openingCount === false ) {
-                               $openingCount = $this->count;
-                       }
-                       $s = substr( $this->open, 0, -1 );
-                       $s .= str_repeat(
-                               substr( $this->open, -1 ),
-                               $openingCount - strlen( $s )
-                       );
-                       $s = $this->savedPrefix . $s;
-                       $first = true;
-                       foreach ( $this->parts as $part ) {
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $s .= '|';
-                               }
-                               $s .= $part->out;
-                       }
-               }
-               return $s;
-       }
-}
-
-/**
- * @ingroup Parser
- */
-class PPDPart {
-       /**
-        * @var string Output accumulator string
-        */
-       public $out;
-
-       // Optional member variables:
-       //   eqpos        Position of equals sign in output accumulator
-       //   commentEnd   Past-the-end input pointer for the last comment encountered
-       //   visualEnd    Past-the-end input pointer for the end of the accumulator minus comments
-
-       public function __construct( $out = '' ) {
-               $this->out = $out;
-       }
-}
-
-/**
- * An expansion frame, used as a context to expand the result of preprocessToObj()
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPFrame_DOM implements PPFrame {
-
-       /**
-        * @var Preprocessor
-        */
-       public $preprocessor;
-
-       /**
-        * @var Parser
-        */
-       public $parser;
-
-       /**
-        * @var Title
-        */
-       public $title;
-       public $titleCache;
-
-       /**
-        * Hashtable listing templates which are disallowed for expansion in this frame,
-        * having been encountered previously in parent frames.
-        */
-       public $loopCheckHash;
-
-       /**
-        * Recursion depth of this frame, top = 0
-        * Note that this is NOT the same as expansion depth in expand()
-        */
-       public $depth;
-
-       private $volatile = false;
-       private $ttl = null;
-
-       /**
-        * @var array
-        */
-       protected $childExpansionCache;
-
-       /**
-        * Construct a new preprocessor frame.
-        * @param Preprocessor $preprocessor The parent preprocessor
-        */
-       public function __construct( $preprocessor ) {
-               $this->preprocessor = $preprocessor;
-               $this->parser = $preprocessor->parser;
-               $this->title = $this->parser->mTitle;
-               $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
-               $this->loopCheckHash = [];
-               $this->depth = 0;
-               $this->childExpansionCache = [];
-       }
-
-       /**
-        * Create a new child frame
-        * $args is optionally a multi-root PPNode or array containing the template arguments
-        *
-        * @param bool|array $args
-        * @param Title|bool $title
-        * @param int $indexOffset
-        * @return PPTemplateFrame_DOM
-        */
-       public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
-               $namedArgs = [];
-               $numberedArgs = [];
-               if ( $title === false ) {
-                       $title = $this->title;
-               }
-               if ( $args !== false ) {
-                       $xpath = false;
-                       if ( $args instanceof PPNode ) {
-                               $args = $args->node;
-                       }
-                       foreach ( $args as $arg ) {
-                               if ( $arg instanceof PPNode ) {
-                                       $arg = $arg->node;
-                               }
-                               if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
-                                       $xpath = new DOMXPath( $arg->ownerDocument );
-                               }
-
-                               $nameNodes = $xpath->query( 'name', $arg );
-                               $value = $xpath->query( 'value', $arg );
-                               if ( $nameNodes->item( 0 )->hasAttributes() ) {
-                                       // Numbered parameter
-                                       $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
-                                       $index = $index - $indexOffset;
-                                       if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
-                                               $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
-                                                       wfEscapeWikiText( $this->title ),
-                                                       wfEscapeWikiText( $title ),
-                                                       wfEscapeWikiText( $index ) )->text() );
-                                               $this->parser->addTrackingCategory( 'duplicate-args-category' );
-                                       }
-                                       $numberedArgs[$index] = $value->item( 0 );
-                                       unset( $namedArgs[$index] );
-                               } else {
-                                       // Named parameter
-                                       $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
-                                       if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
-                                               $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
-                                                       wfEscapeWikiText( $this->title ),
-                                                       wfEscapeWikiText( $title ),
-                                                       wfEscapeWikiText( $name ) )->text() );
-                                               $this->parser->addTrackingCategory( 'duplicate-args-category' );
-                                       }
-                                       $namedArgs[$name] = $value->item( 0 );
-                                       unset( $numberedArgs[$name] );
-                               }
-                       }
-               }
-               return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
-       }
-
-       /**
-        * @throws MWException
-        * @param string|int $key
-        * @param string|PPNode_DOM|DOMDocument $root
-        * @param int $flags
-        * @return string
-        */
-       public function cachedExpand( $key, $root, $flags = 0 ) {
-               // we don't have a parent, so we don't have a cache
-               return $this->expand( $root, $flags );
-       }
-
-       /**
-        * @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 = [ '', '' ];
-               $iteratorStack = [ false, $root ];
-               $indexStack = [ 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 = [
-                                                       '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 = [
-                                                       '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 = [
-                                                       '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[] = [ $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];
-       }
-
-       /**
-        * @param string $sep
-        * @param int $flags
-        * @param string|PPNode_DOM|DOMDocument ...$args
-        * @return string
-        */
-       public function implodeWithFlags( $sep, $flags, ...$args ) {
-               $first = true;
-               $s = '';
-               foreach ( $args as $root ) {
-                       if ( $root instanceof PPNode_DOM ) {
-                               $root = $root->node;
-                       }
-                       if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
-                               $root = [ $root ];
-                       }
-                       foreach ( $root as $node ) {
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $s .= $sep;
-                               }
-                               $s .= $this->expand( $node, $flags );
-                       }
-               }
-               return $s;
-       }
-
-       /**
-        * Implode with no flags specified
-        * This previously called implodeWithFlags but has now been inlined to reduce stack depth
-        *
-        * @param string $sep
-        * @param string|PPNode_DOM|DOMDocument ...$args
-        * @return string
-        */
-       public function implode( $sep, ...$args ) {
-               $first = true;
-               $s = '';
-               foreach ( $args as $root ) {
-                       if ( $root instanceof PPNode_DOM ) {
-                               $root = $root->node;
-                       }
-                       if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
-                               $root = [ $root ];
-                       }
-                       foreach ( $root as $node ) {
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $s .= $sep;
-                               }
-                               $s .= $this->expand( $node );
-                       }
-               }
-               return $s;
-       }
-
-       /**
-        * Makes an object that, when expand()ed, will be the same as one obtained
-        * with implode()
-        *
-        * @param string $sep
-        * @param string|PPNode_DOM|DOMDocument ...$args
-        * @return array
-        */
-       public function virtualImplode( $sep, ...$args ) {
-               $out = [];
-               $first = true;
-
-               foreach ( $args as $root ) {
-                       if ( $root instanceof PPNode_DOM ) {
-                               $root = $root->node;
-                       }
-                       if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
-                               $root = [ $root ];
-                       }
-                       foreach ( $root as $node ) {
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $out[] = $sep;
-                               }
-                               $out[] = $node;
-                       }
-               }
-               return $out;
-       }
-
-       /**
-        * Virtual implode with brackets
-        * @param string $start
-        * @param string $sep
-        * @param string $end
-        * @param string|PPNode_DOM|DOMDocument ...$args
-        * @return array
-        */
-       public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
-               $out = [ $start ];
-               $first = true;
-
-               foreach ( $args as $root ) {
-                       if ( $root instanceof PPNode_DOM ) {
-                               $root = $root->node;
-                       }
-                       if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
-                               $root = [ $root ];
-                       }
-                       foreach ( $root as $node ) {
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $out[] = $sep;
-                               }
-                               $out[] = $node;
-                       }
-               }
-               $out[] = $end;
-               return $out;
-       }
-
-       public function __toString() {
-               return 'frame{}';
-       }
-
-       public function getPDBK( $level = false ) {
-               if ( $level === false ) {
-                       return $this->title->getPrefixedDBkey();
-               } else {
-                       return $this->titleCache[$level] ?? false;
-               }
-       }
-
-       /**
-        * @return array
-        */
-       public function getArguments() {
-               return [];
-       }
-
-       /**
-        * @return array
-        */
-       public function getNumberedArguments() {
-               return [];
-       }
-
-       /**
-        * @return array
-        */
-       public function getNamedArguments() {
-               return [];
-       }
-
-       /**
-        * Returns true if there are no arguments in this frame
-        *
-        * @return bool
-        */
-       public function isEmpty() {
-               return true;
-       }
-
-       /**
-        * @param int|string $name
-        * @return bool Always false in this implementation.
-        */
-       public function getArgument( $name ) {
-               return false;
-       }
-
-       /**
-        * Returns true if the infinite loop check is OK, false if a loop is detected
-        *
-        * @param Title $title
-        * @return bool
-        */
-       public function loopCheck( $title ) {
-               return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
-       }
-
-       /**
-        * Return true if the frame is a template frame
-        *
-        * @return bool
-        */
-       public function isTemplate() {
-               return false;
-       }
-
-       /**
-        * Get a title of frame
-        *
-        * @return Title
-        */
-       public function getTitle() {
-               return $this->title;
-       }
-
-       /**
-        * Set the volatile flag
-        *
-        * @param bool $flag
-        */
-       public function setVolatile( $flag = true ) {
-               $this->volatile = $flag;
-       }
-
-       /**
-        * Get the volatile flag
-        *
-        * @return bool
-        */
-       public function isVolatile() {
-               return $this->volatile;
-       }
-
-       /**
-        * Set the TTL
-        *
-        * @param int $ttl
-        */
-       public function setTTL( $ttl ) {
-               if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
-                       $this->ttl = $ttl;
-               }
-       }
-
-       /**
-        * Get the TTL
-        *
-        * @return int|null
-        */
-       public function getTTL() {
-               return $this->ttl;
-       }
-}
-
-/**
- * Expansion frame with template arguments
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPTemplateFrame_DOM extends PPFrame_DOM {
-
-       public $numberedArgs, $namedArgs;
-
-       /**
-        * @var PPFrame_DOM
-        */
-       public $parent;
-       public $numberedExpansionCache, $namedExpansionCache;
-
-       /**
-        * @param Preprocessor $preprocessor
-        * @param bool|PPFrame_DOM $parent
-        * @param array $numberedArgs
-        * @param array $namedArgs
-        * @param bool|Title $title
-        */
-       public function __construct( $preprocessor, $parent = false, $numberedArgs = [],
-               $namedArgs = [], $title = false
-       ) {
-               parent::__construct( $preprocessor );
-
-               $this->parent = $parent;
-               $this->numberedArgs = $numberedArgs;
-               $this->namedArgs = $namedArgs;
-               $this->title = $title;
-               $pdbk = $title ? $title->getPrefixedDBkey() : false;
-               $this->titleCache = $parent->titleCache;
-               $this->titleCache[] = $pdbk;
-               $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
-               if ( $pdbk !== false ) {
-                       $this->loopCheckHash[$pdbk] = true;
-               }
-               $this->depth = $parent->depth + 1;
-               $this->numberedExpansionCache = $this->namedExpansionCache = [];
-       }
-
-       public function __toString() {
-               $s = 'tplframe{';
-               $first = true;
-               $args = $this->numberedArgs + $this->namedArgs;
-               foreach ( $args as $name => $value ) {
-                       if ( $first ) {
-                               $first = false;
-                       } else {
-                               $s .= ', ';
-                       }
-                       $s .= "\"$name\":\"" .
-                               str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"';
-               }
-               $s .= '}';
-               return $s;
-       }
-
-       /**
-        * @throws MWException
-        * @param string|int $key
-        * @param string|PPNode_DOM|DOMDocument $root
-        * @param int $flags
-        * @return string
-        */
-       public function cachedExpand( $key, $root, $flags = 0 ) {
-               if ( isset( $this->parent->childExpansionCache[$key] ) ) {
-                       return $this->parent->childExpansionCache[$key];
-               }
-               $retval = $this->expand( $root, $flags );
-               if ( !$this->isVolatile() ) {
-                       $this->parent->childExpansionCache[$key] = $retval;
-               }
-               return $retval;
-       }
-
-       /**
-        * Returns true if there are no arguments in this frame
-        *
-        * @return bool
-        */
-       public function isEmpty() {
-               return !count( $this->numberedArgs ) && !count( $this->namedArgs );
-       }
-
-       public function getArguments() {
-               $arguments = [];
-               foreach ( array_merge(
-                               array_keys( $this->numberedArgs ),
-                               array_keys( $this->namedArgs ) ) as $key ) {
-                       $arguments[$key] = $this->getArgument( $key );
-               }
-               return $arguments;
-       }
-
-       public function getNumberedArguments() {
-               $arguments = [];
-               foreach ( array_keys( $this->numberedArgs ) as $key ) {
-                       $arguments[$key] = $this->getArgument( $key );
-               }
-               return $arguments;
-       }
-
-       public function getNamedArguments() {
-               $arguments = [];
-               foreach ( array_keys( $this->namedArgs ) as $key ) {
-                       $arguments[$key] = $this->getArgument( $key );
-               }
-               return $arguments;
-       }
-
-       /**
-        * @param int $index
-        * @return string|bool
-        */
-       public function getNumberedArgument( $index ) {
-               if ( !isset( $this->numberedArgs[$index] ) ) {
-                       return false;
-               }
-               if ( !isset( $this->numberedExpansionCache[$index] ) ) {
-                       # No trimming for unnamed arguments
-                       $this->numberedExpansionCache[$index] = $this->parent->expand(
-                               $this->numberedArgs[$index],
-                               PPFrame::STRIP_COMMENTS
-                       );
-               }
-               return $this->numberedExpansionCache[$index];
-       }
-
-       /**
-        * @param string $name
-        * @return string|bool
-        */
-       public function getNamedArgument( $name ) {
-               if ( !isset( $this->namedArgs[$name] ) ) {
-                       return false;
-               }
-               if ( !isset( $this->namedExpansionCache[$name] ) ) {
-                       # Trim named arguments post-expand, for backwards compatibility
-                       $this->namedExpansionCache[$name] = trim(
-                               $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
-               }
-               return $this->namedExpansionCache[$name];
-       }
-
-       /**
-        * @param int|string $name
-        * @return string|bool
-        */
-       public function getArgument( $name ) {
-               $text = $this->getNumberedArgument( $name );
-               if ( $text === false ) {
-                       $text = $this->getNamedArgument( $name );
-               }
-               return $text;
-       }
-
-       /**
-        * Return true if the frame is a template frame
-        *
-        * @return bool
-        */
-       public function isTemplate() {
-               return true;
-       }
-
-       public function setVolatile( $flag = true ) {
-               parent::setVolatile( $flag );
-               $this->parent->setVolatile( $flag );
-       }
-
-       public function setTTL( $ttl ) {
-               parent::setTTL( $ttl );
-               $this->parent->setTTL( $ttl );
-       }
-}
-
-/**
- * Expansion frame with custom arguments
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPCustomFrame_DOM extends PPFrame_DOM {
-
-       public $args;
-
-       public function __construct( $preprocessor, $args ) {
-               parent::__construct( $preprocessor );
-               $this->args = $args;
-       }
-
-       public function __toString() {
-               $s = 'cstmframe{';
-               $first = true;
-               foreach ( $this->args as $name => $value ) {
-                       if ( $first ) {
-                               $first = false;
-                       } else {
-                               $s .= ', ';
-                       }
-                       $s .= "\"$name\":\"" .
-                               str_replace( '"', '\\"', $value->__toString() ) . '"';
-               }
-               $s .= '}';
-               return $s;
-       }
-
-       /**
-        * @return bool
-        */
-       public function isEmpty() {
-               return !count( $this->args );
-       }
-
-       /**
-        * @param int|string $index
-        * @return string|bool
-        */
-       public function getArgument( $index ) {
-               return $this->args[$index] ?? false;
-       }
-
-       public function getArguments() {
-               return $this->args;
-       }
-}
-
-/**
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPNode_DOM implements PPNode {
-
-       /**
-        * @var DOMElement
-        */
-       public $node;
-       public $xpath;
-
-       public function __construct( $node, $xpath = false ) {
-               $this->node = $node;
-       }
-
-       /**
-        * @return DOMXPath
-        */
-       public function getXPath() {
-               if ( $this->xpath === null ) {
-                       $this->xpath = new DOMXPath( $this->node->ownerDocument );
-               }
-               return $this->xpath;
-       }
-
-       public function __toString() {
-               if ( $this->node instanceof DOMNodeList ) {
-                       $s = '';
-                       foreach ( $this->node as $node ) {
-                               $s .= $node->ownerDocument->saveXML( $node );
-                       }
-               } else {
-                       $s = $this->node->ownerDocument->saveXML( $this->node );
-               }
-               return $s;
-       }
-
-       /**
-        * @return bool|PPNode_DOM
-        */
-       public function getChildren() {
-               return $this->node->childNodes ? new self( $this->node->childNodes ) : false;
-       }
-
-       /**
-        * @return bool|PPNode_DOM
-        */
-       public function getFirstChild() {
-               return $this->node->firstChild ? new self( $this->node->firstChild ) : false;
-       }
-
-       /**
-        * @return bool|PPNode_DOM
-        */
-       public function getNextSibling() {
-               return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false;
-       }
-
-       /**
-        * @param string $type
-        *
-        * @return bool|PPNode_DOM
-        */
-       public function getChildrenOfType( $type ) {
-               return new self( $this->getXPath()->query( $type, $this->node ) );
-       }
-
-       /**
-        * @return int
-        */
-       public function getLength() {
-               if ( $this->node instanceof DOMNodeList ) {
-                       return $this->node->length;
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @param int $i
-        * @return bool|PPNode_DOM
-        */
-       public function item( $i ) {
-               $item = $this->node->item( $i );
-               return $item ? new self( $item ) : false;
-       }
-
-       /**
-        * @return string
-        */
-       public function getName() {
-               if ( $this->node instanceof DOMNodeList ) {
-                       return '#nodelist';
-               } else {
-                       return $this->node->nodeName;
-               }
-       }
-
-       /**
-        * Split a "<part>" node into an associative array containing:
-        *  - name          PPNode name
-        *  - index         String index
-        *  - value         PPNode value
-        *
-        * @throws MWException
-        * @return array
-        */
-       public function splitArg() {
-               $xpath = $this->getXPath();
-               $names = $xpath->query( 'name', $this->node );
-               $values = $xpath->query( 'value', $this->node );
-               if ( !$names->length || !$values->length ) {
-                       throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
-               }
-               $name = $names->item( 0 );
-               $index = $name->getAttribute( 'index' );
-               return [
-                       'name' => new self( $name ),
-                       'index' => $index,
-                       'value' => new self( $values->item( 0 ) ) ];
-       }
-
-       /**
-        * Split an "<ext>" node into an associative array containing name, attr, inner and close
-        * All values in the resulting array are PPNodes. Inner and close are optional.
-        *
-        * @throws MWException
-        * @return array
-        */
-       public function splitExt() {
-               $xpath = $this->getXPath();
-               $names = $xpath->query( 'name', $this->node );
-               $attrs = $xpath->query( 'attr', $this->node );
-               $inners = $xpath->query( 'inner', $this->node );
-               $closes = $xpath->query( 'close', $this->node );
-               if ( !$names->length || !$attrs->length ) {
-                       throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
-               }
-               $parts = [
-                       'name' => new self( $names->item( 0 ) ),
-                       'attr' => new self( $attrs->item( 0 ) ) ];
-               if ( $inners->length ) {
-                       $parts['inner'] = new self( $inners->item( 0 ) );
-               }
-               if ( $closes->length ) {
-                       $parts['close'] = new self( $closes->item( 0 ) );
-               }
-               return $parts;
-       }
-
-       /**
-        * Split a "<h>" node
-        * @throws MWException
-        * @return array
-        */
-       public function splitHeading() {
-               if ( $this->getName() !== 'h' ) {
-                       throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
-               }
-               return [
-                       'i' => $this->node->getAttribute( 'i' ),
-                       'level' => $this->node->getAttribute( 'level' ),
-                       'contents' => $this->getChildren()
-               ];
-       }
-}