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