Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
40.71% covered (danger)
40.71%
46 / 113
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
22/**
23 * @ingroup Parser
24 */
25// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
26class PPNode_Hash_Tree implements Stringable, PPNode {
27
28    /** @var string */
29    public $name;
30
31    /**
32     * The store array for children of this node. It is "raw" in the sense that
33     * nodes are two-element arrays ("descriptors") rather than PPNode_Hash_*
34     * objects.
35     * @var array
36     */
37    private $rawChildren;
38
39    /**
40     * The store array for the siblings of this node, including this node itself.
41     * @var array
42     */
43    private $store;
44
45    /**
46     * The index into $this->store which contains the descriptor of this node.
47     * @var int
48     */
49    private $index;
50
51    /**
52     * The offset of the name within descriptors, used in some places for
53     * readability.
54     */
55    public const NAME = 0;
56
57    /**
58     * The offset of the child list within descriptors, used in some places for
59     * readability.
60     */
61    public const CHILDREN = 1;
62
63    /**
64     * Construct an object using the data from $store[$index]. The rest of the
65     * store array can be accessed via getNextSibling().
66     *
67     * @param array $store
68     * @param int $index
69     */
70    public function __construct( array $store, $index ) {
71        $this->store = $store;
72        $this->index = $index;
73        [ $this->name, $this->rawChildren ] = $this->store[$index];
74    }
75
76    /**
77     * Construct an appropriate PPNode_Hash_* object with a class that depends
78     * on what is at the relevant store index.
79     *
80     * @param array $store
81     * @param int $index
82     * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
83     */
84    public static function factory( array $store, $index ) {
85        if ( !isset( $store[$index] ) ) {
86            return false;
87        }
88
89        $descriptor = $store[$index];
90        if ( is_string( $descriptor ) ) {
91            $class = PPNode_Hash_Text::class;
92        } elseif ( is_array( $descriptor ) ) {
93            if ( $descriptor[self::NAME][0] === '@' ) {
94                $class = PPNode_Hash_Attr::class;
95            } else {
96                $class = self::class;
97            }
98        } else {
99            throw new InvalidArgumentException( __METHOD__ . ': invalid node descriptor' );
100        }
101        return new $class( $store, $index );
102    }
103
104    /**
105     * Convert a node to XML, for debugging
106     * @return string
107     */
108    public function __toString() {
109        $inner = '';
110        $attribs = '';
111        for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
112            if ( $node instanceof PPNode_Hash_Attr ) {
113                $attribs .= ' ' . $node->name .
114                    '="' . htmlspecialchars( $node->value, ENT_COMPAT ) . '"';
115            } else {
116                $inner .= $node->__toString();
117            }
118        }
119        if ( $inner === '' ) {
120            return "<{$this->name}$attribs/>";
121        } else {
122            return "<{$this->name}$attribs>$inner</{$this->name}>";
123        }
124    }
125
126    /**
127     * @return PPNode_Hash_Array
128     */
129    public function getChildren() {
130        $children = [];
131        foreach ( $this->rawChildren as $i => $child ) {
132            $children[] = self::factory( $this->rawChildren, $i );
133        }
134        return new PPNode_Hash_Array( $children );
135    }
136
137    /**
138     * Get the first child, or false if there is none. Note that this will
139     * return a temporary proxy object: different instances will be returned
140     * if this is called more than once on the same node.
141     *
142     * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
143     */
144    public function getFirstChild() {
145        if ( !isset( $this->rawChildren[0] ) ) {
146            return false;
147        } else {
148            return self::factory( $this->rawChildren, 0 );
149        }
150    }
151
152    /**
153     * Get the next sibling, or false if there is none. Note that this will
154     * return a temporary proxy object: different instances will be returned
155     * if this is called more than once on the same node.
156     *
157     * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
158     */
159    public function getNextSibling() {
160        return self::factory( $this->store, $this->index + 1 );
161    }
162
163    /**
164     * Get an array of the children with a given node name
165     *
166     * @param string $name
167     * @return PPNode_Hash_Array
168     */
169    public function getChildrenOfType( $name ) {
170        $children = [];
171        foreach ( $this->rawChildren as $i => $child ) {
172            if ( is_array( $child ) && $child[self::NAME] === $name ) {
173                $children[] = self::factory( $this->rawChildren, $i );
174            }
175        }
176        return new PPNode_Hash_Array( $children );
177    }
178
179    /**
180     * Get the raw child array. For internal use.
181     * @return array
182     */
183    public function getRawChildren() {
184        return $this->rawChildren;
185    }
186
187    /**
188     * @return bool
189     */
190    public function getLength() {
191        return false;
192    }
193
194    /**
195     * @param int $i
196     * @return bool
197     */
198    public function item( $i ) {
199        return false;
200    }
201
202    /**
203     * @return string
204     */
205    public function getName() {
206        return $this->name;
207    }
208
209    /**
210     * Split a "<part>" node into an associative array containing:
211     *  - name          PPNode name
212     *  - index         String index
213     *  - value         PPNode value
214     *
215     * @return array
216     */
217    public function splitArg() {
218        return self::splitRawArg( $this->rawChildren );
219    }
220
221    /**
222     * Like splitArg() but for a raw child array. For internal use only.
223     * @param array $children
224     * @return array
225     */
226    public static function splitRawArg( array $children ) {
227        $bits = [];
228        foreach ( $children as $i => $child ) {
229            if ( !is_array( $child ) ) {
230                continue;
231            }
232            if ( $child[self::NAME] === 'name' ) {
233                $bits['name'] = new self( $children, $i );
234                if ( isset( $child[self::CHILDREN][0][self::NAME] )
235                    && $child[self::CHILDREN][0][self::NAME] === '@index'
236                ) {
237                    $bits['index'] = $child[self::CHILDREN][0][self::CHILDREN][0];
238                }
239            } elseif ( $child[self::NAME] === 'value' ) {
240                $bits['value'] = new self( $children, $i );
241            }
242        }
243
244        if ( !isset( $bits['name'] ) ) {
245            throw new InvalidArgumentException( 'Invalid brace node passed to ' . __METHOD__ );
246        }
247        if ( !isset( $bits['index'] ) ) {
248            $bits['index'] = '';
249        }
250        return $bits;
251    }
252
253    /**
254     * Split an "<ext>" node into an associative array containing name, attr, inner and close
255     * All values in the resulting array are PPNodes. Inner and close are optional.
256     *
257     * @return array
258     */
259    public function splitExt() {
260        return self::splitRawExt( $this->rawChildren );
261    }
262
263    /**
264     * Like splitExt() but for a raw child array. For internal use only.
265     * @param array $children
266     * @return array
267     */
268    public static function splitRawExt( array $children ) {
269        $bits = [];
270        foreach ( $children as $i => $child ) {
271            if ( !is_array( $child ) ) {
272                continue;
273            }
274            switch ( $child[self::NAME] ) {
275                case 'name':
276                    $bits['name'] = new self( $children, $i );
277                    break;
278                case 'attr':
279                    $bits['attr'] = new self( $children, $i );
280                    break;
281                case 'inner':
282                    $bits['inner'] = new self( $children, $i );
283                    break;
284                case 'close':
285                    $bits['close'] = new self( $children, $i );
286                    break;
287            }
288        }
289        if ( !isset( $bits['name'] ) ) {
290            throw new InvalidArgumentException( 'Invalid ext node passed to ' . __METHOD__ );
291        }
292        return $bits;
293    }
294
295    /**
296     * Split an "<h>" node
297     *
298     * @return array
299     */
300    public function splitHeading() {
301        if ( $this->name !== 'h' ) {
302            throw new BadMethodCallException( 'Invalid h node passed to ' . __METHOD__ );
303        }
304        return self::splitRawHeading( $this->rawChildren );
305    }
306
307    /**
308     * Like splitHeading() but for a raw child array. For internal use only.
309     * @param array $children
310     * @return array
311     */
312    public static function splitRawHeading( array $children ) {
313        $bits = [];
314        foreach ( $children as $child ) {
315            if ( !is_array( $child ) ) {
316                continue;
317            }
318            if ( $child[self::NAME] === '@i' ) {
319                $bits['i'] = $child[self::CHILDREN][0];
320            } elseif ( $child[self::NAME] === '@level' ) {
321                $bits['level'] = $child[self::CHILDREN][0];
322            }
323        }
324        if ( !isset( $bits['i'] ) ) {
325            throw new InvalidArgumentException( 'Invalid h node passed to ' . __METHOD__ );
326        }
327        return $bits;
328    }
329
330    /**
331     * Split a "<template>" or "<tplarg>" node
332     *
333     * @return array
334     */
335    public function splitTemplate() {
336        return self::splitRawTemplate( $this->rawChildren );
337    }
338
339    /**
340     * Like splitTemplate() but for a raw child array. For internal use only.
341     * @param array $children
342     * @return array
343     * @suppress SecurityCheck-XSS
344     */
345    public static function splitRawTemplate( array $children ) {
346        $parts = [];
347        $bits = [ 'lineStart' => '' ];
348        foreach ( $children as $i => $child ) {
349            if ( !is_array( $child ) ) {
350                continue;
351            }
352            switch ( $child[self::NAME] ) {
353                case 'title':
354                    $bits['title'] = new self( $children, $i );
355                    break;
356                case 'part':
357                    $parts[] = new self( $children, $i );
358                    break;
359                case '@lineStart':
360                    $bits['lineStart'] = '1';
361                    break;
362            }
363        }
364        if ( !isset( $bits['title'] ) ) {
365            throw new InvalidArgumentException( 'Invalid node passed to ' . __METHOD__ );
366        }
367        $bits['parts'] = new PPNode_Hash_Array( $parts );
368        return $bits;
369    }
370}