Split parser related files to have one class in one file

Change-Id: I36b26609ccb3f135a22961b32a46cdc06603b3e4
This commit is contained in:
Zoranzoki21 2019-04-21 01:34:48 +02:00 committed by Reedy
parent f415b6e162
commit 4226fada45
24 changed files with 3359 additions and 2982 deletions

View file

@ -189,9 +189,6 @@
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>

View file

@ -1050,28 +1050,28 @@ $wgAutoloadLocalClasses = [
'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',

View file

@ -0,0 +1,70 @@
<?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;
}
}

View file

@ -0,0 +1,70 @@
<?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;
}
}

View file

@ -0,0 +1,39 @@
<?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;
}
}

View file

@ -0,0 +1,36 @@
<?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 );
}
}

View file

@ -0,0 +1,113 @@
<?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();
}
}
}

View file

@ -0,0 +1,129 @@
<?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;
}
}

View file

@ -0,0 +1,73 @@
<?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;
}
}

View file

@ -0,0 +1,34 @@
<?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 = [];
}
}

204
includes/parser/PPFrame.php Normal file
View file

@ -0,0 +1,204 @@
<?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();
}

View file

@ -0,0 +1,631 @@
<?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;
}
}

View file

@ -0,0 +1,613 @@
<?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;
}
}

112
includes/parser/PPNode.php Normal file
View file

@ -0,0 +1,112 @@
<?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();
}

View file

@ -0,0 +1,188 @@
<?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()
];
}
}

View file

@ -0,0 +1,77 @@
<?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' );
}
}

View file

@ -0,0 +1,92 @@
<?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' );
}
}

View file

@ -0,0 +1,90 @@
<?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' );
}
}

View file

@ -0,0 +1,369 @@
<?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;
}
}

View file

@ -0,0 +1,198 @@
<?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 );
}
}

View file

@ -0,0 +1,202 @@
<?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 );
}
}

View file

@ -164,279 +164,3 @@ abstract class Preprocessor {
*/
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();
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff