379 lines
9.4 KiB
PHP
379 lines
9.4 KiB
PHP
<?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
|
|
*/
|
|
|
|
namespace MediaWiki\Parser;
|
|
|
|
use BadMethodCallException;
|
|
use InvalidArgumentException;
|
|
use Stringable;
|
|
|
|
/**
|
|
* @ingroup Parser
|
|
*/
|
|
// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
|
|
class PPNode_Hash_Tree implements Stringable, PPNode {
|
|
|
|
/** @var string */
|
|
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.
|
|
* @var array
|
|
*/
|
|
private $rawChildren;
|
|
|
|
/**
|
|
* The store array for the siblings of this node, including this node itself.
|
|
* @var array
|
|
*/
|
|
private $store;
|
|
|
|
/**
|
|
* The index into $this->store which contains the descriptor of this node.
|
|
* @var int
|
|
*/
|
|
private $index;
|
|
|
|
/**
|
|
* The offset of the name within descriptors, used in some places for
|
|
* readability.
|
|
*/
|
|
public const NAME = 0;
|
|
|
|
/**
|
|
* The offset of the child list within descriptors, used in some places for
|
|
* readability.
|
|
*/
|
|
public 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;
|
|
[ $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
|
|
*/
|
|
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 InvalidArgumentException( __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, ENT_COMPAT ) . '"';
|
|
} 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|false
|
|
*/
|
|
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|false
|
|
*/
|
|
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
|
|
*
|
|
* @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 InvalidArgumentException( '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.
|
|
*
|
|
* @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 InvalidArgumentException( 'Invalid ext node passed to ' . __METHOD__ );
|
|
}
|
|
return $bits;
|
|
}
|
|
|
|
/**
|
|
* Split an "<h>" node
|
|
*
|
|
* @return array
|
|
*/
|
|
public function splitHeading() {
|
|
if ( $this->name !== 'h' ) {
|
|
throw new BadMethodCallException( '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 $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 InvalidArgumentException( 'Invalid h node passed to ' . __METHOD__ );
|
|
}
|
|
return $bits;
|
|
}
|
|
|
|
/**
|
|
* Split a "<template>" or "<tplarg>" node
|
|
*
|
|
* @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
|
|
* @suppress SecurityCheck-XSS
|
|
*/
|
|
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 InvalidArgumentException( 'Invalid node passed to ' . __METHOD__ );
|
|
}
|
|
$bits['parts'] = new PPNode_Hash_Array( $parts );
|
|
return $bits;
|
|
}
|
|
}
|
|
|
|
/** @deprecated class alias since 1.43 */
|
|
class_alias( PPNode_Hash_Tree::class, 'PPNode_Hash_Tree' );
|