Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
40.35% covered (danger)
40.35%
46 / 114
26.32% covered (danger)
26.32%
5 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
PPNode_Hash_Tree
40.71% covered (danger)
40.71%
46 / 113
26.32% covered (danger)
26.32%
5 / 19
759.21
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 factory
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
5.02
 __toString
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getChildren
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getFirstChild
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getNextSibling
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getChildrenOfType
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 getRawChildren
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLength
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 item
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 splitArg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 splitRawArg
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
90
 splitExt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 splitRawExt
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
8.06
 splitHeading
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 splitRawHeading
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 splitTemplate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 splitRawTemplate
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
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.
7 *
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.
12 *
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
17 *
18 * @file
19 * @ingroup Parser
20 */
21
22namespace MediaWiki\Parser;
23
24use BadMethodCallException;
25use InvalidArgumentException;
26use Stringable;
27
28/**
29 * @ingroup Parser
30 */
31// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
32class PPNode_Hash_Tree implements Stringable, PPNode {
33
34    /** @var string */
35    public $name;
36
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
42     */
43    private $rawChildren;
44
45    /**
46     * The store array for the siblings of this node, including this node itself.
47     * @var array
48     */
49    private $store;
50
51    /**
52     * The index into $this->store which contains the descriptor of this node.
53     * @var int
54     */
55    private $index;
56
57    /**
58     * The offset of the name within descriptors, used in some places for
59     * readability.
60     */
61    public const NAME = 0;
62
63    /**
64     * The offset of the child list within descriptors, used in some places for
65     * readability.
66     */
67    public const CHILDREN = 1;
68
69    /**
70     * Construct an object using the data from $store[$index]. The rest of the
71     * store array can be accessed via getNextSibling().
72     *
73     * @param array $store
74     * @param int $index
75     */
76    public function __construct( array $store, $index ) {
77        $this->store = $store;
78        $this->index = $index;
79        [ $this->name, $this->rawChildren ] = $this->store[$index];
80    }
81
82    /**
83     * Construct an appropriate PPNode_Hash_* object with a class that depends
84     * on what is at the relevant store index.
85     *
86     * @param array $store
87     * @param int $index
88     * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
89     */
90    public static function factory( array $store, $index ) {
91        if ( !isset( $store[$index] ) ) {
92            return false;
93        }
94
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;
103            }
104        } else {
105            throw new InvalidArgumentException( __METHOD__ . ': invalid node descriptor' );
106        }
107        return new $class( $store, $index );
108    }
109
110    /**
111     * Convert a node to XML, for debugging
112     * @return string
113     */
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();
123            }
124        }
125        if ( $inner === '' ) {
126            return "<{$this->name}$attribs/>";
127        } else {
128            return "<{$this->name}$attribs>$inner</{$this->name}>";
129        }
130    }
131
132    /**
133     * @return PPNode_Hash_Array
134     */
135    public function getChildren() {
136        $children = [];
137        foreach ( $this->rawChildren as $i => $child ) {
138            $children[] = self::factory( $this->rawChildren, $i );
139        }
140        return new PPNode_Hash_Array( $children );
141    }
142
143    /**
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.
147     *
148     * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
149     */
150    public function getFirstChild() {
151        if ( !isset( $this->rawChildren[0] ) ) {
152            return false;
153        } else {
154            return self::factory( $this->rawChildren, 0 );
155        }
156    }
157
158    /**
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.
162     *
163     * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
164     */
165    public function getNextSibling() {
166        return self::factory( $this->store, $this->index + 1 );
167    }
168
169    /**
170     * Get an array of the children with a given node name
171     *
172     * @param string $name
173     * @return PPNode_Hash_Array
174     */
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 );
180            }
181        }
182        return new PPNode_Hash_Array( $children );
183    }
184
185    /**
186     * Get the raw child array. For internal use.
187     * @return array
188     */
189    public function getRawChildren() {
190        return $this->rawChildren;
191    }
192
193    /**
194     * @return bool
195     */
196    public function getLength() {
197        return false;
198    }
199
200    /**
201     * @param int $i
202     * @return bool
203     */
204    public function item( $i ) {
205        return false;
206    }
207
208    /**
209     * @return string
210     */
211    public function getName() {
212        return $this->name;
213    }
214
215    /**
216     * Split a "<part>" node into an associative array containing:
217     *  - name          PPNode name
218     *  - index         String index
219     *  - value         PPNode value
220     *
221     * @return array
222     */
223    public function splitArg() {
224        return self::splitRawArg( $this->rawChildren );
225    }
226
227    /**
228     * Like splitArg() but for a raw child array. For internal use only.
229     * @param array $children
230     * @return array
231     */
232    public static function splitRawArg( array $children ) {
233        $bits = [];
234        foreach ( $children as $i => $child ) {
235            if ( !is_array( $child ) ) {
236                continue;
237            }
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'
242                ) {
243                    $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
244                }
245            } elseif ( $child[self::NAME] === 'value' ) {
246                $bits['value'] = new self( $children, $i );
247            }
248        }
249
250        if ( !isset( $bits['name'] ) ) {
251            throw new InvalidArgumentException( 'Invalid brace node passed to ' . __METHOD__ );
252        }
253        if ( !isset( $bits['index'] ) ) {
254            $bits['index'] = '';
255        }
256        return $bits;
257    }
258
259    /**
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.
262     *
263     * @return array
264     */
265    public function splitExt() {
266        return self::splitRawExt( $this->rawChildren );
267    }
268
269    /**
270     * Like splitExt() but for a raw child array. For internal use only.
271     * @param array $children
272     * @return array
273     */
274    public static function splitRawExt( array $children ) {
275        $bits = [];
276        foreach ( $children as $i => $child ) {
277            if ( !is_array( $child ) ) {
278                continue;
279            }
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;
293            }
294        }
295        if ( !isset( $bits['name'] ) ) {
296            throw new InvalidArgumentException( 'Invalid ext node passed to ' . __METHOD__ );
297        }
298        return $bits;
299    }
300
301    /**
302     * Split an "<h>" node
303     *
304     * @return array
305     */
306    public function splitHeading() {
307        if ( $this->name !== 'h' ) {
308            throw new BadMethodCallException( 'Invalid h node passed to ' . __METHOD__ );
309        }
310        return self::splitRawHeading( $this->rawChildren );
311    }
312
313    /**
314     * Like splitHeading() but for a raw child array. For internal use only.
315     * @param array $children
316     * @return array
317     */
318    public static function splitRawHeading( array $children ) {
319        $bits = [];
320        foreach ( $children as $child ) {
321            if ( !is_array( $child ) ) {
322                continue;
323            }
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];
328            }
329        }
330        if ( !isset( $bits['i'] ) ) {
331            throw new InvalidArgumentException( 'Invalid h node passed to ' . __METHOD__ );
332        }
333        return $bits;
334    }
335
336    /**
337     * Split a "<template>" or "<tplarg>" node
338     *
339     * @return array
340     */
341    public function splitTemplate() {
342        return self::splitRawTemplate( $this->rawChildren );
343    }
344
345    /**
346     * Like splitTemplate() but for a raw child array. For internal use only.
347     * @param array $children
348     * @return array
349     * @suppress SecurityCheck-XSS
350     */
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;
357            }
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;
368            }
369        }
370        if ( !isset( $bits['title'] ) ) {
371            throw new InvalidArgumentException( 'Invalid node passed to ' . __METHOD__ );
372        }
373        $bits['parts'] = new PPNode_Hash_Array( $parts );
374        return $bits;
375    }
376}
377
378/** @deprecated class alias since 1.43 */
379class_alias( PPNode_Hash_Tree::class, 'PPNode_Hash_Tree' );