3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
22 namespace MediaWiki\Parser
;
24 use BadMethodCallException
;
25 use InvalidArgumentException
;
31 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
32 class PPNode_Hash_Tree
implements Stringable
, PPNode
{
38 * The store array for children of this node. It is "raw" in the sense that
39 * nodes are two-element arrays ("descriptors") rather than PPNode_Hash_*
46 * The store array for the siblings of this node, including this node itself.
52 * The index into $this->store which contains the descriptor of this node.
58 * The offset of the name within descriptors, used in some places for
61 public const NAME
= 0;
64 * The offset of the child list within descriptors, used in some places for
67 public const CHILDREN
= 1;
70 * Construct an object using the data from $store[$index]. The rest of the
71 * store array can be accessed via getNextSibling().
76 public function __construct( array $store, $index ) {
77 $this->store
= $store;
78 $this->index
= $index;
79 [ $this->name
, $this->rawChildren
] = $this->store
[$index];
83 * Construct an appropriate PPNode_Hash_* object with a class that depends
84 * on what is at the relevant store index.
88 * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
90 public static function factory( array $store, $index ) {
91 if ( !isset( $store[$index] ) ) {
95 $descriptor = $store[$index];
96 if ( is_string( $descriptor ) ) {
97 $class = PPNode_Hash_Text
::class;
98 } elseif ( is_array( $descriptor ) ) {
99 if ( $descriptor[self
::NAME
][0] === '@' ) {
100 $class = PPNode_Hash_Attr
::class;
102 $class = self
::class;
105 throw new InvalidArgumentException( __METHOD__
. ': invalid node descriptor' );
107 return new $class( $store, $index );
111 * Convert a node to XML, for debugging
114 public function __toString() {
117 for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
118 if ( $node instanceof PPNode_Hash_Attr
) {
119 $attribs .= ' ' . $node->name
.
120 '="' . htmlspecialchars( $node->value
, ENT_COMPAT
) . '"';
122 $inner .= $node->__toString();
125 if ( $inner === '' ) {
126 return "<{$this->name}$attribs/>";
128 return "<{$this->name}$attribs>$inner</{$this->name}>";
133 * @return PPNode_Hash_Array
135 public function getChildren() {
137 foreach ( $this->rawChildren
as $i => $child ) {
138 $children[] = self
::factory( $this->rawChildren
, $i );
140 return new PPNode_Hash_Array( $children );
144 * Get the first child, or false if there is none. Note that this will
145 * return a temporary proxy object: different instances will be returned
146 * if this is called more than once on the same node.
148 * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
150 public function getFirstChild() {
151 if ( !isset( $this->rawChildren
[0] ) ) {
154 return self
::factory( $this->rawChildren
, 0 );
159 * Get the next sibling, or false if there is none. Note that this will
160 * return a temporary proxy object: different instances will be returned
161 * if this is called more than once on the same node.
163 * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
165 public function getNextSibling() {
166 return self
::factory( $this->store
, $this->index +
1 );
170 * Get an array of the children with a given node name
172 * @param string $name
173 * @return PPNode_Hash_Array
175 public function getChildrenOfType( $name ) {
177 foreach ( $this->rawChildren
as $i => $child ) {
178 if ( is_array( $child ) && $child[self
::NAME
] === $name ) {
179 $children[] = self
::factory( $this->rawChildren
, $i );
182 return new PPNode_Hash_Array( $children );
186 * Get the raw child array. For internal use.
189 public function getRawChildren() {
190 return $this->rawChildren
;
196 public function getLength() {
204 public function item( $i ) {
211 public function getName() {
216 * Split a "<part>" node into an associative array containing:
218 * - index String index
219 * - value PPNode value
223 public function splitArg() {
224 return self
::splitRawArg( $this->rawChildren
);
228 * Like splitArg() but for a raw child array. For internal use only.
229 * @param array $children
232 public static function splitRawArg( array $children ) {
234 foreach ( $children as $i => $child ) {
235 if ( !is_array( $child ) ) {
238 if ( $child[self
::NAME
] === 'name' ) {
239 $bits['name'] = new self( $children, $i );
240 if ( isset( $child[self
::CHILDREN
][0][self
::NAME
] )
241 && $child[self
::CHILDREN
][0][self
::NAME
] === '@index'
243 $bits['index'] = $child[self
::CHILDREN
][0][self
::CHILDREN
][0];
245 } elseif ( $child[self
::NAME
] === 'value' ) {
246 $bits['value'] = new self( $children, $i );
250 if ( !isset( $bits['name'] ) ) {
251 throw new InvalidArgumentException( 'Invalid brace node passed to ' . __METHOD__
);
253 if ( !isset( $bits['index'] ) ) {
260 * Split an "<ext>" node into an associative array containing name, attr, inner and close
261 * All values in the resulting array are PPNodes. Inner and close are optional.
265 public function splitExt() {
266 return self
::splitRawExt( $this->rawChildren
);
270 * Like splitExt() but for a raw child array. For internal use only.
271 * @param array $children
274 public static function splitRawExt( array $children ) {
276 foreach ( $children as $i => $child ) {
277 if ( !is_array( $child ) ) {
280 switch ( $child[self
::NAME
] ) {
282 $bits['name'] = new self( $children, $i );
285 $bits['attr'] = new self( $children, $i );
288 $bits['inner'] = new self( $children, $i );
291 $bits['close'] = new self( $children, $i );
295 if ( !isset( $bits['name'] ) ) {
296 throw new InvalidArgumentException( 'Invalid ext node passed to ' . __METHOD__
);
302 * Split an "<h>" node
306 public function splitHeading() {
307 if ( $this->name
!== 'h' ) {
308 throw new BadMethodCallException( 'Invalid h node passed to ' . __METHOD__
);
310 return self
::splitRawHeading( $this->rawChildren
);
314 * Like splitHeading() but for a raw child array. For internal use only.
315 * @param array $children
318 public static function splitRawHeading( array $children ) {
320 foreach ( $children as $child ) {
321 if ( !is_array( $child ) ) {
324 if ( $child[self
::NAME
] === '@i' ) {
325 $bits['i'] = $child[self
::CHILDREN
][0];
326 } elseif ( $child[self
::NAME
] === '@level' ) {
327 $bits['level'] = $child[self
::CHILDREN
][0];
330 if ( !isset( $bits['i'] ) ) {
331 throw new InvalidArgumentException( 'Invalid h node passed to ' . __METHOD__
);
337 * Split a "<template>" or "<tplarg>" node
341 public function splitTemplate() {
342 return self
::splitRawTemplate( $this->rawChildren
);
346 * Like splitTemplate() but for a raw child array. For internal use only.
347 * @param array $children
349 * @suppress SecurityCheck-XSS
351 public static function splitRawTemplate( array $children ) {
353 $bits = [ 'lineStart' => '' ];
354 foreach ( $children as $i => $child ) {
355 if ( !is_array( $child ) ) {
358 switch ( $child[self
::NAME
] ) {
360 $bits['title'] = new self( $children, $i );
363 $parts[] = new self( $children, $i );
366 $bits['lineStart'] = '1';
370 if ( !isset( $bits['title'] ) ) {
371 throw new InvalidArgumentException( 'Invalid node passed to ' . __METHOD__
);
373 $bits['parts'] = new PPNode_Hash_Array( $parts );
378 /** @deprecated class alias since 1.43 */
379 class_alias( PPNode_Hash_Tree
::class, 'PPNode_Hash_Tree' );