Merge "mediawiki.api: Remove console warning for legacy token type"
[mediawiki.git] / includes / parser / PPNode_Hash_Tree.php
blobb7bec777525644a801f39ae38f6945b30ed92cfc
1 <?php
2 /**
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
18 * @file
19 * @ingroup Parser
22 namespace MediaWiki\Parser;
24 use BadMethodCallException;
25 use InvalidArgumentException;
26 use Stringable;
28 /**
29 * @ingroup Parser
31 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
32 class PPNode_Hash_Tree implements Stringable, PPNode {
34 /** @var string */
35 public $name;
37 /**
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_*
40 * objects.
41 * @var array
43 private $rawChildren;
45 /**
46 * The store array for the siblings of this node, including this node itself.
47 * @var array
49 private $store;
51 /**
52 * The index into $this->store which contains the descriptor of this node.
53 * @var int
55 private $index;
57 /**
58 * The offset of the name within descriptors, used in some places for
59 * readability.
61 public const NAME = 0;
63 /**
64 * The offset of the child list within descriptors, used in some places for
65 * readability.
67 public const CHILDREN = 1;
69 /**
70 * Construct an object using the data from $store[$index]. The rest of the
71 * store array can be accessed via getNextSibling().
73 * @param array $store
74 * @param int $index
76 public function __construct( array $store, $index ) {
77 $this->store = $store;
78 $this->index = $index;
79 [ $this->name, $this->rawChildren ] = $this->store[$index];
82 /**
83 * Construct an appropriate PPNode_Hash_* object with a class that depends
84 * on what is at the relevant store index.
86 * @param array $store
87 * @param int $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] ) ) {
92 return false;
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;
101 } else {
102 $class = self::class;
104 } else {
105 throw new InvalidArgumentException( __METHOD__ . ': invalid node descriptor' );
107 return new $class( $store, $index );
111 * Convert a node to XML, for debugging
112 * @return string
114 public function __toString() {
115 $inner = '';
116 $attribs = '';
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 ) . '"';
121 } else {
122 $inner .= $node->__toString();
125 if ( $inner === '' ) {
126 return "<{$this->name}$attribs/>";
127 } else {
128 return "<{$this->name}$attribs>$inner</{$this->name}>";
133 * @return PPNode_Hash_Array
135 public function getChildren() {
136 $children = [];
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] ) ) {
152 return false;
153 } else {
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 ) {
176 $children = [];
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.
187 * @return array
189 public function getRawChildren() {
190 return $this->rawChildren;
194 * @return bool
196 public function getLength() {
197 return false;
201 * @param int $i
202 * @return bool
204 public function item( $i ) {
205 return false;
209 * @return string
211 public function getName() {
212 return $this->name;
216 * Split a "<part>" node into an associative array containing:
217 * - name PPNode name
218 * - index String index
219 * - value PPNode value
221 * @return array
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
230 * @return array
232 public static function splitRawArg( array $children ) {
233 $bits = [];
234 foreach ( $children as $i => $child ) {
235 if ( !is_array( $child ) ) {
236 continue;
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'] ) ) {
254 $bits['index'] = '';
256 return $bits;
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.
263 * @return array
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
272 * @return array
274 public static function splitRawExt( array $children ) {
275 $bits = [];
276 foreach ( $children as $i => $child ) {
277 if ( !is_array( $child ) ) {
278 continue;
280 switch ( $child[self::NAME] ) {
281 case 'name':
282 $bits['name'] = new self( $children, $i );
283 break;
284 case 'attr':
285 $bits['attr'] = new self( $children, $i );
286 break;
287 case 'inner':
288 $bits['inner'] = new self( $children, $i );
289 break;
290 case 'close':
291 $bits['close'] = new self( $children, $i );
292 break;
295 if ( !isset( $bits['name'] ) ) {
296 throw new InvalidArgumentException( 'Invalid ext node passed to ' . __METHOD__ );
298 return $bits;
302 * Split an "<h>" node
304 * @return array
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
316 * @return array
318 public static function splitRawHeading( array $children ) {
319 $bits = [];
320 foreach ( $children as $child ) {
321 if ( !is_array( $child ) ) {
322 continue;
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__ );
333 return $bits;
337 * Split a "<template>" or "<tplarg>" node
339 * @return array
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
348 * @return array
349 * @suppress SecurityCheck-XSS
351 public static function splitRawTemplate( array $children ) {
352 $parts = [];
353 $bits = [ 'lineStart' => '' ];
354 foreach ( $children as $i => $child ) {
355 if ( !is_array( $child ) ) {
356 continue;
358 switch ( $child[self::NAME] ) {
359 case 'title':
360 $bits['title'] = new self( $children, $i );
361 break;
362 case 'part':
363 $parts[] = new self( $children, $i );
364 break;
365 case '@lineStart':
366 $bits['lineStart'] = '1';
367 break;
370 if ( !isset( $bits['title'] ) ) {
371 throw new InvalidArgumentException( 'Invalid node passed to ' . __METHOD__ );
373 $bits['parts'] = new PPNode_Hash_Array( $parts );
374 return $bits;
378 /** @deprecated class alias since 1.43 */
379 class_alias( PPNode_Hash_Tree::class, 'PPNode_Hash_Tree' );