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