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
25 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
26 class PPNode_Hash_Tree
implements PPNode
{
31 * The store array for children of this node. It is "raw" in the sense that
32 * nodes are two-element arrays ("descriptors") rather than PPNode_Hash_*
38 * The store array for the siblings of this node, including this node itself.
43 * The index into $this->store which contains the descriptor of this node.
48 * The offset of the name within descriptors, used in some places for
54 * The offset of the child list within descriptors, used in some places for
60 * Construct an object using the data from $store[$index]. The rest of the
61 * store array can be accessed via getNextSibling().
66 public function __construct( array $store, $index ) {
67 $this->store
= $store;
68 $this->index
= $index;
69 list( $this->name
, $this->rawChildren
) = $this->store
[$index];
73 * Construct an appropriate PPNode_Hash_* object with a class that depends
74 * on what is at the relevant store index.
78 * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|false
81 public static function factory( array $store, $index ) {
82 if ( !isset( $store[$index] ) ) {
86 $descriptor = $store[$index];
87 if ( is_string( $descriptor ) ) {
88 $class = PPNode_Hash_Text
::class;
89 } elseif ( is_array( $descriptor ) ) {
90 if ( $descriptor[self
::NAME
][0] === '@' ) {
91 $class = PPNode_Hash_Attr
::class;
96 throw new MWException( __METHOD__
. ': invalid node descriptor' );
98 return new $class( $store, $index );
102 * Convert a node to XML, for debugging
105 public function __toString() {
108 for ( $node = $this->getFirstChild(); $node; $node = $node->getNextSibling() ) {
109 if ( $node instanceof PPNode_Hash_Attr
) {
110 $attribs .= ' ' . $node->name
. '="' . htmlspecialchars( $node->value
) . '"';
112 $inner .= $node->__toString();
115 if ( $inner === '' ) {
116 return "<{$this->name}$attribs/>";
118 return "<{$this->name}$attribs>$inner</{$this->name}>";
123 * @return PPNode_Hash_Array
125 public function getChildren() {
127 foreach ( $this->rawChildren
as $i => $child ) {
128 $children[] = self
::factory( $this->rawChildren
, $i );
130 return new PPNode_Hash_Array( $children );
134 * Get the first child, or false if there is none. Note that this will
135 * return a temporary proxy object: different instances will be returned
136 * if this is called more than once on the same node.
138 * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|bool
140 public function getFirstChild() {
141 if ( !isset( $this->rawChildren
[0] ) ) {
144 return self
::factory( $this->rawChildren
, 0 );
149 * Get the next sibling, or false if there is none. Note that this will
150 * return a temporary proxy object: different instances will be returned
151 * if this is called more than once on the same node.
153 * @return PPNode_Hash_Tree|PPNode_Hash_Attr|PPNode_Hash_Text|bool
155 public function getNextSibling() {
156 return self
::factory( $this->store
, $this->index +
1 );
160 * Get an array of the children with a given node name
162 * @param string $name
163 * @return PPNode_Hash_Array
165 public function getChildrenOfType( $name ) {
167 foreach ( $this->rawChildren
as $i => $child ) {
168 if ( is_array( $child ) && $child[self
::NAME
] === $name ) {
169 $children[] = self
::factory( $this->rawChildren
, $i );
172 return new PPNode_Hash_Array( $children );
176 * Get the raw child array. For internal use.
179 public function getRawChildren() {
180 return $this->rawChildren
;
186 public function getLength() {
194 public function item( $i ) {
201 public function getName() {
206 * Split a "<part>" node into an associative array containing:
208 * - index String index
209 * - value PPNode value
211 * @throws MWException
214 public function splitArg() {
215 return self
::splitRawArg( $this->rawChildren
);
219 * Like splitArg() but for a raw child array. For internal use only.
220 * @param array $children
223 public static function splitRawArg( array $children ) {
225 foreach ( $children as $i => $child ) {
226 if ( !is_array( $child ) ) {
229 if ( $child[self
::NAME
] === 'name' ) {
230 $bits['name'] = new self( $children, $i );
231 if ( isset( $child[self
::CHILDREN
][0][self
::NAME
] )
232 && $child[self
::CHILDREN
][0][self
::NAME
] === '@index'
234 $bits['index'] = $child[self
::CHILDREN
][0][self
::CHILDREN
][0];
236 } elseif ( $child[self
::NAME
] === 'value' ) {
237 $bits['value'] = new self( $children, $i );
241 if ( !isset( $bits['name'] ) ) {
242 throw new MWException( 'Invalid brace node passed to ' . __METHOD__
);
244 if ( !isset( $bits['index'] ) ) {
251 * Split an "<ext>" node into an associative array containing name, attr, inner and close
252 * All values in the resulting array are PPNodes. Inner and close are optional.
254 * @throws MWException
257 public function splitExt() {
258 return self
::splitRawExt( $this->rawChildren
);
262 * Like splitExt() but for a raw child array. For internal use only.
263 * @param array $children
266 public static function splitRawExt( array $children ) {
268 foreach ( $children as $i => $child ) {
269 if ( !is_array( $child ) ) {
272 switch ( $child[self
::NAME
] ) {
274 $bits['name'] = new self( $children, $i );
277 $bits['attr'] = new self( $children, $i );
280 $bits['inner'] = new self( $children, $i );
283 $bits['close'] = new self( $children, $i );
287 if ( !isset( $bits['name'] ) ) {
288 throw new MWException( 'Invalid ext node passed to ' . __METHOD__
);
294 * Split an "<h>" node
296 * @throws MWException
299 public function splitHeading() {
300 if ( $this->name
!== 'h' ) {
301 throw new MWException( 'Invalid h node passed to ' . __METHOD__
);
303 return self
::splitRawHeading( $this->rawChildren
);
307 * Like splitHeading() but for a raw child array. For internal use only.
308 * @param array $children
311 public static function splitRawHeading( array $children ) {
313 foreach ( $children as $i => $child ) {
314 if ( !is_array( $child ) ) {
317 if ( $child[self
::NAME
] === '@i' ) {
318 $bits['i'] = $child[self
::CHILDREN
][0];
319 } elseif ( $child[self
::NAME
] === '@level' ) {
320 $bits['level'] = $child[self
::CHILDREN
][0];
323 if ( !isset( $bits['i'] ) ) {
324 throw new MWException( 'Invalid h node passed to ' . __METHOD__
);
330 * Split a "<template>" or "<tplarg>" node
332 * @throws MWException
335 public function splitTemplate() {
336 return self
::splitRawTemplate( $this->rawChildren
);
340 * Like splitTemplate() but for a raw child array. For internal use only.
341 * @param array $children
344 public static function splitRawTemplate( array $children ) {
346 $bits = [ 'lineStart' => '' ];
347 foreach ( $children as $i => $child ) {
348 if ( !is_array( $child ) ) {
351 switch ( $child[self
::NAME
] ) {
353 $bits['title'] = new self( $children, $i );
356 $parts[] = new self( $children, $i );
359 $bits['lineStart'] = '1';
363 if ( !isset( $bits['title'] ) ) {
364 throw new MWException( 'Invalid node passed to ' . __METHOD__
);
366 $bits['parts'] = new PPNode_Hash_Array( $parts );