Whitelist existing violations, but enable the sniff to prevent
any new occurrences.
-->
- <exclude-pattern>*/includes/parser/Preprocessor_DOM\.php</exclude-pattern>
- <exclude-pattern>*/includes/parser/Preprocessor_Hash\.php</exclude-pattern>
- <exclude-pattern>*/includes/parser/Preprocessor\.php</exclude-pattern>
<exclude-pattern>*/maintenance/dumpIterator\.php</exclude-pattern>
<exclude-pattern>*/maintenance/Maintenance\.php</exclude-pattern>
<exclude-pattern>*/maintenance/findDeprecated\.php</exclude-pattern>
'PHPVersionCheck' => __DIR__ . '/includes/PHPVersionCheck.php',
'PNGHandler' => __DIR__ . '/includes/media/PNGHandler.php',
'PNGMetadataExtractor' => __DIR__ . '/includes/media/PNGMetadataExtractor.php',
- 'PPCustomFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
- 'PPCustomFrame_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPDPart' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
- 'PPDPart_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPDStack' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
- 'PPDStackElement' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
- 'PPDStackElement_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPDStack_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPFrame' => __DIR__ . '/includes/parser/Preprocessor.php',
- 'PPFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
- 'PPFrame_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
+ 'PPCustomFrame_DOM' => __DIR__ . '/includes/parser/PPCustomFrame_DOM.php',
+ 'PPCustomFrame_Hash' => __DIR__ . '/includes/parser/PPCustomFrame_Hash.php',
+ 'PPDPart' => __DIR__ . '/includes/parser/PPDPart.php',
+ 'PPDPart_Hash' => __DIR__ . '/includes/parser/PPDPart_Hash.php',
+ 'PPDStack' => __DIR__ . '/includes/parser/PPDStack.php',
+ 'PPDStackElement' => __DIR__ . '/includes/parser/PPDStackElement.php',
+ 'PPDStackElement_Hash' => __DIR__ . '/includes/parser/PPDStackElement_Hash.php',
+ 'PPDStack_Hash' => __DIR__ . '/includes/parser/PPDStack_Hash.php',
+ 'PPFrame' => __DIR__ . '/includes/parser/PPFrame.php',
+ 'PPFrame_DOM' => __DIR__ . '/includes/parser/PPFrame_DOM.php',
+ 'PPFrame_Hash' => __DIR__ . '/includes/parser/PPFrame_Hash.php',
'PPFuzzTest' => __DIR__ . '/maintenance/preprocessorFuzzTest.php',
'PPFuzzTester' => __DIR__ . '/maintenance/preprocessorFuzzTest.php',
'PPFuzzUser' => __DIR__ . '/maintenance/preprocessorFuzzTest.php',
- 'PPNode' => __DIR__ . '/includes/parser/Preprocessor.php',
- 'PPNode_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
- 'PPNode_Hash_Array' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPNode_Hash_Attr' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPNode_Hash_Text' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPNode_Hash_Tree' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PPTemplateFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
- 'PPTemplateFrame_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
+ 'PPNode' => __DIR__ . '/includes/parser/PPNode.php',
+ 'PPNode_DOM' => __DIR__ . '/includes/parser/PPNode_DOM.php',
+ 'PPNode_Hash_Array' => __DIR__ . '/includes/parser/PPNode_Hash_Array.php',
+ 'PPNode_Hash_Attr' => __DIR__ . '/includes/parser/PPNode_Hash_Attr.php',
+ 'PPNode_Hash_Text' => __DIR__ . '/includes/parser/PPNode_Hash_Text.php',
+ 'PPNode_Hash_Tree' => __DIR__ . '/includes/parser/PPNode_Hash_Tree.php',
+ 'PPTemplateFrame_DOM' => __DIR__ . '/includes/parser/PPTemplateFrame_DOM.php',
+ 'PPTemplateFrame_Hash' => __DIR__ . '/includes/parser/PPTemplateFrame_Hash.php',
'PackedHoverImageGallery' => __DIR__ . '/includes/gallery/PackedHoverImageGallery.php',
'PackedImageGallery' => __DIR__ . '/includes/gallery/PackedImageGallery.php',
'PackedOverlayImageGallery' => __DIR__ . '/includes/gallery/PackedOverlayImageGallery.php',
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * Expansion frame with custom arguments
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPCustomFrame_Hash extends PPFrame_Hash {
+
+ 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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPDPart_Hash extends PPDPart {
+
+ public function __construct( $out = '' ) {
+ if ( $out !== '' ) {
+ $accum = [ $out ];
+ } else {
+ $accum = [];
+ }
+ parent::__construct( $accum );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * 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();
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPDStackElement_Hash extends PPDStackElement {
+
+ public function __construct( $data = [] ) {
+ $this->partClass = PPDPart_Hash::class;
+ parent::__construct( $data );
+ }
+
+ /**
+ * Get the accumulator that would result if the close is not found.
+ *
+ * @param int|bool $openingCount
+ * @return array
+ */
+ public function breakSyntax( $openingCount = false ) {
+ if ( $this->open == "\n" ) {
+ $accum = array_merge( [ $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 )
+ );
+ $accum = [ $this->savedPrefix . $s ];
+ $lastIndex = 0;
+ $first = true;
+ foreach ( $this->parts as $part ) {
+ if ( $first ) {
+ $first = false;
+ } elseif ( is_string( $accum[$lastIndex] ) ) {
+ $accum[$lastIndex] .= '|';
+ } else {
+ $accum[++$lastIndex] = '|';
+ }
+ foreach ( $part->out as $node ) {
+ if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
+ $accum[$lastIndex] .= $node;
+ } else {
+ $accum[++$lastIndex] = $node;
+ }
+ }
+ }
+ }
+ return $accum;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * Stack class to help Preprocessor::preprocessToObj()
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPDStack_Hash extends PPDStack {
+
+ public function __construct() {
+ $this->elementClass = PPDStackElement_Hash::class;
+ parent::__construct();
+ $this->rootAccum = [];
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+interface PPFrame {
+ const NO_ARGS = 1;
+ const NO_TEMPLATES = 2;
+ const STRIP_COMMENTS = 4;
+ const NO_IGNORE = 8;
+ const RECOVER_COMMENTS = 16;
+ const NO_TAGS = 32;
+
+ const RECOVER_ORIG = self::NO_ARGS | self::NO_TEMPLATES | self::NO_IGNORE |
+ self::RECOVER_COMMENTS | self::NO_TAGS;
+
+ /** This constant exists when $indexOffset is supported in newChild() */
+ const SUPPORTS_INDEX_OFFSET = 1;
+
+ /**
+ * Create a child frame
+ *
+ * @param array|bool $args
+ * @param bool|Title $title
+ * @param int $indexOffset A number subtracted from the index attributes of the arguments
+ *
+ * @return PPFrame
+ */
+ public function newChild( $args = false, $title = false, $indexOffset = 0 );
+
+ /**
+ * Expand a document tree node, caching the result on its parent with the given key
+ * @param string|int $key
+ * @param string|PPNode $root
+ * @param int $flags
+ * @return string
+ */
+ public function cachedExpand( $key, $root, $flags = 0 );
+
+ /**
+ * Expand a document tree node
+ * @param string|PPNode $root
+ * @param int $flags
+ * @return string
+ */
+ public function expand( $root, $flags = 0 );
+
+ /**
+ * Implode with flags for expand()
+ * @param string $sep
+ * @param int $flags
+ * @param string|PPNode $args,...
+ * @return string
+ */
+ public function implodeWithFlags( $sep, $flags /*, ... */ );
+
+ /**
+ * Implode with no flags specified
+ * @param string $sep
+ * @param string|PPNode $args,...
+ * @return string
+ */
+ public function implode( $sep /*, ... */ );
+
+ /**
+ * Makes an object that, when expand()ed, will be the same as one obtained
+ * with implode()
+ * @param string $sep
+ * @param string|PPNode $args,...
+ * @return PPNode
+ */
+ public function virtualImplode( $sep /*, ... */ );
+
+ /**
+ * Virtual implode with brackets
+ * @param string $start
+ * @param string $sep
+ * @param string $end
+ * @param string|PPNode $args,...
+ * @return PPNode
+ */
+ public function virtualBracketedImplode( $start, $sep, $end /*, ... */ );
+
+ /**
+ * Returns true if there are no arguments in this frame
+ *
+ * @return bool
+ */
+ public function isEmpty();
+
+ /**
+ * Returns all arguments of this frame
+ * @return array
+ */
+ public function getArguments();
+
+ /**
+ * Returns all numbered arguments of this frame
+ * @return array
+ */
+ public function getNumberedArguments();
+
+ /**
+ * Returns all named arguments of this frame
+ * @return array
+ */
+ public function getNamedArguments();
+
+ /**
+ * Get an argument to this frame by name
+ * @param int|string $name
+ * @return string|bool
+ */
+ public function getArgument( $name );
+
+ /**
+ * 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 true if the frame is a template frame
+ * @return bool
+ */
+ public function isTemplate();
+
+ /**
+ * Set the "volatile" flag.
+ *
+ * Note that this is somewhat of a "hack" in order to make extensions
+ * with side effects (such as Cite) work with the PHP parser. New
+ * extensions should be written in a way that they do not need this
+ * function, because other parsers (such as Parsoid) are not guaranteed
+ * to respect it, and it may be removed in the future.
+ *
+ * @param bool $flag
+ */
+ public function setVolatile( $flag = true );
+
+ /**
+ * Get the "volatile" flag.
+ *
+ * Callers should avoid caching the result of an expansion if it has the
+ * volatile flag set.
+ *
+ * @see self::setVolatile()
+ * @return bool
+ */
+ public function isVolatile();
+
+ /**
+ * Get the TTL of the frame's output.
+ *
+ * This is the maximum amount of time, in seconds, that this frame's
+ * output should be cached for. A value of null indicates that no
+ * maximum has been specified.
+ *
+ * Note that this TTL only applies to caching frames as parts of pages.
+ * It is not relevant to caching the entire rendered output of a page.
+ *
+ * @return int|null
+ */
+ public function getTTL();
+
+ /**
+ * Set the TTL of the output of this frame and all of its ancestors.
+ * Has no effect if the new TTL is greater than the one already set.
+ * Note that it is the caller's responsibility to change the cache
+ * expiry of the page as a whole, if such behavior is desired.
+ *
+ * @see self::getTTL()
+ * @param int $ttl
+ */
+ public function setTTL( $ttl );
+
+ /**
+ * Get a title of frame
+ *
+ * @return Title
+ */
+ public function getTitle();
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * An expansion frame, used as a context to expand the result of preprocessToObj()
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPFrame_Hash implements PPFrame {
+
+ /**
+ * @var Parser
+ */
+ public $parser;
+
+ /**
+ * @var Preprocessor
+ */
+ public $preprocessor;
+
+ /**
+ * @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 array|bool|PPNode_Hash_Array $args
+ * @param Title|bool $title
+ * @param int $indexOffset
+ * @throws MWException
+ * @return PPTemplateFrame_Hash
+ */
+ public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
+ $namedArgs = [];
+ $numberedArgs = [];
+ if ( $title === false ) {
+ $title = $this->title;
+ }
+ if ( $args !== false ) {
+ if ( $args instanceof PPNode_Hash_Array ) {
+ $args = $args->value;
+ } elseif ( !is_array( $args ) ) {
+ throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
+ }
+ foreach ( $args as $arg ) {
+ $bits = $arg->splitArg();
+ if ( $bits['index'] !== '' ) {
+ // Numbered parameter
+ $index = $bits['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] = $bits['value'];
+ unset( $namedArgs[$index] );
+ } else {
+ // Named parameter
+ $name = trim( $this->expand( $bits['name'], 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] = $bits['value'];
+ unset( $numberedArgs[$name] );
+ }
+ }
+ }
+ return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
+ }
+
+ /**
+ * @throws MWException
+ * @param string|int $key
+ * @param string|PPNode $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 $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 = [ '', '' ];
+ $iteratorStack = [ false, $root ];
+ $indexStack = [ 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;
+ $contextName = false;
+ $contextChildren = false;
+
+ if ( $contextNode === false ) {
+ // nothing to do
+ } elseif ( is_string( $contextNode ) ) {
+ $out .= $contextNode;
+ } elseif ( $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 ) {
+ $contextName = $contextNode->name;
+ $contextChildren = $contextNode->getRawChildren();
+ } elseif ( is_array( $contextNode ) ) {
+ // Node descriptor array
+ if ( count( $contextNode ) !== 2 ) {
+ throw new MWException( __METHOD__ .
+ ': found an array where a node descriptor should be' );
+ }
+ list( $contextName, $contextChildren ) = $contextNode;
+ } else {
+ throw new MWException( __METHOD__ . ': Invalid parameter type' );
+ }
+
+ // Handle node descriptor array or tree object
+ if ( $contextName === false ) {
+ // Not a node, already handled above
+ } elseif ( $contextName[0] === '@' ) {
+ // Attribute: no output
+ } elseif ( $contextName === 'template' ) {
+ # Double-brace expansion
+ $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
+ 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 ( $contextName === 'tplarg' ) {
+ # Triple-brace expansion
+ $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
+ 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 ( $contextName === '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( $contextChildren[0] );
+ } else {
+ # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
+ $out .= $contextChildren[0];
+ }
+ } elseif ( $contextName === '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 .= $contextChildren[0];
+ } else {
+ // $out .= '';
+ }
+ } elseif ( $contextName === 'ext' ) {
+ # Extension tag
+ $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
+ [ 'attr' => null, 'inner' => null, 'close' => null ];
+ if ( $flags & PPFrame::NO_TAGS ) {
+ $s = '<' . $bits['name']->getFirstChild()->value;
+ if ( $bits['attr'] ) {
+ $s .= $bits['attr']->getFirstChild()->value;
+ }
+ if ( $bits['inner'] ) {
+ $s .= '>' . $bits['inner']->getFirstChild()->value;
+ if ( $bits['close'] ) {
+ $s .= $bits['close']->getFirstChild()->value;
+ }
+ } else {
+ $s .= '/>';
+ }
+ $out .= $s;
+ } else {
+ $out .= $this->parser->extensionSubstitution( $bits, $this );
+ }
+ } elseif ( $contextName === 'h' ) {
+ # Heading
+ if ( $this->parser->ot['html'] ) {
+ # Expand immediately and insert heading index marker
+ $s = $this->expand( $contextChildren, $flags );
+ $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
+ $titleText = $this->title->getPrefixedDBkey();
+ $this->parser->mHeadings[] = [ $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 = $contextChildren;
+ }
+ } else {
+ # Generic recursive expansion
+ $newIterator = $contextChildren;
+ }
+
+ 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];
+ }
+
+ /**
+ * @param string $sep
+ * @param int $flags
+ * @param string|PPNode ...$args
+ * @return string
+ */
+ public function implodeWithFlags( $sep, $flags, ...$args ) {
+ $first = true;
+ $s = '';
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_Hash_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $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 ...$args
+ * @return string
+ */
+ public function implode( $sep, ...$args ) {
+ $first = true;
+ $s = '';
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_Hash_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $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 ...$args
+ * @return PPNode_Hash_Array
+ */
+ public function virtualImplode( $sep, ...$args ) {
+ $out = [];
+ $first = true;
+
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_Hash_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $root = [ $root ];
+ }
+ foreach ( $root as $node ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $out[] = $sep;
+ }
+ $out[] = $node;
+ }
+ }
+ return new PPNode_Hash_Array( $out );
+ }
+
+ /**
+ * Virtual implode with brackets
+ *
+ * @param string $start
+ * @param string $sep
+ * @param string $end
+ * @param string|PPNode ...$args
+ * @return PPNode_Hash_Array
+ */
+ public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
+ $out = [ $start ];
+ $first = true;
+
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_Hash_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $root = [ $root ];
+ }
+ foreach ( $root as $node ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $out[] = $sep;
+ }
+ $out[] = $node;
+ }
+ }
+ $out[] = $end;
+ return new PPNode_Hash_Array( $out );
+ }
+
+ public function __toString() {
+ return 'frame{}';
+ }
+
+ /**
+ * @param bool $level
+ * @return array|bool|string
+ */
+ 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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * There are three types of nodes:
+ * * Tree nodes, which have a name and contain other nodes as children
+ * * Array nodes, which also contain other nodes but aren't considered part of a tree
+ * * Leaf nodes, which contain the actual data
+ *
+ * This interface provides access to the tree structure and to the contents of array nodes,
+ * but it does not provide access to the internal structure of leaf nodes. Access to leaf
+ * data is provided via two means:
+ * * PPFrame::expand(), which provides expanded text
+ * * The PPNode::split*() functions, which provide metadata about certain types of tree node
+ * @ingroup Parser
+ */
+interface PPNode {
+ /**
+ * Get an array-type node containing the children of this node.
+ * Returns false if this is not a tree node.
+ * @return PPNode
+ */
+ public function getChildren();
+
+ /**
+ * Get the first child of a tree node. False if there isn't one.
+ *
+ * @return PPNode
+ */
+ public function getFirstChild();
+
+ /**
+ * Get the next sibling of any node. False if there isn't one
+ * @return PPNode
+ */
+ public function getNextSibling();
+
+ /**
+ * Get all children of this tree node which have a given name.
+ * Returns an array-type node, or false if this is not a tree node.
+ * @param string $type
+ * @return bool|PPNode
+ */
+ public function getChildrenOfType( $type );
+
+ /**
+ * Returns the length of the array, or false if this is not an array-type node
+ */
+ public function getLength();
+
+ /**
+ * Returns an item of an array-type node
+ * @param int $i
+ * @return bool|PPNode
+ */
+ public function item( $i );
+
+ /**
+ * Get the name of this node. The following names are defined here:
+ *
+ * h A heading node.
+ * template A double-brace node.
+ * tplarg A triple-brace node.
+ * title The first argument to a template or tplarg node.
+ * part Subsequent arguments to a template or tplarg node.
+ * #nodelist An array-type node
+ *
+ * The subclass may define various other names for tree and leaf nodes.
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Split a "<part>" node into an associative array containing:
+ * name PPNode name
+ * index String index
+ * value PPNode value
+ * @return array
+ */
+ public function splitArg();
+
+ /**
+ * 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.
+ * @return array
+ */
+ public function splitExt();
+
+ /**
+ * Split an "<h>" node
+ * @return array
+ */
+ public function splitHeading();
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @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()
+ ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPNode_Hash_Array implements PPNode {
+
+ public $value;
+
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ public function __toString() {
+ return var_export( $this, true );
+ }
+
+ public function getLength() {
+ return count( $this->value );
+ }
+
+ public function item( $i ) {
+ return $this->value[$i];
+ }
+
+ public function getName() {
+ return '#nodelist';
+ }
+
+ public function getNextSibling() {
+ return false;
+ }
+
+ public function getChildren() {
+ return false;
+ }
+
+ public function getFirstChild() {
+ return false;
+ }
+
+ public function getChildrenOfType( $name ) {
+ return false;
+ }
+
+ public function splitArg() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitExt() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitHeading() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPNode_Hash_Attr implements PPNode {
+
+ public $name, $value;
+ private $store, $index;
+
+ /**
+ * Construct an object using the data from $store[$index]. The rest of the
+ * store array can be accessed via getNextSibling().
+ *
+ * @param array $store
+ * @param int $index
+ */
+ public function __construct( array $store, $index ) {
+ $descriptor = $store[$index];
+ if ( $descriptor[PPNode_Hash_Tree::NAME][0] !== '@' ) {
+ throw new MWException( __METHOD__ . ': invalid name in attribute descriptor' );
+ }
+ $this->name = substr( $descriptor[PPNode_Hash_Tree::NAME], 1 );
+ $this->value = $descriptor[PPNode_Hash_Tree::CHILDREN][0];
+ $this->store = $store;
+ $this->index = $index;
+ }
+
+ public function __toString() {
+ return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
+ }
+
+ public function getName() {
+ return $this->name;
+ }
+
+ public function getNextSibling() {
+ return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
+ }
+
+ public function getChildren() {
+ return false;
+ }
+
+ public function getFirstChild() {
+ return false;
+ }
+
+ public function getChildrenOfType( $name ) {
+ return false;
+ }
+
+ public function getLength() {
+ return false;
+ }
+
+ public function item( $i ) {
+ return false;
+ }
+
+ public function splitArg() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitExt() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitHeading() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPNode_Hash_Text implements PPNode {
+
+ public $value;
+ private $store, $index;
+
+ /**
+ * Construct an object using the data from $store[$index]. The rest of the
+ * store array can be accessed via getNextSibling().
+ *
+ * @param array $store
+ * @param int $index
+ */
+ public function __construct( array $store, $index ) {
+ $this->value = $store[$index];
+ if ( !is_scalar( $this->value ) ) {
+ throw new MWException( __CLASS__ . ' given object instead of string' );
+ }
+ $this->store = $store;
+ $this->index = $index;
+ }
+
+ public function __toString() {
+ return htmlspecialchars( $this->value );
+ }
+
+ public function getNextSibling() {
+ return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
+ }
+
+ public function getChildren() {
+ return false;
+ }
+
+ public function getFirstChild() {
+ return false;
+ }
+
+ public function getChildrenOfType( $name ) {
+ return false;
+ }
+
+ public function getLength() {
+ return false;
+ }
+
+ public function item( $i ) {
+ return false;
+ }
+
+ public function getName() {
+ return '#text';
+ }
+
+ public function splitArg() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitExt() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+
+ public function splitHeading() {
+ throw new MWException( __METHOD__ . ': not supported' );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPNode_Hash_Tree implements PPNode {
+
+ public $name;
+
+ /**
+ * The store array for children of this node. It is "raw" in the sense that
+ * nodes are two-element arrays ("descriptors") rather than PPNode_Hash_*
+ * objects.
+ */
+ private $rawChildren;
+
+ /**
+ * The store array for the siblings of this node, including this node itself.
+ */
+ private $store;
+
+ /**
+ * The index into $this->store which contains the descriptor of this node.
+ */
+ private $index;
+
+ /**
+ * The offset of the name within descriptors, used in some places for
+ * readability.
+ */
+ const NAME = 0;
+
+ /**
+ * The offset of the child list within descriptors, used in some places for
+ * readability.
+ */
+ const CHILDREN = 1;
+
+ /**
+ * Construct an object using the data from $store[$index]. The rest of the
+ * store array can be accessed via getNextSibling().
+ *
+ * @param array $store
+ * @param int $index
+ */
+ public function __construct( array $store, $index ) {
+ $this->store = $store;
+ $this->index = $index;
+ list( $this->name, $this->rawChildren ) = $this->store[$index];
+ }
+
+ /**
+ * Construct an appropriate PPNode_Hash_* object with a class that depends
+ * on what is at the relevant store index.
+ *
+ * @param array $store
+ * @param int $index
+ * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
+ * @throws MWException
+ */
+ public static function factory( array $store, $index ) {
+ if ( !isset( $store[$index] ) ) {
+ return false;
+ }
+
+ $descriptor = $store[$index];
+ if ( is_string( $descriptor ) ) {
+ $class = PPNode_Hash_Text::class;
+ } elseif ( is_array( $descriptor ) ) {
+ if ( $descriptor[self::NAME][0] === '@' ) {
+ $class = PPNode_Hash_Attr::class;
+ } else {
+ $class = self::class;
+ }
+ } else {
+ throw new MWException( __METHOD__ . ': invalid node descriptor' );
+ }
+ return new $class( $store, $index );
+ }
+
+ /**
+ * Convert a node to XML, for debugging
+ * @return string
+ */
+ public function __toString() {
+ $inner = '';
+ $attribs = '';
+ for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
+ if ( $node instanceof PPNode_Hash_Attr ) {
+ $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
+ } else {
+ $inner .= $node->__toString();
+ }
+ }
+ if ( $inner === '' ) {
+ return "<{$this->name}$attribs/>";
+ } else {
+ return "<{$this->name}$attribs>$inner</{$this->name}>";
+ }
+ }
+
+ /**
+ * @return PPNode_Hash_Array
+ */
+ public function getChildren() {
+ $children = [];
+ foreach ( $this->rawChildren as $i => $child ) {
+ $children[] = self::factory( $this->rawChildren, $i );
+ }
+ return new PPNode_Hash_Array( $children );
+ }
+
+ /**
+ * Get the first child, or false if there is none. Note that this will
+ * return a temporary proxy object: different instances will be returned
+ * if this is called more than once on the same node.
+ *
+ * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|bool
+ */
+ public function getFirstChild() {
+ if ( !isset( $this->rawChildren[0] ) ) {
+ return false;
+ } else {
+ return self::factory( $this->rawChildren, 0 );
+ }
+ }
+
+ /**
+ * Get the next sibling, or false if there is none. Note that this will
+ * return a temporary proxy object: different instances will be returned
+ * if this is called more than once on the same node.
+ *
+ * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|bool
+ */
+ public function getNextSibling() {
+ return self::factory( $this->store, $this->index + 1 );
+ }
+
+ /**
+ * Get an array of the children with a given node name
+ *
+ * @param string $name
+ * @return PPNode_Hash_Array
+ */
+ public function getChildrenOfType( $name ) {
+ $children = [];
+ foreach ( $this->rawChildren as $i => $child ) {
+ if ( is_array( $child ) && $child[self::NAME] === $name ) {
+ $children[] = self::factory( $this->rawChildren, $i );
+ }
+ }
+ return new PPNode_Hash_Array( $children );
+ }
+
+ /**
+ * Get the raw child array. For internal use.
+ * @return array
+ */
+ public function getRawChildren() {
+ return $this->rawChildren;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getLength() {
+ return false;
+ }
+
+ /**
+ * @param int $i
+ * @return bool
+ */
+ public function item( $i ) {
+ return false;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * 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() {
+ return self::splitRawArg( $this->rawChildren );
+ }
+
+ /**
+ * Like splitArg() but for a raw child array. For internal use only.
+ * @param array $children
+ * @return array
+ */
+ public static function splitRawArg( array $children ) {
+ $bits = [];
+ foreach ( $children as $i => $child ) {
+ if ( !is_array( $child ) ) {
+ continue;
+ }
+ if ( $child[self::NAME] === 'name' ) {
+ $bits['name'] = new self( $children, $i );
+ if ( isset( $child[self::CHILDREN][0][self::NAME] )
+ && $child[self::CHILDREN][0][self::NAME] === '@index'
+ ) {
+ $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
+ }
+ } elseif ( $child[self::NAME] === 'value' ) {
+ $bits['value'] = new self( $children, $i );
+ }
+ }
+
+ if ( !isset( $bits['name'] ) ) {
+ throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
+ }
+ if ( !isset( $bits['index'] ) ) {
+ $bits['index'] = '';
+ }
+ return $bits;
+ }
+
+ /**
+ * 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() {
+ return self::splitRawExt( $this->rawChildren );
+ }
+
+ /**
+ * Like splitExt() but for a raw child array. For internal use only.
+ * @param array $children
+ * @return array
+ */
+ public static function splitRawExt( array $children ) {
+ $bits = [];
+ foreach ( $children as $i => $child ) {
+ if ( !is_array( $child ) ) {
+ continue;
+ }
+ switch ( $child[self::NAME] ) {
+ case 'name':
+ $bits['name'] = new self( $children, $i );
+ break;
+ case 'attr':
+ $bits['attr'] = new self( $children, $i );
+ break;
+ case 'inner':
+ $bits['inner'] = new self( $children, $i );
+ break;
+ case 'close':
+ $bits['close'] = new self( $children, $i );
+ break;
+ }
+ }
+ if ( !isset( $bits['name'] ) ) {
+ throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
+ }
+ return $bits;
+ }
+
+ /**
+ * Split an "<h>" node
+ *
+ * @throws MWException
+ * @return array
+ */
+ public function splitHeading() {
+ if ( $this->name !== 'h' ) {
+ throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
+ }
+ return self::splitRawHeading( $this->rawChildren );
+ }
+
+ /**
+ * Like splitHeading() but for a raw child array. For internal use only.
+ * @param array $children
+ * @return array
+ */
+ public static function splitRawHeading( array $children ) {
+ $bits = [];
+ foreach ( $children as $i => $child ) {
+ if ( !is_array( $child ) ) {
+ continue;
+ }
+ if ( $child[self::NAME] === '@i' ) {
+ $bits['i'] = $child[self::CHILDREN][0];
+ } elseif ( $child[self::NAME] === '@level' ) {
+ $bits['level'] = $child[self::CHILDREN][0];
+ }
+ }
+ if ( !isset( $bits['i'] ) ) {
+ throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
+ }
+ return $bits;
+ }
+
+ /**
+ * Split a "<template>" or "<tplarg>" node
+ *
+ * @throws MWException
+ * @return array
+ */
+ public function splitTemplate() {
+ return self::splitRawTemplate( $this->rawChildren );
+ }
+
+ /**
+ * Like splitTemplate() but for a raw child array. For internal use only.
+ * @param array $children
+ * @return array
+ */
+ public static function splitRawTemplate( array $children ) {
+ $parts = [];
+ $bits = [ 'lineStart' => '' ];
+ foreach ( $children as $i => $child ) {
+ if ( !is_array( $child ) ) {
+ continue;
+ }
+ switch ( $child[self::NAME] ) {
+ case 'title':
+ $bits['title'] = new self( $children, $i );
+ break;
+ case 'part':
+ $parts[] = new self( $children, $i );
+ break;
+ case '@lineStart':
+ $bits['lineStart'] = '1';
+ break;
+ }
+ }
+ if ( !isset( $bits['title'] ) ) {
+ throw new MWException( 'Invalid node passed to ' . __METHOD__ );
+ }
+ $bits['parts'] = new PPNode_Hash_Array( $parts );
+ return $bits;
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * 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 );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * Expansion frame with template arguments
+ * @ingroup Parser
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class PPTemplateFrame_Hash extends PPFrame_Hash {
+
+ public $numberedArgs, $namedArgs, $parent;
+ public $numberedExpansionCache, $namedExpansionCache;
+
+ /**
+ * @param Preprocessor $preprocessor
+ * @param bool|PPFrame $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->__toString() ) . '"';
+ }
+ $s .= '}';
+ return $s;
+ }
+
+ /**
+ * @throws MWException
+ * @param string|int $key
+ * @param string|PPNode $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 );
+ }
+
+ /**
+ * @return array
+ */
+ public function getArguments() {
+ $arguments = [];
+ foreach ( array_merge(
+ array_keys( $this->numberedArgs ),
+ array_keys( $this->namedArgs ) ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
+ }
+ return $arguments;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNumberedArguments() {
+ $arguments = [];
+ foreach ( array_keys( $this->numberedArgs ) as $key ) {
+ $arguments[$key] = $this->getArgument( $key );
+ }
+ return $arguments;
+ }
+
+ /**
+ * @return array
+ */
+ 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 );
+ }
+}
*/
abstract public function preprocessToObj( $text, $flags = 0 );
}
-
-/**
- * @ingroup Parser
- */
-interface PPFrame {
- const NO_ARGS = 1;
- const NO_TEMPLATES = 2;
- const STRIP_COMMENTS = 4;
- const NO_IGNORE = 8;
- const RECOVER_COMMENTS = 16;
- const NO_TAGS = 32;
-
- const RECOVER_ORIG = self::NO_ARGS | self::NO_TEMPLATES | self::NO_IGNORE |
- self::RECOVER_COMMENTS | self::NO_TAGS;
-
- /** This constant exists when $indexOffset is supported in newChild() */
- const SUPPORTS_INDEX_OFFSET = 1;
-
- /**
- * Create a child frame
- *
- * @param array|bool $args
- * @param bool|Title $title
- * @param int $indexOffset A number subtracted from the index attributes of the arguments
- *
- * @return PPFrame
- */
- public function newChild( $args = false, $title = false, $indexOffset = 0 );
-
- /**
- * Expand a document tree node, caching the result on its parent with the given key
- * @param string|int $key
- * @param string|PPNode $root
- * @param int $flags
- * @return string
- */
- public function cachedExpand( $key, $root, $flags = 0 );
-
- /**
- * Expand a document tree node
- * @param string|PPNode $root
- * @param int $flags
- * @return string
- */
- public function expand( $root, $flags = 0 );
-
- /**
- * Implode with flags for expand()
- * @param string $sep
- * @param int $flags
- * @param string|PPNode $args,...
- * @return string
- */
- public function implodeWithFlags( $sep, $flags /*, ... */ );
-
- /**
- * Implode with no flags specified
- * @param string $sep
- * @param string|PPNode $args,...
- * @return string
- */
- public function implode( $sep /*, ... */ );
-
- /**
- * Makes an object that, when expand()ed, will be the same as one obtained
- * with implode()
- * @param string $sep
- * @param string|PPNode $args,...
- * @return PPNode
- */
- public function virtualImplode( $sep /*, ... */ );
-
- /**
- * Virtual implode with brackets
- * @param string $start
- * @param string $sep
- * @param string $end
- * @param string|PPNode $args,...
- * @return PPNode
- */
- public function virtualBracketedImplode( $start, $sep, $end /*, ... */ );
-
- /**
- * Returns true if there are no arguments in this frame
- *
- * @return bool
- */
- public function isEmpty();
-
- /**
- * Returns all arguments of this frame
- * @return array
- */
- public function getArguments();
-
- /**
- * Returns all numbered arguments of this frame
- * @return array
- */
- public function getNumberedArguments();
-
- /**
- * Returns all named arguments of this frame
- * @return array
- */
- public function getNamedArguments();
-
- /**
- * Get an argument to this frame by name
- * @param int|string $name
- * @return string|bool
- */
- public function getArgument( $name );
-
- /**
- * 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 true if the frame is a template frame
- * @return bool
- */
- public function isTemplate();
-
- /**
- * Set the "volatile" flag.
- *
- * Note that this is somewhat of a "hack" in order to make extensions
- * with side effects (such as Cite) work with the PHP parser. New
- * extensions should be written in a way that they do not need this
- * function, because other parsers (such as Parsoid) are not guaranteed
- * to respect it, and it may be removed in the future.
- *
- * @param bool $flag
- */
- public function setVolatile( $flag = true );
-
- /**
- * Get the "volatile" flag.
- *
- * Callers should avoid caching the result of an expansion if it has the
- * volatile flag set.
- *
- * @see self::setVolatile()
- * @return bool
- */
- public function isVolatile();
-
- /**
- * Get the TTL of the frame's output.
- *
- * This is the maximum amount of time, in seconds, that this frame's
- * output should be cached for. A value of null indicates that no
- * maximum has been specified.
- *
- * Note that this TTL only applies to caching frames as parts of pages.
- * It is not relevant to caching the entire rendered output of a page.
- *
- * @return int|null
- */
- public function getTTL();
-
- /**
- * Set the TTL of the output of this frame and all of its ancestors.
- * Has no effect if the new TTL is greater than the one already set.
- * Note that it is the caller's responsibility to change the cache
- * expiry of the page as a whole, if such behavior is desired.
- *
- * @see self::getTTL()
- * @param int $ttl
- */
- public function setTTL( $ttl );
-
- /**
- * Get a title of frame
- *
- * @return Title
- */
- public function getTitle();
-}
-
-/**
- * There are three types of nodes:
- * * Tree nodes, which have a name and contain other nodes as children
- * * Array nodes, which also contain other nodes but aren't considered part of a tree
- * * Leaf nodes, which contain the actual data
- *
- * This interface provides access to the tree structure and to the contents of array nodes,
- * but it does not provide access to the internal structure of leaf nodes. Access to leaf
- * data is provided via two means:
- * * PPFrame::expand(), which provides expanded text
- * * The PPNode::split*() functions, which provide metadata about certain types of tree node
- * @ingroup Parser
- */
-interface PPNode {
- /**
- * Get an array-type node containing the children of this node.
- * Returns false if this is not a tree node.
- * @return PPNode
- */
- public function getChildren();
-
- /**
- * Get the first child of a tree node. False if there isn't one.
- *
- * @return PPNode
- */
- public function getFirstChild();
-
- /**
- * Get the next sibling of any node. False if there isn't one
- * @return PPNode
- */
- public function getNextSibling();
-
- /**
- * Get all children of this tree node which have a given name.
- * Returns an array-type node, or false if this is not a tree node.
- * @param string $type
- * @return bool|PPNode
- */
- public function getChildrenOfType( $type );
-
- /**
- * Returns the length of the array, or false if this is not an array-type node
- */
- public function getLength();
-
- /**
- * Returns an item of an array-type node
- * @param int $i
- * @return bool|PPNode
- */
- public function item( $i );
-
- /**
- * Get the name of this node. The following names are defined here:
- *
- * h A heading node.
- * template A double-brace node.
- * tplarg A triple-brace node.
- * title The first argument to a template or tplarg node.
- * part Subsequent arguments to a template or tplarg node.
- * #nodelist An array-type node
- *
- * The subclass may define various other names for tree and leaf nodes.
- * @return string
- */
- public function getName();
-
- /**
- * Split a "<part>" node into an associative array containing:
- * name PPNode name
- * index String index
- * value PPNode value
- * @return array
- */
- public function splitArg();
-
- /**
- * 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.
- * @return array
- */
- public function splitExt();
-
- /**
- * Split an "<h>" node
- * @return array
- */
- public function splitHeading();
-}
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()
- ];
- }
-}
}
}
}
-
-/**
- * Stack class to help Preprocessor::preprocessToObj()
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPDStack_Hash extends PPDStack {
-
- public function __construct() {
- $this->elementClass = PPDStackElement_Hash::class;
- parent::__construct();
- $this->rootAccum = [];
- }
-}
-
-/**
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPDStackElement_Hash extends PPDStackElement {
-
- public function __construct( $data = [] ) {
- $this->partClass = PPDPart_Hash::class;
- parent::__construct( $data );
- }
-
- /**
- * Get the accumulator that would result if the close is not found.
- *
- * @param int|bool $openingCount
- * @return array
- */
- public function breakSyntax( $openingCount = false ) {
- if ( $this->open == "\n" ) {
- $accum = array_merge( [ $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 )
- );
- $accum = [ $this->savedPrefix . $s ];
- $lastIndex = 0;
- $first = true;
- foreach ( $this->parts as $part ) {
- if ( $first ) {
- $first = false;
- } elseif ( is_string( $accum[$lastIndex] ) ) {
- $accum[$lastIndex] .= '|';
- } else {
- $accum[++$lastIndex] = '|';
- }
- foreach ( $part->out as $node ) {
- if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
- $accum[$lastIndex] .= $node;
- } else {
- $accum[++$lastIndex] = $node;
- }
- }
- }
- }
- return $accum;
- }
-}
-
-/**
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPDPart_Hash extends PPDPart {
-
- public function __construct( $out = '' ) {
- if ( $out !== '' ) {
- $accum = [ $out ];
- } else {
- $accum = [];
- }
- parent::__construct( $accum );
- }
-}
-
-/**
- * An expansion frame, used as a context to expand the result of preprocessToObj()
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPFrame_Hash implements PPFrame {
-
- /**
- * @var Parser
- */
- public $parser;
-
- /**
- * @var Preprocessor
- */
- public $preprocessor;
-
- /**
- * @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 array|bool|PPNode_Hash_Array $args
- * @param Title|bool $title
- * @param int $indexOffset
- * @throws MWException
- * @return PPTemplateFrame_Hash
- */
- public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
- $namedArgs = [];
- $numberedArgs = [];
- if ( $title === false ) {
- $title = $this->title;
- }
- if ( $args !== false ) {
- if ( $args instanceof PPNode_Hash_Array ) {
- $args = $args->value;
- } elseif ( !is_array( $args ) ) {
- throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
- }
- foreach ( $args as $arg ) {
- $bits = $arg->splitArg();
- if ( $bits['index'] !== '' ) {
- // Numbered parameter
- $index = $bits['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] = $bits['value'];
- unset( $namedArgs[$index] );
- } else {
- // Named parameter
- $name = trim( $this->expand( $bits['name'], 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] = $bits['value'];
- unset( $numberedArgs[$name] );
- }
- }
- }
- return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
- }
-
- /**
- * @throws MWException
- * @param string|int $key
- * @param string|PPNode $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 $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 = [ '', '' ];
- $iteratorStack = [ false, $root ];
- $indexStack = [ 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;
- $contextName = false;
- $contextChildren = false;
-
- if ( $contextNode === false ) {
- // nothing to do
- } elseif ( is_string( $contextNode ) ) {
- $out .= $contextNode;
- } elseif ( $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 ) {
- $contextName = $contextNode->name;
- $contextChildren = $contextNode->getRawChildren();
- } elseif ( is_array( $contextNode ) ) {
- // Node descriptor array
- if ( count( $contextNode ) !== 2 ) {
- throw new MWException( __METHOD__ .
- ': found an array where a node descriptor should be' );
- }
- list( $contextName, $contextChildren ) = $contextNode;
- } else {
- throw new MWException( __METHOD__ . ': Invalid parameter type' );
- }
-
- // Handle node descriptor array or tree object
- if ( $contextName === false ) {
- // Not a node, already handled above
- } elseif ( $contextName[0] === '@' ) {
- // Attribute: no output
- } elseif ( $contextName === 'template' ) {
- # Double-brace expansion
- $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
- 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 ( $contextName === 'tplarg' ) {
- # Triple-brace expansion
- $bits = PPNode_Hash_Tree::splitRawTemplate( $contextChildren );
- 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 ( $contextName === '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( $contextChildren[0] );
- } else {
- # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
- $out .= $contextChildren[0];
- }
- } elseif ( $contextName === '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 .= $contextChildren[0];
- } else {
- // $out .= '';
- }
- } elseif ( $contextName === 'ext' ) {
- # Extension tag
- $bits = PPNode_Hash_Tree::splitRawExt( $contextChildren ) +
- [ 'attr' => null, 'inner' => null, 'close' => null ];
- if ( $flags & PPFrame::NO_TAGS ) {
- $s = '<' . $bits['name']->getFirstChild()->value;
- if ( $bits['attr'] ) {
- $s .= $bits['attr']->getFirstChild()->value;
- }
- if ( $bits['inner'] ) {
- $s .= '>' . $bits['inner']->getFirstChild()->value;
- if ( $bits['close'] ) {
- $s .= $bits['close']->getFirstChild()->value;
- }
- } else {
- $s .= '/>';
- }
- $out .= $s;
- } else {
- $out .= $this->parser->extensionSubstitution( $bits, $this );
- }
- } elseif ( $contextName === 'h' ) {
- # Heading
- if ( $this->parser->ot['html'] ) {
- # Expand immediately and insert heading index marker
- $s = $this->expand( $contextChildren, $flags );
- $bits = PPNode_Hash_Tree::splitRawHeading( $contextChildren );
- $titleText = $this->title->getPrefixedDBkey();
- $this->parser->mHeadings[] = [ $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 = $contextChildren;
- }
- } else {
- # Generic recursive expansion
- $newIterator = $contextChildren;
- }
-
- 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];
- }
-
- /**
- * @param string $sep
- * @param int $flags
- * @param string|PPNode ...$args
- * @return string
- */
- public function implodeWithFlags( $sep, $flags, ...$args ) {
- $first = true;
- $s = '';
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_Hash_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $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 ...$args
- * @return string
- */
- public function implode( $sep, ...$args ) {
- $first = true;
- $s = '';
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_Hash_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $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 ...$args
- * @return PPNode_Hash_Array
- */
- public function virtualImplode( $sep, ...$args ) {
- $out = [];
- $first = true;
-
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_Hash_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $root = [ $root ];
- }
- foreach ( $root as $node ) {
- if ( $first ) {
- $first = false;
- } else {
- $out[] = $sep;
- }
- $out[] = $node;
- }
- }
- return new PPNode_Hash_Array( $out );
- }
-
- /**
- * Virtual implode with brackets
- *
- * @param string $start
- * @param string $sep
- * @param string $end
- * @param string|PPNode ...$args
- * @return PPNode_Hash_Array
- */
- public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
- $out = [ $start ];
- $first = true;
-
- foreach ( $args as $root ) {
- if ( $root instanceof PPNode_Hash_Array ) {
- $root = $root->value;
- }
- if ( !is_array( $root ) ) {
- $root = [ $root ];
- }
- foreach ( $root as $node ) {
- if ( $first ) {
- $first = false;
- } else {
- $out[] = $sep;
- }
- $out[] = $node;
- }
- }
- $out[] = $end;
- return new PPNode_Hash_Array( $out );
- }
-
- public function __toString() {
- return 'frame{}';
- }
-
- /**
- * @param bool $level
- * @return array|bool|string
- */
- 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_Hash extends PPFrame_Hash {
-
- public $numberedArgs, $namedArgs, $parent;
- public $numberedExpansionCache, $namedExpansionCache;
-
- /**
- * @param Preprocessor $preprocessor
- * @param bool|PPFrame $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->__toString() ) . '"';
- }
- $s .= '}';
- return $s;
- }
-
- /**
- * @throws MWException
- * @param string|int $key
- * @param string|PPNode $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 );
- }
-
- /**
- * @return array
- */
- public function getArguments() {
- $arguments = [];
- foreach ( array_merge(
- array_keys( $this->numberedArgs ),
- array_keys( $this->namedArgs ) ) as $key ) {
- $arguments[$key] = $this->getArgument( $key );
- }
- return $arguments;
- }
-
- /**
- * @return array
- */
- public function getNumberedArguments() {
- $arguments = [];
- foreach ( array_keys( $this->numberedArgs ) as $key ) {
- $arguments[$key] = $this->getArgument( $key );
- }
- return $arguments;
- }
-
- /**
- * @return array
- */
- 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_Hash extends PPFrame_Hash {
-
- 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_Hash_Tree implements PPNode {
-
- public $name;
-
- /**
- * The store array for children of this node. It is "raw" in the sense that
- * nodes are two-element arrays ("descriptors") rather than PPNode_Hash_*
- * objects.
- */
- private $rawChildren;
-
- /**
- * The store array for the siblings of this node, including this node itself.
- */
- private $store;
-
- /**
- * The index into $this->store which contains the descriptor of this node.
- */
- private $index;
-
- /**
- * The offset of the name within descriptors, used in some places for
- * readability.
- */
- const NAME = 0;
-
- /**
- * The offset of the child list within descriptors, used in some places for
- * readability.
- */
- const CHILDREN = 1;
-
- /**
- * Construct an object using the data from $store[$index]. The rest of the
- * store array can be accessed via getNextSibling().
- *
- * @param array $store
- * @param int $index
- */
- public function __construct( array $store, $index ) {
- $this->store = $store;
- $this->index = $index;
- list( $this->name, $this->rawChildren ) = $this->store[$index];
- }
-
- /**
- * Construct an appropriate PPNode_Hash_* object with a class that depends
- * on what is at the relevant store index.
- *
- * @param array $store
- * @param int $index
- * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
- * @throws MWException
- */
- public static function factory( array $store, $index ) {
- if ( !isset( $store[$index] ) ) {
- return false;
- }
-
- $descriptor = $store[$index];
- if ( is_string( $descriptor ) ) {
- $class = PPNode_Hash_Text::class;
- } elseif ( is_array( $descriptor ) ) {
- if ( $descriptor[self::NAME][0] === '@' ) {
- $class = PPNode_Hash_Attr::class;
- } else {
- $class = self::class;
- }
- } else {
- throw new MWException( __METHOD__ . ': invalid node descriptor' );
- }
- return new $class( $store, $index );
- }
-
- /**
- * Convert a node to XML, for debugging
- * @return string
- */
- public function __toString() {
- $inner = '';
- $attribs = '';
- for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
- if ( $node instanceof PPNode_Hash_Attr ) {
- $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
- } else {
- $inner .= $node->__toString();
- }
- }
- if ( $inner === '' ) {
- return "<{$this->name}$attribs/>";
- } else {
- return "<{$this->name}$attribs>$inner</{$this->name}>";
- }
- }
-
- /**
- * @return PPNode_Hash_Array
- */
- public function getChildren() {
- $children = [];
- foreach ( $this->rawChildren as $i => $child ) {
- $children[] = self::factory( $this->rawChildren, $i );
- }
- return new PPNode_Hash_Array( $children );
- }
-
- /**
- * Get the first child, or false if there is none. Note that this will
- * return a temporary proxy object: different instances will be returned
- * if this is called more than once on the same node.
- *
- * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|bool
- */
- public function getFirstChild() {
- if ( !isset( $this->rawChildren[0] ) ) {
- return false;
- } else {
- return self::factory( $this->rawChildren, 0 );
- }
- }
-
- /**
- * Get the next sibling, or false if there is none. Note that this will
- * return a temporary proxy object: different instances will be returned
- * if this is called more than once on the same node.
- *
- * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|bool
- */
- public function getNextSibling() {
- return self::factory( $this->store, $this->index + 1 );
- }
-
- /**
- * Get an array of the children with a given node name
- *
- * @param string $name
- * @return PPNode_Hash_Array
- */
- public function getChildrenOfType( $name ) {
- $children = [];
- foreach ( $this->rawChildren as $i => $child ) {
- if ( is_array( $child ) && $child[self::NAME] === $name ) {
- $children[] = self::factory( $this->rawChildren, $i );
- }
- }
- return new PPNode_Hash_Array( $children );
- }
-
- /**
- * Get the raw child array. For internal use.
- * @return array
- */
- public function getRawChildren() {
- return $this->rawChildren;
- }
-
- /**
- * @return bool
- */
- public function getLength() {
- return false;
- }
-
- /**
- * @param int $i
- * @return bool
- */
- public function item( $i ) {
- return false;
- }
-
- /**
- * @return string
- */
- public function getName() {
- return $this->name;
- }
-
- /**
- * 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() {
- return self::splitRawArg( $this->rawChildren );
- }
-
- /**
- * Like splitArg() but for a raw child array. For internal use only.
- * @param array $children
- * @return array
- */
- public static function splitRawArg( array $children ) {
- $bits = [];
- foreach ( $children as $i => $child ) {
- if ( !is_array( $child ) ) {
- continue;
- }
- if ( $child[self::NAME] === 'name' ) {
- $bits['name'] = new self( $children, $i );
- if ( isset( $child[self::CHILDREN][0][self::NAME] )
- && $child[self::CHILDREN][0][self::NAME] === '@index'
- ) {
- $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
- }
- } elseif ( $child[self::NAME] === 'value' ) {
- $bits['value'] = new self( $children, $i );
- }
- }
-
- if ( !isset( $bits['name'] ) ) {
- throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
- }
- if ( !isset( $bits['index'] ) ) {
- $bits['index'] = '';
- }
- return $bits;
- }
-
- /**
- * 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() {
- return self::splitRawExt( $this->rawChildren );
- }
-
- /**
- * Like splitExt() but for a raw child array. For internal use only.
- * @param array $children
- * @return array
- */
- public static function splitRawExt( array $children ) {
- $bits = [];
- foreach ( $children as $i => $child ) {
- if ( !is_array( $child ) ) {
- continue;
- }
- switch ( $child[self::NAME] ) {
- case 'name':
- $bits['name'] = new self( $children, $i );
- break;
- case 'attr':
- $bits['attr'] = new self( $children, $i );
- break;
- case 'inner':
- $bits['inner'] = new self( $children, $i );
- break;
- case 'close':
- $bits['close'] = new self( $children, $i );
- break;
- }
- }
- if ( !isset( $bits['name'] ) ) {
- throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
- }
- return $bits;
- }
-
- /**
- * Split an "<h>" node
- *
- * @throws MWException
- * @return array
- */
- public function splitHeading() {
- if ( $this->name !== 'h' ) {
- throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
- }
- return self::splitRawHeading( $this->rawChildren );
- }
-
- /**
- * Like splitHeading() but for a raw child array. For internal use only.
- * @param array $children
- * @return array
- */
- public static function splitRawHeading( array $children ) {
- $bits = [];
- foreach ( $children as $i => $child ) {
- if ( !is_array( $child ) ) {
- continue;
- }
- if ( $child[self::NAME] === '@i' ) {
- $bits['i'] = $child[self::CHILDREN][0];
- } elseif ( $child[self::NAME] === '@level' ) {
- $bits['level'] = $child[self::CHILDREN][0];
- }
- }
- if ( !isset( $bits['i'] ) ) {
- throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
- }
- return $bits;
- }
-
- /**
- * Split a "<template>" or "<tplarg>" node
- *
- * @throws MWException
- * @return array
- */
- public function splitTemplate() {
- return self::splitRawTemplate( $this->rawChildren );
- }
-
- /**
- * Like splitTemplate() but for a raw child array. For internal use only.
- * @param array $children
- * @return array
- */
- public static function splitRawTemplate( array $children ) {
- $parts = [];
- $bits = [ 'lineStart' => '' ];
- foreach ( $children as $i => $child ) {
- if ( !is_array( $child ) ) {
- continue;
- }
- switch ( $child[self::NAME] ) {
- case 'title':
- $bits['title'] = new self( $children, $i );
- break;
- case 'part':
- $parts[] = new self( $children, $i );
- break;
- case '@lineStart':
- $bits['lineStart'] = '1';
- break;
- }
- }
- if ( !isset( $bits['title'] ) ) {
- throw new MWException( 'Invalid node passed to ' . __METHOD__ );
- }
- $bits['parts'] = new PPNode_Hash_Array( $parts );
- return $bits;
- }
-}
-
-/**
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPNode_Hash_Text implements PPNode {
-
- public $value;
- private $store, $index;
-
- /**
- * Construct an object using the data from $store[$index]. The rest of the
- * store array can be accessed via getNextSibling().
- *
- * @param array $store
- * @param int $index
- */
- public function __construct( array $store, $index ) {
- $this->value = $store[$index];
- if ( !is_scalar( $this->value ) ) {
- throw new MWException( __CLASS__ . ' given object instead of string' );
- }
- $this->store = $store;
- $this->index = $index;
- }
-
- public function __toString() {
- return htmlspecialchars( $this->value );
- }
-
- public function getNextSibling() {
- return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
- }
-
- public function getChildren() {
- return false;
- }
-
- public function getFirstChild() {
- return false;
- }
-
- public function getChildrenOfType( $name ) {
- return false;
- }
-
- public function getLength() {
- return false;
- }
-
- public function item( $i ) {
- return false;
- }
-
- public function getName() {
- return '#text';
- }
-
- public function splitArg() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-
- public function splitExt() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-
- public function splitHeading() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-}
-
-/**
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPNode_Hash_Array implements PPNode {
-
- public $value;
-
- public function __construct( $value ) {
- $this->value = $value;
- }
-
- public function __toString() {
- return var_export( $this, true );
- }
-
- public function getLength() {
- return count( $this->value );
- }
-
- public function item( $i ) {
- return $this->value[$i];
- }
-
- public function getName() {
- return '#nodelist';
- }
-
- public function getNextSibling() {
- return false;
- }
-
- public function getChildren() {
- return false;
- }
-
- public function getFirstChild() {
- return false;
- }
-
- public function getChildrenOfType( $name ) {
- return false;
- }
-
- public function splitArg() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-
- public function splitExt() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-
- public function splitHeading() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-}
-
-/**
- * @ingroup Parser
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class PPNode_Hash_Attr implements PPNode {
-
- public $name, $value;
- private $store, $index;
-
- /**
- * Construct an object using the data from $store[$index]. The rest of the
- * store array can be accessed via getNextSibling().
- *
- * @param array $store
- * @param int $index
- */
- public function __construct( array $store, $index ) {
- $descriptor = $store[$index];
- if ( $descriptor[PPNode_Hash_Tree::NAME][0] !== '@' ) {
- throw new MWException( __METHOD__ . ': invalid name in attribute descriptor' );
- }
- $this->name = substr( $descriptor[PPNode_Hash_Tree::NAME], 1 );
- $this->value = $descriptor[PPNode_Hash_Tree::CHILDREN][0];
- $this->store = $store;
- $this->index = $index;
- }
-
- public function __toString() {
- return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
- }
-
- public function getName() {
- return $this->name;
- }
-
- public function getNextSibling() {
- return PPNode_Hash_Tree::factory( $this->store, $this->index + 1 );
- }
-
- public function getChildren() {
- return false;
- }
-
- public function getFirstChild() {
- return false;
- }
-
- public function getChildrenOfType( $name ) {
- return false;
- }
-
- public function getLength() {
- return false;
- }
-
- public function item( $i ) {
- return false;
- }
-
- public function splitArg() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-
- public function splitExt() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-
- public function splitHeading() {
- throw new MWException( __METHOD__ . ': not supported' );
- }
-}