Index: branches/parser-work/phase3/includes/parser/Preprocessor_Hash.php |
— | — | @@ -1,1619 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * Differences from DOM schema: |
6 | | - * * attribute nodes are children |
7 | | - * * <h> nodes that aren't at the top are replaced with <possible-h> |
8 | | - * @ingroup Parser |
9 | | - */ |
10 | | -class Preprocessor_Hash implements Preprocessor { |
11 | | - var $parser; |
12 | | - |
13 | | - const CACHE_VERSION = 1; |
14 | | - |
15 | | - function __construct( $parser ) { |
16 | | - $this->parser = $parser; |
17 | | - } |
18 | | - |
19 | | - function newFrame() { |
20 | | - return new PPFrame_Hash( $this ); |
21 | | - } |
22 | | - |
23 | | - function newCustomFrame( $args ) { |
24 | | - return new PPCustomFrame_Hash( $this, $args ); |
25 | | - } |
26 | | - |
27 | | - /** |
28 | | - * Preprocess some wikitext and return the document tree. |
29 | | - * This is the ghost of Parser::replace_variables(). |
30 | | - * |
31 | | - * @param string $text The text to parse |
32 | | - * @param integer flags Bitwise combination of: |
33 | | - * Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being |
34 | | - * included. Default is to assume a direct page view. |
35 | | - * |
36 | | - * The generated DOM tree must depend only on the input text and the flags. |
37 | | - * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. |
38 | | - * |
39 | | - * Any flag added to the $flags parameter here, or any other parameter liable to cause a |
40 | | - * change in the DOM tree for a given text, must be passed through the section identifier |
41 | | - * in the section edit link and thus back to extractSections(). |
42 | | - * |
43 | | - * The output of this function is currently only cached in process memory, but a persistent |
44 | | - * cache may be implemented at a later date which takes further advantage of these strict |
45 | | - * dependency requirements. |
46 | | - * |
47 | | - * @private |
48 | | - */ |
49 | | - function preprocessToObj( $text, $flags = 0 ) { |
50 | | - wfProfileIn( __METHOD__ ); |
51 | | - |
52 | | - |
53 | | - // Check cache. |
54 | | - global $wgMemc, $wgPreprocessorCacheThreshold; |
55 | | - |
56 | | - $cacheable = strlen( $text ) > $wgPreprocessorCacheThreshold; |
57 | | - if ( $cacheable ) { |
58 | | - wfProfileIn( __METHOD__.'-cacheable' ); |
59 | | - |
60 | | - $cacheKey = wfMemcKey( 'preprocess-hash', md5($text), $flags ); |
61 | | - $cacheValue = $wgMemc->get( $cacheKey ); |
62 | | - if ( $cacheValue ) { |
63 | | - $version = substr( $cacheValue, 0, 8 ); |
64 | | - if ( intval( $version ) == self::CACHE_VERSION ) { |
65 | | - $hash = unserialize( substr( $cacheValue, 8 ) ); |
66 | | - // From the cache |
67 | | - wfDebugLog( "Preprocessor", |
68 | | - "Loaded preprocessor hash from memcached (key $cacheKey)" ); |
69 | | - wfProfileOut( __METHOD__.'-cacheable' ); |
70 | | - wfProfileOut( __METHOD__ ); |
71 | | - return $hash; |
72 | | - } |
73 | | - } |
74 | | - wfProfileIn( __METHOD__.'-cache-miss' ); |
75 | | - } |
76 | | - |
77 | | - $rules = array( |
78 | | - '{' => array( |
79 | | - 'end' => '}', |
80 | | - 'names' => array( |
81 | | - 2 => 'template', |
82 | | - 3 => 'tplarg', |
83 | | - ), |
84 | | - 'min' => 2, |
85 | | - 'max' => 3, |
86 | | - ), |
87 | | - '[' => array( |
88 | | - 'end' => ']', |
89 | | - 'names' => array( 2 => null ), |
90 | | - 'min' => 2, |
91 | | - 'max' => 2, |
92 | | - ) |
93 | | - ); |
94 | | - |
95 | | - $forInclusion = $flags & Parser::PTD_FOR_INCLUSION; |
96 | | - |
97 | | - $xmlishElements = $this->parser->getStripList(); |
98 | | - $enableOnlyinclude = false; |
99 | | - if ( $forInclusion ) { |
100 | | - $ignoredTags = array( 'includeonly', '/includeonly' ); |
101 | | - $ignoredElements = array( 'noinclude' ); |
102 | | - $xmlishElements[] = 'noinclude'; |
103 | | - if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) { |
104 | | - $enableOnlyinclude = true; |
105 | | - } |
106 | | - } else { |
107 | | - $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ); |
108 | | - $ignoredElements = array( 'includeonly' ); |
109 | | - $xmlishElements[] = 'includeonly'; |
110 | | - } |
111 | | - $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) ); |
112 | | - |
113 | | - // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset |
114 | | - $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA"; |
115 | | - |
116 | | - $stack = new PPDStack_Hash; |
117 | | - |
118 | | - $searchBase = "[{<\n"; |
119 | | - $revText = strrev( $text ); // For fast reverse searches |
120 | | - |
121 | | - $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start |
122 | | - $accum =& $stack->getAccum(); # Current accumulator |
123 | | - $findEquals = false; # True to find equals signs in arguments |
124 | | - $findPipe = false; # True to take notice of pipe characters |
125 | | - $headingIndex = 1; |
126 | | - $inHeading = false; # True if $i is inside a possible heading |
127 | | - $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i |
128 | | - $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude> |
129 | | - $fakeLineStart = true; # Do a line-start run without outputting an LF character |
130 | | - |
131 | | - while ( true ) { |
132 | | - //$this->memCheck(); |
133 | | - |
134 | | - if ( $findOnlyinclude ) { |
135 | | - // Ignore all input up to the next <onlyinclude> |
136 | | - $startPos = strpos( $text, '<onlyinclude>', $i ); |
137 | | - if ( $startPos === false ) { |
138 | | - // Ignored section runs to the end |
139 | | - $accum->addNodeWithText( 'ignore', substr( $text, $i ) ); |
140 | | - break; |
141 | | - } |
142 | | - $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end |
143 | | - $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i ) ); |
144 | | - $i = $tagEndPos; |
145 | | - $findOnlyinclude = false; |
146 | | - } |
147 | | - |
148 | | - if ( $fakeLineStart ) { |
149 | | - $found = 'line-start'; |
150 | | - $curChar = ''; |
151 | | - } else { |
152 | | - # Find next opening brace, closing brace or pipe |
153 | | - $search = $searchBase; |
154 | | - if ( $stack->top === false ) { |
155 | | - $currentClosing = ''; |
156 | | - } else { |
157 | | - $currentClosing = $stack->top->close; |
158 | | - $search .= $currentClosing; |
159 | | - } |
160 | | - if ( $findPipe ) { |
161 | | - $search .= '|'; |
162 | | - } |
163 | | - if ( $findEquals ) { |
164 | | - // First equals will be for the template |
165 | | - $search .= '='; |
166 | | - } |
167 | | - $rule = null; |
168 | | - # Output literal section, advance input counter |
169 | | - $literalLength = strcspn( $text, $search, $i ); |
170 | | - if ( $literalLength > 0 ) { |
171 | | - $accum->addLiteral( substr( $text, $i, $literalLength ) ); |
172 | | - $i += $literalLength; |
173 | | - } |
174 | | - if ( $i >= strlen( $text ) ) { |
175 | | - if ( $currentClosing == "\n" ) { |
176 | | - // Do a past-the-end run to finish off the heading |
177 | | - $curChar = ''; |
178 | | - $found = 'line-end'; |
179 | | - } else { |
180 | | - # All done |
181 | | - break; |
182 | | - } |
183 | | - } else { |
184 | | - $curChar = $text[$i]; |
185 | | - if ( $curChar == '|' ) { |
186 | | - $found = 'pipe'; |
187 | | - } elseif ( $curChar == '=' ) { |
188 | | - $found = 'equals'; |
189 | | - } elseif ( $curChar == '<' ) { |
190 | | - $found = 'angle'; |
191 | | - } elseif ( $curChar == "\n" ) { |
192 | | - if ( $inHeading ) { |
193 | | - $found = 'line-end'; |
194 | | - } else { |
195 | | - $found = 'line-start'; |
196 | | - } |
197 | | - } elseif ( $curChar == $currentClosing ) { |
198 | | - $found = 'close'; |
199 | | - } elseif ( isset( $rules[$curChar] ) ) { |
200 | | - $found = 'open'; |
201 | | - $rule = $rules[$curChar]; |
202 | | - } else { |
203 | | - # Some versions of PHP have a strcspn which stops on null characters |
204 | | - # Ignore and continue |
205 | | - ++$i; |
206 | | - continue; |
207 | | - } |
208 | | - } |
209 | | - } |
210 | | - |
211 | | - if ( $found == 'angle' ) { |
212 | | - $matches = false; |
213 | | - // Handle </onlyinclude> |
214 | | - if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) { |
215 | | - $findOnlyinclude = true; |
216 | | - continue; |
217 | | - } |
218 | | - |
219 | | - // Determine element name |
220 | | - if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) { |
221 | | - // Element name missing or not listed |
222 | | - $accum->addLiteral( '<' ); |
223 | | - ++$i; |
224 | | - continue; |
225 | | - } |
226 | | - // Handle comments |
227 | | - if ( isset( $matches[2] ) && $matches[2] == '!--' ) { |
228 | | - // To avoid leaving blank lines, when a comment is both preceded |
229 | | - // and followed by a newline (ignoring spaces), trim leading and |
230 | | - // trailing spaces and one of the newlines. |
231 | | - |
232 | | - // Find the end |
233 | | - $endPos = strpos( $text, '-->', $i + 4 ); |
234 | | - if ( $endPos === false ) { |
235 | | - // Unclosed comment in input, runs to end |
236 | | - $inner = substr( $text, $i ); |
237 | | - $accum->addNodeWithText( 'comment', $inner ); |
238 | | - $i = strlen( $text ); |
239 | | - } else { |
240 | | - // Search backwards for leading whitespace |
241 | | - $wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0; |
242 | | - // Search forwards for trailing whitespace |
243 | | - // $wsEnd will be the position of the last space |
244 | | - $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 ); |
245 | | - // Eat the line if possible |
246 | | - // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at |
247 | | - // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but |
248 | | - // it's a possible beneficial b/c break. |
249 | | - if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" |
250 | | - && substr( $text, $wsEnd + 1, 1 ) == "\n" ) |
251 | | - { |
252 | | - $startPos = $wsStart; |
253 | | - $endPos = $wsEnd + 1; |
254 | | - // Remove leading whitespace from the end of the accumulator |
255 | | - // Sanity check first though |
256 | | - $wsLength = $i - $wsStart; |
257 | | - if ( $wsLength > 0 |
258 | | - && $accum->lastNode instanceof PPNode_Hash_Text |
259 | | - && substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) ) |
260 | | - { |
261 | | - $accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength ); |
262 | | - } |
263 | | - // Do a line-start run next time to look for headings after the comment |
264 | | - $fakeLineStart = true; |
265 | | - } else { |
266 | | - // No line to eat, just take the comment itself |
267 | | - $startPos = $i; |
268 | | - $endPos += 2; |
269 | | - } |
270 | | - |
271 | | - if ( $stack->top ) { |
272 | | - $part = $stack->top->getCurrentPart(); |
273 | | - if ( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) { |
274 | | - // Comments abutting, no change in visual end |
275 | | - $part->commentEnd = $wsEnd; |
276 | | - } else { |
277 | | - $part->visualEnd = $wsStart; |
278 | | - $part->commentEnd = $endPos; |
279 | | - } |
280 | | - } |
281 | | - $i = $endPos + 1; |
282 | | - $inner = substr( $text, $startPos, $endPos - $startPos + 1 ); |
283 | | - $accum->addNodeWithText( 'comment', $inner ); |
284 | | - } |
285 | | - continue; |
286 | | - } |
287 | | - $name = $matches[1]; |
288 | | - $lowerName = strtolower( $name ); |
289 | | - $attrStart = $i + strlen( $name ) + 1; |
290 | | - |
291 | | - // Find end of tag |
292 | | - $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart ); |
293 | | - if ( $tagEndPos === false ) { |
294 | | - // Infinite backtrack |
295 | | - // Disable tag search to prevent worst-case O(N^2) performance |
296 | | - $noMoreGT = true; |
297 | | - $accum->addLiteral( '<' ); |
298 | | - ++$i; |
299 | | - continue; |
300 | | - } |
301 | | - |
302 | | - // Handle ignored tags |
303 | | - if ( in_array( $lowerName, $ignoredTags ) ) { |
304 | | - $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i + 1 ) ); |
305 | | - $i = $tagEndPos + 1; |
306 | | - continue; |
307 | | - } |
308 | | - |
309 | | - $tagStartPos = $i; |
310 | | - if ( $text[$tagEndPos-1] == '/' ) { |
311 | | - // Short end tag |
312 | | - $attrEnd = $tagEndPos - 1; |
313 | | - $inner = null; |
314 | | - $i = $tagEndPos + 1; |
315 | | - $close = null; |
316 | | - } else { |
317 | | - $attrEnd = $tagEndPos; |
318 | | - // Find closing tag |
319 | | - if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", |
320 | | - $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) |
321 | | - { |
322 | | - $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); |
323 | | - $i = $matches[0][1] + strlen( $matches[0][0] ); |
324 | | - $close = $matches[0][0]; |
325 | | - } else { |
326 | | - // No end tag -- let it run out to the end of the text. |
327 | | - $inner = substr( $text, $tagEndPos + 1 ); |
328 | | - $i = strlen( $text ); |
329 | | - $close = null; |
330 | | - } |
331 | | - } |
332 | | - // <includeonly> and <noinclude> just become <ignore> tags |
333 | | - if ( in_array( $lowerName, $ignoredElements ) ) { |
334 | | - $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) ); |
335 | | - continue; |
336 | | - } |
337 | | - |
338 | | - if ( $attrEnd <= $attrStart ) { |
339 | | - $attr = ''; |
340 | | - } else { |
341 | | - // Note that the attr element contains the whitespace between name and attribute, |
342 | | - // this is necessary for precise reconstruction during pre-save transform. |
343 | | - $attr = substr( $text, $attrStart, $attrEnd - $attrStart ); |
344 | | - } |
345 | | - |
346 | | - $extNode = new PPNode_Hash_Tree( 'ext' ); |
347 | | - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'name', $name ) ); |
348 | | - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'attr', $attr ) ); |
349 | | - if ( $inner !== null ) { |
350 | | - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'inner', $inner ) ); |
351 | | - } |
352 | | - if ( $close !== null ) { |
353 | | - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) ); |
354 | | - } |
355 | | - $accum->addNode( $extNode ); |
356 | | - } |
357 | | - |
358 | | - elseif ( $found == 'line-start' ) { |
359 | | - // Is this the start of a heading? |
360 | | - // Line break belongs before the heading element in any case |
361 | | - if ( $fakeLineStart ) { |
362 | | - $fakeLineStart = false; |
363 | | - } else { |
364 | | - $accum->addLiteral( $curChar ); |
365 | | - $i++; |
366 | | - } |
367 | | - |
368 | | - $count = strspn( $text, '=', $i, 6 ); |
369 | | - if ( $count == 1 && $findEquals ) { |
370 | | - // DWIM: This looks kind of like a name/value separator |
371 | | - // Let's let the equals handler have it and break the potential heading |
372 | | - // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex. |
373 | | - } elseif ( $count > 0 ) { |
374 | | - $piece = array( |
375 | | - 'open' => "\n", |
376 | | - 'close' => "\n", |
377 | | - 'parts' => array( new PPDPart_Hash( str_repeat( '=', $count ) ) ), |
378 | | - 'startPos' => $i, |
379 | | - 'count' => $count ); |
380 | | - $stack->push( $piece ); |
381 | | - $accum =& $stack->getAccum(); |
382 | | - extract( $stack->getFlags() ); |
383 | | - $i += $count; |
384 | | - } |
385 | | - } |
386 | | - |
387 | | - elseif ( $found == 'line-end' ) { |
388 | | - $piece = $stack->top; |
389 | | - // A heading must be open, otherwise \n wouldn't have been in the search list |
390 | | - assert( $piece->open == "\n" ); |
391 | | - $part = $piece->getCurrentPart(); |
392 | | - // Search back through the input to see if it has a proper close |
393 | | - // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient |
394 | | - $wsLength = strspn( $revText, " \t", strlen( $text ) - $i ); |
395 | | - $searchStart = $i - $wsLength; |
396 | | - if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { |
397 | | - // Comment found at line end |
398 | | - // Search for equals signs before the comment |
399 | | - $searchStart = $part->visualEnd; |
400 | | - $searchStart -= strspn( $revText, " \t", strlen( $text ) - $searchStart ); |
401 | | - } |
402 | | - $count = $piece->count; |
403 | | - $equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart ); |
404 | | - if ( $equalsLength > 0 ) { |
405 | | - if ( $i - $equalsLength == $piece->startPos ) { |
406 | | - // This is just a single string of equals signs on its own line |
407 | | - // Replicate the doHeadings behaviour /={count}(.+)={count}/ |
408 | | - // First find out how many equals signs there really are (don't stop at 6) |
409 | | - $count = $equalsLength; |
410 | | - if ( $count < 3 ) { |
411 | | - $count = 0; |
412 | | - } else { |
413 | | - $count = min( 6, intval( ( $count - 1 ) / 2 ) ); |
414 | | - } |
415 | | - } else { |
416 | | - $count = min( $equalsLength, $count ); |
417 | | - } |
418 | | - if ( $count > 0 ) { |
419 | | - // Normal match, output <h> |
420 | | - $element = new PPNode_Hash_Tree( 'possible-h' ); |
421 | | - $element->addChild( new PPNode_Hash_Attr( 'level', $count ) ); |
422 | | - $element->addChild( new PPNode_Hash_Attr( 'i', $headingIndex++ ) ); |
423 | | - $element->lastChild->nextSibling = $accum->firstNode; |
424 | | - $element->lastChild = $accum->lastNode; |
425 | | - } else { |
426 | | - // Single equals sign on its own line, count=0 |
427 | | - $element = $accum; |
428 | | - } |
429 | | - } else { |
430 | | - // No match, no <h>, just pass down the inner text |
431 | | - $element = $accum; |
432 | | - } |
433 | | - // Unwind the stack |
434 | | - $stack->pop(); |
435 | | - $accum =& $stack->getAccum(); |
436 | | - extract( $stack->getFlags() ); |
437 | | - |
438 | | - // Append the result to the enclosing accumulator |
439 | | - if ( $element instanceof PPNode ) { |
440 | | - $accum->addNode( $element ); |
441 | | - } else { |
442 | | - $accum->addAccum( $element ); |
443 | | - } |
444 | | - // Note that we do NOT increment the input pointer. |
445 | | - // This is because the closing linebreak could be the opening linebreak of |
446 | | - // another heading. Infinite loops are avoided because the next iteration MUST |
447 | | - // hit the heading open case above, which unconditionally increments the |
448 | | - // input pointer. |
449 | | - } |
450 | | - |
451 | | - elseif ( $found == 'open' ) { |
452 | | - # count opening brace characters |
453 | | - $count = strspn( $text, $curChar, $i ); |
454 | | - |
455 | | - # we need to add to stack only if opening brace count is enough for one of the rules |
456 | | - if ( $count >= $rule['min'] ) { |
457 | | - # Add it to the stack |
458 | | - $piece = array( |
459 | | - 'open' => $curChar, |
460 | | - 'close' => $rule['end'], |
461 | | - 'count' => $count, |
462 | | - 'lineStart' => ($i > 0 && $text[$i-1] == "\n"), |
463 | | - ); |
464 | | - |
465 | | - $stack->push( $piece ); |
466 | | - $accum =& $stack->getAccum(); |
467 | | - extract( $stack->getFlags() ); |
468 | | - } else { |
469 | | - # Add literal brace(s) |
470 | | - $accum->addLiteral( str_repeat( $curChar, $count ) ); |
471 | | - } |
472 | | - $i += $count; |
473 | | - } |
474 | | - |
475 | | - elseif ( $found == 'close' ) { |
476 | | - $piece = $stack->top; |
477 | | - # lets check if there are enough characters for closing brace |
478 | | - $maxCount = $piece->count; |
479 | | - $count = strspn( $text, $curChar, $i, $maxCount ); |
480 | | - |
481 | | - # check for maximum matching characters (if there are 5 closing |
482 | | - # characters, we will probably need only 3 - depending on the rules) |
483 | | - $matchingCount = 0; |
484 | | - $rule = $rules[$piece->open]; |
485 | | - if ( $count > $rule['max'] ) { |
486 | | - # The specified maximum exists in the callback array, unless the caller |
487 | | - # has made an error |
488 | | - $matchingCount = $rule['max']; |
489 | | - } else { |
490 | | - # Count is less than the maximum |
491 | | - # Skip any gaps in the callback array to find the true largest match |
492 | | - # Need to use array_key_exists not isset because the callback can be null |
493 | | - $matchingCount = $count; |
494 | | - while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) { |
495 | | - --$matchingCount; |
496 | | - } |
497 | | - } |
498 | | - |
499 | | - if ($matchingCount <= 0) { |
500 | | - # No matching element found in callback array |
501 | | - # Output a literal closing brace and continue |
502 | | - $accum->addLiteral( str_repeat( $curChar, $count ) ); |
503 | | - $i += $count; |
504 | | - continue; |
505 | | - } |
506 | | - $name = $rule['names'][$matchingCount]; |
507 | | - if ( $name === null ) { |
508 | | - // No element, just literal text |
509 | | - $element = $piece->breakSyntax( $matchingCount ); |
510 | | - $element->addLiteral( str_repeat( $rule['end'], $matchingCount ) ); |
511 | | - } else { |
512 | | - # Create XML element |
513 | | - # Note: $parts is already XML, does not need to be encoded further |
514 | | - $parts = $piece->parts; |
515 | | - $titleAccum = $parts[0]->out; |
516 | | - unset( $parts[0] ); |
517 | | - |
518 | | - $element = new PPNode_Hash_Tree( $name ); |
519 | | - |
520 | | - # The invocation is at the start of the line if lineStart is set in |
521 | | - # the stack, and all opening brackets are used up. |
522 | | - if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) { |
523 | | - $element->addChild( new PPNode_Hash_Attr( 'lineStart', 1 ) ); |
524 | | - } |
525 | | - $titleNode = new PPNode_Hash_Tree( 'title' ); |
526 | | - $titleNode->firstChild = $titleAccum->firstNode; |
527 | | - $titleNode->lastChild = $titleAccum->lastNode; |
528 | | - $element->addChild( $titleNode ); |
529 | | - $argIndex = 1; |
530 | | - foreach ( $parts as $partIndex => $part ) { |
531 | | - if ( isset( $part->eqpos ) ) { |
532 | | - // Find equals |
533 | | - $lastNode = false; |
534 | | - for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) { |
535 | | - if ( $node === $part->eqpos ) { |
536 | | - break; |
537 | | - } |
538 | | - $lastNode = $node; |
539 | | - } |
540 | | - if ( !$node ) { |
541 | | - throw new MWException( __METHOD__. ': eqpos not found' ); |
542 | | - } |
543 | | - if ( $node->name !== 'equals' ) { |
544 | | - throw new MWException( __METHOD__ .': eqpos is not equals' ); |
545 | | - } |
546 | | - $equalsNode = $node; |
547 | | - |
548 | | - // Construct name node |
549 | | - $nameNode = new PPNode_Hash_Tree( 'name' ); |
550 | | - if ( $lastNode !== false ) { |
551 | | - $lastNode->nextSibling = false; |
552 | | - $nameNode->firstChild = $part->out->firstNode; |
553 | | - $nameNode->lastChild = $lastNode; |
554 | | - } |
555 | | - |
556 | | - // Construct value node |
557 | | - $valueNode = new PPNode_Hash_Tree( 'value' ); |
558 | | - if ( $equalsNode->nextSibling !== false ) { |
559 | | - $valueNode->firstChild = $equalsNode->nextSibling; |
560 | | - $valueNode->lastChild = $part->out->lastNode; |
561 | | - } |
562 | | - $partNode = new PPNode_Hash_Tree( 'part' ); |
563 | | - $partNode->addChild( $nameNode ); |
564 | | - $partNode->addChild( $equalsNode->firstChild ); |
565 | | - $partNode->addChild( $valueNode ); |
566 | | - $element->addChild( $partNode ); |
567 | | - } else { |
568 | | - $partNode = new PPNode_Hash_Tree( 'part' ); |
569 | | - $nameNode = new PPNode_Hash_Tree( 'name' ); |
570 | | - $nameNode->addChild( new PPNode_Hash_Attr( 'index', $argIndex++ ) ); |
571 | | - $valueNode = new PPNode_Hash_Tree( 'value' ); |
572 | | - $valueNode->firstChild = $part->out->firstNode; |
573 | | - $valueNode->lastChild = $part->out->lastNode; |
574 | | - $partNode->addChild( $nameNode ); |
575 | | - $partNode->addChild( $valueNode ); |
576 | | - $element->addChild( $partNode ); |
577 | | - } |
578 | | - } |
579 | | - } |
580 | | - |
581 | | - # Advance input pointer |
582 | | - $i += $matchingCount; |
583 | | - |
584 | | - # Unwind the stack |
585 | | - $stack->pop(); |
586 | | - $accum =& $stack->getAccum(); |
587 | | - |
588 | | - # Re-add the old stack element if it still has unmatched opening characters remaining |
589 | | - if ($matchingCount < $piece->count) { |
590 | | - $piece->parts = array( new PPDPart_Hash ); |
591 | | - $piece->count -= $matchingCount; |
592 | | - # do we still qualify for any callback with remaining count? |
593 | | - $names = $rules[$piece->open]['names']; |
594 | | - $skippedBraces = 0; |
595 | | - $enclosingAccum =& $accum; |
596 | | - while ( $piece->count ) { |
597 | | - if ( array_key_exists( $piece->count, $names ) ) { |
598 | | - $stack->push( $piece ); |
599 | | - $accum =& $stack->getAccum(); |
600 | | - break; |
601 | | - } |
602 | | - --$piece->count; |
603 | | - $skippedBraces ++; |
604 | | - } |
605 | | - $enclosingAccum->addLiteral( str_repeat( $piece->open, $skippedBraces ) ); |
606 | | - } |
607 | | - |
608 | | - extract( $stack->getFlags() ); |
609 | | - |
610 | | - # Add XML element to the enclosing accumulator |
611 | | - if ( $element instanceof PPNode ) { |
612 | | - $accum->addNode( $element ); |
613 | | - } else { |
614 | | - $accum->addAccum( $element ); |
615 | | - } |
616 | | - } |
617 | | - |
618 | | - elseif ( $found == 'pipe' ) { |
619 | | - $findEquals = true; // shortcut for getFlags() |
620 | | - $stack->addPart(); |
621 | | - $accum =& $stack->getAccum(); |
622 | | - ++$i; |
623 | | - } |
624 | | - |
625 | | - elseif ( $found == 'equals' ) { |
626 | | - $findEquals = false; // shortcut for getFlags() |
627 | | - $accum->addNodeWithText( 'equals', '=' ); |
628 | | - $stack->getCurrentPart()->eqpos = $accum->lastNode; |
629 | | - ++$i; |
630 | | - } |
631 | | - } |
632 | | - |
633 | | - # Output any remaining unclosed brackets |
634 | | - foreach ( $stack->stack as $piece ) { |
635 | | - $stack->rootAccum->addAccum( $piece->breakSyntax() ); |
636 | | - } |
637 | | - |
638 | | - # Enable top-level headings |
639 | | - for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) { |
640 | | - if ( isset( $node->name ) && $node->name === 'possible-h' ) { |
641 | | - $node->name = 'h'; |
642 | | - } |
643 | | - } |
644 | | - |
645 | | - $rootNode = new PPNode_Hash_Tree( 'root' ); |
646 | | - $rootNode->firstChild = $stack->rootAccum->firstNode; |
647 | | - $rootNode->lastChild = $stack->rootAccum->lastNode; |
648 | | - |
649 | | - // Cache |
650 | | - if ($cacheable) { |
651 | | - $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );; |
652 | | - $wgMemc->set( $cacheKey, $cacheValue, 86400 ); |
653 | | - wfProfileOut( __METHOD__.'-cache-miss' ); |
654 | | - wfProfileOut( __METHOD__.'-cacheable' ); |
655 | | - wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" ); |
656 | | - } |
657 | | - |
658 | | - wfProfileOut( __METHOD__ ); |
659 | | - return $rootNode; |
660 | | - } |
661 | | -} |
662 | | - |
663 | | -/** |
664 | | - * Stack class to help Preprocessor::preprocessToObj() |
665 | | - * @ingroup Parser |
666 | | - */ |
667 | | -class PPDStack_Hash extends PPDStack { |
668 | | - function __construct() { |
669 | | - $this->elementClass = 'PPDStackElement_Hash'; |
670 | | - parent::__construct(); |
671 | | - $this->rootAccum = new PPDAccum_Hash; |
672 | | - } |
673 | | -} |
674 | | - |
675 | | -/** |
676 | | - * @ingroup Parser |
677 | | - */ |
678 | | -class PPDStackElement_Hash extends PPDStackElement { |
679 | | - function __construct( $data = array() ) { |
680 | | - $this->partClass = 'PPDPart_Hash'; |
681 | | - parent::__construct( $data ); |
682 | | - } |
683 | | - |
684 | | - /** |
685 | | - * Get the accumulator that would result if the close is not found. |
686 | | - */ |
687 | | - function breakSyntax( $openingCount = false ) { |
688 | | - if ( $this->open == "\n" ) { |
689 | | - $accum = $this->parts[0]->out; |
690 | | - } else { |
691 | | - if ( $openingCount === false ) { |
692 | | - $openingCount = $this->count; |
693 | | - } |
694 | | - $accum = new PPDAccum_Hash; |
695 | | - $accum->addLiteral( str_repeat( $this->open, $openingCount ) ); |
696 | | - $first = true; |
697 | | - foreach ( $this->parts as $part ) { |
698 | | - if ( $first ) { |
699 | | - $first = false; |
700 | | - } else { |
701 | | - $accum->addLiteral( '|' ); |
702 | | - } |
703 | | - $accum->addAccum( $part->out ); |
704 | | - } |
705 | | - } |
706 | | - return $accum; |
707 | | - } |
708 | | -} |
709 | | - |
710 | | -/** |
711 | | - * @ingroup Parser |
712 | | - */ |
713 | | -class PPDPart_Hash extends PPDPart { |
714 | | - function __construct( $out = '' ) { |
715 | | - $accum = new PPDAccum_Hash; |
716 | | - if ( $out !== '' ) { |
717 | | - $accum->addLiteral( $out ); |
718 | | - } |
719 | | - parent::__construct( $accum ); |
720 | | - } |
721 | | -} |
722 | | - |
723 | | -/** |
724 | | - * @ingroup Parser |
725 | | - */ |
726 | | -class PPDAccum_Hash { |
727 | | - var $firstNode, $lastNode; |
728 | | - |
729 | | - function __construct() { |
730 | | - $this->firstNode = $this->lastNode = false; |
731 | | - } |
732 | | - |
733 | | - /** |
734 | | - * Append a string literal |
735 | | - */ |
736 | | - function addLiteral( $s ) { |
737 | | - if ( $this->lastNode === false ) { |
738 | | - $this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s ); |
739 | | - } elseif ( $this->lastNode instanceof PPNode_Hash_Text ) { |
740 | | - $this->lastNode->value .= $s; |
741 | | - } else { |
742 | | - $this->lastNode->nextSibling = new PPNode_Hash_Text( $s ); |
743 | | - $this->lastNode = $this->lastNode->nextSibling; |
744 | | - } |
745 | | - } |
746 | | - |
747 | | - /** |
748 | | - * Append a PPNode |
749 | | - */ |
750 | | - function addNode( PPNode $node ) { |
751 | | - if ( $this->lastNode === false ) { |
752 | | - $this->firstNode = $this->lastNode = $node; |
753 | | - } else { |
754 | | - $this->lastNode->nextSibling = $node; |
755 | | - $this->lastNode = $node; |
756 | | - } |
757 | | - } |
758 | | - |
759 | | - /** |
760 | | - * Append a tree node with text contents |
761 | | - */ |
762 | | - function addNodeWithText( $name, $value ) { |
763 | | - $node = PPNode_Hash_Tree::newWithText( $name, $value ); |
764 | | - $this->addNode( $node ); |
765 | | - } |
766 | | - |
767 | | - /** |
768 | | - * Append a PPAccum_Hash |
769 | | - * Takes over ownership of the nodes in the source argument. These nodes may |
770 | | - * subsequently be modified, especially nextSibling. |
771 | | - */ |
772 | | - function addAccum( $accum ) { |
773 | | - if ( $accum->lastNode === false ) { |
774 | | - // nothing to add |
775 | | - } elseif ( $this->lastNode === false ) { |
776 | | - $this->firstNode = $accum->firstNode; |
777 | | - $this->lastNode = $accum->lastNode; |
778 | | - } else { |
779 | | - $this->lastNode->nextSibling = $accum->firstNode; |
780 | | - $this->lastNode = $accum->lastNode; |
781 | | - } |
782 | | - } |
783 | | -} |
784 | | - |
785 | | -/** |
786 | | - * An expansion frame, used as a context to expand the result of preprocessToObj() |
787 | | - * @ingroup Parser |
788 | | - */ |
789 | | -class PPFrame_Hash implements PPFrame { |
790 | | - var $preprocessor, $parser, $title; |
791 | | - var $titleCache; |
792 | | - |
793 | | - /** |
794 | | - * Hashtable listing templates which are disallowed for expansion in this frame, |
795 | | - * having been encountered previously in parent frames. |
796 | | - */ |
797 | | - var $loopCheckHash; |
798 | | - |
799 | | - /** |
800 | | - * Recursion depth of this frame, top = 0 |
801 | | - * Note that this is NOT the same as expansion depth in expand() |
802 | | - */ |
803 | | - var $depth; |
804 | | - |
805 | | - |
806 | | - /** |
807 | | - * Construct a new preprocessor frame. |
808 | | - * @param Preprocessor $preprocessor The parent preprocessor |
809 | | - */ |
810 | | - function __construct( $preprocessor ) { |
811 | | - $this->preprocessor = $preprocessor; |
812 | | - $this->parser = $preprocessor->parser; |
813 | | - $this->title = $this->parser->mTitle; |
814 | | - $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); |
815 | | - $this->loopCheckHash = array(); |
816 | | - $this->depth = 0; |
817 | | - } |
818 | | - |
819 | | - /** |
820 | | - * Create a new child frame |
821 | | - * $args is optionally a multi-root PPNode or array containing the template arguments |
822 | | - */ |
823 | | - function newChild( $args = false, $title = false ) { |
824 | | - $namedArgs = array(); |
825 | | - $numberedArgs = array(); |
826 | | - if ( $title === false ) { |
827 | | - $title = $this->title; |
828 | | - } |
829 | | - if ( $args !== false ) { |
830 | | - $xpath = false; |
831 | | - if ( $args instanceof PPNode_Hash_Array ) { |
832 | | - $args = $args->value; |
833 | | - } elseif ( !is_array( $args ) ) { |
834 | | - throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' ); |
835 | | - } |
836 | | - foreach ( $args as $arg ) { |
837 | | - $bits = $arg->splitArg(); |
838 | | - if ( $bits['index'] !== '' ) { |
839 | | - // Numbered parameter |
840 | | - $numberedArgs[$bits['index']] = $bits['value']; |
841 | | - unset( $namedArgs[$bits['index']] ); |
842 | | - } else { |
843 | | - // Named parameter |
844 | | - $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) ); |
845 | | - $namedArgs[$name] = $bits['value']; |
846 | | - unset( $numberedArgs[$name] ); |
847 | | - } |
848 | | - } |
849 | | - } |
850 | | - return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title ); |
851 | | - } |
852 | | - |
853 | | - function expand( $root, $flags = 0 ) { |
854 | | - static $expansionDepth = 0; |
855 | | - if ( is_string( $root ) ) { |
856 | | - return $root; |
857 | | - } |
858 | | - |
859 | | - if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount ) |
860 | | - { |
861 | | - return '<span class="error">Node-count limit exceeded</span>'; |
862 | | - } |
863 | | - if ( $expansionDepth > $this->parser->mOptions->mMaxPPExpandDepth ) { |
864 | | - return '<span class="error">Expansion depth limit exceeded</span>'; |
865 | | - } |
866 | | - ++$expansionDepth; |
867 | | - |
868 | | - $outStack = array( '', '' ); |
869 | | - $iteratorStack = array( false, $root ); |
870 | | - $indexStack = array( 0, 0 ); |
871 | | - |
872 | | - while ( count( $iteratorStack ) > 1 ) { |
873 | | - $level = count( $outStack ) - 1; |
874 | | - $iteratorNode =& $iteratorStack[ $level ]; |
875 | | - $out =& $outStack[$level]; |
876 | | - $index =& $indexStack[$level]; |
877 | | - |
878 | | - if ( is_array( $iteratorNode ) ) { |
879 | | - if ( $index >= count( $iteratorNode ) ) { |
880 | | - // All done with this iterator |
881 | | - $iteratorStack[$level] = false; |
882 | | - $contextNode = false; |
883 | | - } else { |
884 | | - $contextNode = $iteratorNode[$index]; |
885 | | - $index++; |
886 | | - } |
887 | | - } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) { |
888 | | - if ( $index >= $iteratorNode->getLength() ) { |
889 | | - // All done with this iterator |
890 | | - $iteratorStack[$level] = false; |
891 | | - $contextNode = false; |
892 | | - } else { |
893 | | - $contextNode = $iteratorNode->item( $index ); |
894 | | - $index++; |
895 | | - } |
896 | | - } else { |
897 | | - // Copy to $contextNode and then delete from iterator stack, |
898 | | - // because this is not an iterator but we do have to execute it once |
899 | | - $contextNode = $iteratorStack[$level]; |
900 | | - $iteratorStack[$level] = false; |
901 | | - } |
902 | | - |
903 | | - $newIterator = false; |
904 | | - |
905 | | - if ( $contextNode === false ) { |
906 | | - // nothing to do |
907 | | - } elseif ( is_string( $contextNode ) ) { |
908 | | - $out .= $contextNode; |
909 | | - } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_Hash_Array ) { |
910 | | - $newIterator = $contextNode; |
911 | | - } elseif ( $contextNode instanceof PPNode_Hash_Attr ) { |
912 | | - // No output |
913 | | - } elseif ( $contextNode instanceof PPNode_Hash_Text ) { |
914 | | - $out .= $contextNode->value; |
915 | | - } elseif ( $contextNode instanceof PPNode_Hash_Tree ) { |
916 | | - if ( $contextNode->name == 'template' ) { |
917 | | - # Double-brace expansion |
918 | | - $bits = $contextNode->splitTemplate(); |
919 | | - if ( $flags & self::NO_TEMPLATES ) { |
920 | | - $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] ); |
921 | | - } else { |
922 | | - $ret = $this->parser->braceSubstitution( $bits, $this ); |
923 | | - if ( isset( $ret['object'] ) ) { |
924 | | - $newIterator = $ret['object']; |
925 | | - } else { |
926 | | - $out .= $ret['text']; |
927 | | - } |
928 | | - } |
929 | | - } elseif ( $contextNode->name == 'tplarg' ) { |
930 | | - # Triple-brace expansion |
931 | | - $bits = $contextNode->splitTemplate(); |
932 | | - if ( $flags & self::NO_ARGS ) { |
933 | | - $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] ); |
934 | | - } else { |
935 | | - $ret = $this->parser->argSubstitution( $bits, $this ); |
936 | | - if ( isset( $ret['object'] ) ) { |
937 | | - $newIterator = $ret['object']; |
938 | | - } else { |
939 | | - $out .= $ret['text']; |
940 | | - } |
941 | | - } |
942 | | - } elseif ( $contextNode->name == 'comment' ) { |
943 | | - # HTML-style comment |
944 | | - # Remove it in HTML, pre+remove and STRIP_COMMENTS modes |
945 | | - if ( $this->parser->ot['html'] |
946 | | - || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) |
947 | | - || ( $flags & self::STRIP_COMMENTS ) ) |
948 | | - { |
949 | | - $out .= ''; |
950 | | - } |
951 | | - # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result |
952 | | - # Not in RECOVER_COMMENTS mode (extractSections) though |
953 | | - elseif ( $this->parser->ot['wiki'] && ! ( $flags & self::RECOVER_COMMENTS ) ) { |
954 | | - $out .= $this->parser->insertStripItem( $contextNode->firstChild->value ); |
955 | | - } |
956 | | - # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove |
957 | | - else { |
958 | | - $out .= $contextNode->firstChild->value; |
959 | | - } |
960 | | - } elseif ( $contextNode->name == 'ignore' ) { |
961 | | - # Output suppression used by <includeonly> etc. |
962 | | - # OT_WIKI will only respect <ignore> in substed templates. |
963 | | - # The other output types respect it unless NO_IGNORE is set. |
964 | | - # extractSections() sets NO_IGNORE and so never respects it. |
965 | | - if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) { |
966 | | - $out .= $contextNode->firstChild->value; |
967 | | - } else { |
968 | | - //$out .= ''; |
969 | | - } |
970 | | - } elseif ( $contextNode->name == 'ext' ) { |
971 | | - # Extension tag |
972 | | - $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null ); |
973 | | - $out .= $this->parser->extensionSubstitution( $bits, $this ); |
974 | | - } elseif ( $contextNode->name == 'h' ) { |
975 | | - # Heading |
976 | | - if ( $this->parser->ot['html'] ) { |
977 | | - # Expand immediately and insert heading index marker |
978 | | - $s = ''; |
979 | | - for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) { |
980 | | - $s .= $this->expand( $node, $flags ); |
981 | | - } |
982 | | - |
983 | | - $bits = $contextNode->splitHeading(); |
984 | | - $titleText = $this->title->getPrefixedDBkey(); |
985 | | - $this->parser->mHeadings[] = array( $titleText, $bits['i'] ); |
986 | | - $serial = count( $this->parser->mHeadings ) - 1; |
987 | | - $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX; |
988 | | - $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] ); |
989 | | - $this->parser->mStripState->general->setPair( $marker, '' ); |
990 | | - $out .= $s; |
991 | | - } else { |
992 | | - # Expand in virtual stack |
993 | | - $newIterator = $contextNode->getChildren(); |
994 | | - } |
995 | | - } else { |
996 | | - # Generic recursive expansion |
997 | | - $newIterator = $contextNode->getChildren(); |
998 | | - } |
999 | | - } else { |
1000 | | - throw new MWException( __METHOD__.': Invalid parameter type' ); |
1001 | | - } |
1002 | | - |
1003 | | - if ( $newIterator !== false ) { |
1004 | | - $outStack[] = ''; |
1005 | | - $iteratorStack[] = $newIterator; |
1006 | | - $indexStack[] = 0; |
1007 | | - } elseif ( $iteratorStack[$level] === false ) { |
1008 | | - // Return accumulated value to parent |
1009 | | - // With tail recursion |
1010 | | - while ( $iteratorStack[$level] === false && $level > 0 ) { |
1011 | | - $outStack[$level - 1] .= $out; |
1012 | | - array_pop( $outStack ); |
1013 | | - array_pop( $iteratorStack ); |
1014 | | - array_pop( $indexStack ); |
1015 | | - $level--; |
1016 | | - } |
1017 | | - } |
1018 | | - } |
1019 | | - --$expansionDepth; |
1020 | | - return $outStack[0]; |
1021 | | - } |
1022 | | - |
1023 | | - function implodeWithFlags( $sep, $flags /*, ... */ ) { |
1024 | | - $args = array_slice( func_get_args(), 2 ); |
1025 | | - |
1026 | | - $first = true; |
1027 | | - $s = ''; |
1028 | | - foreach ( $args as $root ) { |
1029 | | - if ( $root instanceof PPNode_Hash_Array ) { |
1030 | | - $root = $root->value; |
1031 | | - } |
1032 | | - if ( !is_array( $root ) ) { |
1033 | | - $root = array( $root ); |
1034 | | - } |
1035 | | - foreach ( $root as $node ) { |
1036 | | - if ( $first ) { |
1037 | | - $first = false; |
1038 | | - } else { |
1039 | | - $s .= $sep; |
1040 | | - } |
1041 | | - $s .= $this->expand( $node, $flags ); |
1042 | | - } |
1043 | | - } |
1044 | | - return $s; |
1045 | | - } |
1046 | | - |
1047 | | - /** |
1048 | | - * Implode with no flags specified |
1049 | | - * This previously called implodeWithFlags but has now been inlined to reduce stack depth |
1050 | | - */ |
1051 | | - function implode( $sep /*, ... */ ) { |
1052 | | - $args = array_slice( func_get_args(), 1 ); |
1053 | | - |
1054 | | - $first = true; |
1055 | | - $s = ''; |
1056 | | - foreach ( $args as $root ) { |
1057 | | - if ( $root instanceof PPNode_Hash_Array ) { |
1058 | | - $root = $root->value; |
1059 | | - } |
1060 | | - if ( !is_array( $root ) ) { |
1061 | | - $root = array( $root ); |
1062 | | - } |
1063 | | - foreach ( $root as $node ) { |
1064 | | - if ( $first ) { |
1065 | | - $first = false; |
1066 | | - } else { |
1067 | | - $s .= $sep; |
1068 | | - } |
1069 | | - $s .= $this->expand( $node ); |
1070 | | - } |
1071 | | - } |
1072 | | - return $s; |
1073 | | - } |
1074 | | - |
1075 | | - /** |
1076 | | - * Makes an object that, when expand()ed, will be the same as one obtained |
1077 | | - * with implode() |
1078 | | - */ |
1079 | | - function virtualImplode( $sep /*, ... */ ) { |
1080 | | - $args = array_slice( func_get_args(), 1 ); |
1081 | | - $out = array(); |
1082 | | - $first = true; |
1083 | | - |
1084 | | - foreach ( $args as $root ) { |
1085 | | - if ( $root instanceof PPNode_Hash_Array ) { |
1086 | | - $root = $root->value; |
1087 | | - } |
1088 | | - if ( !is_array( $root ) ) { |
1089 | | - $root = array( $root ); |
1090 | | - } |
1091 | | - foreach ( $root as $node ) { |
1092 | | - if ( $first ) { |
1093 | | - $first = false; |
1094 | | - } else { |
1095 | | - $out[] = $sep; |
1096 | | - } |
1097 | | - $out[] = $node; |
1098 | | - } |
1099 | | - } |
1100 | | - return new PPNode_Hash_Array( $out ); |
1101 | | - } |
1102 | | - |
1103 | | - /** |
1104 | | - * Virtual implode with brackets |
1105 | | - */ |
1106 | | - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { |
1107 | | - $args = array_slice( func_get_args(), 3 ); |
1108 | | - $out = array( $start ); |
1109 | | - $first = true; |
1110 | | - |
1111 | | - foreach ( $args as $root ) { |
1112 | | - if ( $root instanceof PPNode_Hash_Array ) { |
1113 | | - $root = $root->value; |
1114 | | - } |
1115 | | - if ( !is_array( $root ) ) { |
1116 | | - $root = array( $root ); |
1117 | | - } |
1118 | | - foreach ( $root as $node ) { |
1119 | | - if ( $first ) { |
1120 | | - $first = false; |
1121 | | - } else { |
1122 | | - $out[] = $sep; |
1123 | | - } |
1124 | | - $out[] = $node; |
1125 | | - } |
1126 | | - } |
1127 | | - $out[] = $end; |
1128 | | - return new PPNode_Hash_Array( $out ); |
1129 | | - } |
1130 | | - |
1131 | | - function __toString() { |
1132 | | - return 'frame{}'; |
1133 | | - } |
1134 | | - |
1135 | | - function getPDBK( $level = false ) { |
1136 | | - if ( $level === false ) { |
1137 | | - return $this->title->getPrefixedDBkey(); |
1138 | | - } else { |
1139 | | - return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false; |
1140 | | - } |
1141 | | - } |
1142 | | - |
1143 | | - function getArguments() { |
1144 | | - return array(); |
1145 | | - } |
1146 | | - |
1147 | | - function getNumberedArguments() { |
1148 | | - return array(); |
1149 | | - } |
1150 | | - |
1151 | | - function getNamedArguments() { |
1152 | | - return array(); |
1153 | | - } |
1154 | | - |
1155 | | - /** |
1156 | | - * Returns true if there are no arguments in this frame |
1157 | | - */ |
1158 | | - function isEmpty() { |
1159 | | - return true; |
1160 | | - } |
1161 | | - |
1162 | | - function getArgument( $name ) { |
1163 | | - return false; |
1164 | | - } |
1165 | | - |
1166 | | - /** |
1167 | | - * Returns true if the infinite loop check is OK, false if a loop is detected |
1168 | | - */ |
1169 | | - function loopCheck( $title ) { |
1170 | | - return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); |
1171 | | - } |
1172 | | - |
1173 | | - /** |
1174 | | - * Return true if the frame is a template frame |
1175 | | - */ |
1176 | | - function isTemplate() { |
1177 | | - return false; |
1178 | | - } |
1179 | | -} |
1180 | | - |
1181 | | -/** |
1182 | | - * Expansion frame with template arguments |
1183 | | - * @ingroup Parser |
1184 | | - */ |
1185 | | -class PPTemplateFrame_Hash extends PPFrame_Hash { |
1186 | | - var $numberedArgs, $namedArgs, $parent; |
1187 | | - var $numberedExpansionCache, $namedExpansionCache; |
1188 | | - |
1189 | | - function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) { |
1190 | | - PPFrame_Hash::__construct( $preprocessor ); |
1191 | | - $this->parent = $parent; |
1192 | | - $this->numberedArgs = $numberedArgs; |
1193 | | - $this->namedArgs = $namedArgs; |
1194 | | - $this->title = $title; |
1195 | | - $pdbk = $title ? $title->getPrefixedDBkey() : false; |
1196 | | - $this->titleCache = $parent->titleCache; |
1197 | | - $this->titleCache[] = $pdbk; |
1198 | | - $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; |
1199 | | - if ( $pdbk !== false ) { |
1200 | | - $this->loopCheckHash[$pdbk] = true; |
1201 | | - } |
1202 | | - $this->depth = $parent->depth + 1; |
1203 | | - $this->numberedExpansionCache = $this->namedExpansionCache = array(); |
1204 | | - } |
1205 | | - |
1206 | | - function __toString() { |
1207 | | - $s = 'tplframe{'; |
1208 | | - $first = true; |
1209 | | - $args = $this->numberedArgs + $this->namedArgs; |
1210 | | - foreach ( $args as $name => $value ) { |
1211 | | - if ( $first ) { |
1212 | | - $first = false; |
1213 | | - } else { |
1214 | | - $s .= ', '; |
1215 | | - } |
1216 | | - $s .= "\"$name\":\"" . |
1217 | | - str_replace( '"', '\\"', $value->__toString() ) . '"'; |
1218 | | - } |
1219 | | - $s .= '}'; |
1220 | | - return $s; |
1221 | | - } |
1222 | | - /** |
1223 | | - * Returns true if there are no arguments in this frame |
1224 | | - */ |
1225 | | - function isEmpty() { |
1226 | | - return !count( $this->numberedArgs ) && !count( $this->namedArgs ); |
1227 | | - } |
1228 | | - |
1229 | | - function getArguments() { |
1230 | | - $arguments = array(); |
1231 | | - foreach ( array_merge( |
1232 | | - array_keys($this->numberedArgs), |
1233 | | - array_keys($this->namedArgs)) as $key ) { |
1234 | | - $arguments[$key] = $this->getArgument($key); |
1235 | | - } |
1236 | | - return $arguments; |
1237 | | - } |
1238 | | - |
1239 | | - function getNumberedArguments() { |
1240 | | - $arguments = array(); |
1241 | | - foreach ( array_keys($this->numberedArgs) as $key ) { |
1242 | | - $arguments[$key] = $this->getArgument($key); |
1243 | | - } |
1244 | | - return $arguments; |
1245 | | - } |
1246 | | - |
1247 | | - function getNamedArguments() { |
1248 | | - $arguments = array(); |
1249 | | - foreach ( array_keys($this->namedArgs) as $key ) { |
1250 | | - $arguments[$key] = $this->getArgument($key); |
1251 | | - } |
1252 | | - return $arguments; |
1253 | | - } |
1254 | | - |
1255 | | - function getNumberedArgument( $index ) { |
1256 | | - if ( !isset( $this->numberedArgs[$index] ) ) { |
1257 | | - return false; |
1258 | | - } |
1259 | | - if ( !isset( $this->numberedExpansionCache[$index] ) ) { |
1260 | | - # No trimming for unnamed arguments |
1261 | | - $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], self::STRIP_COMMENTS ); |
1262 | | - } |
1263 | | - return $this->numberedExpansionCache[$index]; |
1264 | | - } |
1265 | | - |
1266 | | - function getNamedArgument( $name ) { |
1267 | | - if ( !isset( $this->namedArgs[$name] ) ) { |
1268 | | - return false; |
1269 | | - } |
1270 | | - if ( !isset( $this->namedExpansionCache[$name] ) ) { |
1271 | | - # Trim named arguments post-expand, for backwards compatibility |
1272 | | - $this->namedExpansionCache[$name] = trim( |
1273 | | - $this->parent->expand( $this->namedArgs[$name], self::STRIP_COMMENTS ) ); |
1274 | | - } |
1275 | | - return $this->namedExpansionCache[$name]; |
1276 | | - } |
1277 | | - |
1278 | | - function getArgument( $name ) { |
1279 | | - $text = $this->getNumberedArgument( $name ); |
1280 | | - if ( $text === false ) { |
1281 | | - $text = $this->getNamedArgument( $name ); |
1282 | | - } |
1283 | | - return $text; |
1284 | | - } |
1285 | | - |
1286 | | - /** |
1287 | | - * Return true if the frame is a template frame |
1288 | | - */ |
1289 | | - function isTemplate() { |
1290 | | - return true; |
1291 | | - } |
1292 | | -} |
1293 | | - |
1294 | | -/** |
1295 | | - * Expansion frame with custom arguments |
1296 | | - * @ingroup Parser |
1297 | | - */ |
1298 | | -class PPCustomFrame_Hash extends PPFrame_Hash { |
1299 | | - var $args; |
1300 | | - |
1301 | | - function __construct( $preprocessor, $args ) { |
1302 | | - PPFrame_Hash::__construct( $preprocessor ); |
1303 | | - $this->args = $args; |
1304 | | - } |
1305 | | - |
1306 | | - function __toString() { |
1307 | | - $s = 'cstmframe{'; |
1308 | | - $first = true; |
1309 | | - foreach ( $this->args as $name => $value ) { |
1310 | | - if ( $first ) { |
1311 | | - $first = false; |
1312 | | - } else { |
1313 | | - $s .= ', '; |
1314 | | - } |
1315 | | - $s .= "\"$name\":\"" . |
1316 | | - str_replace( '"', '\\"', $value->__toString() ) . '"'; |
1317 | | - } |
1318 | | - $s .= '}'; |
1319 | | - return $s; |
1320 | | - } |
1321 | | - |
1322 | | - function isEmpty() { |
1323 | | - return !count( $this->args ); |
1324 | | - } |
1325 | | - |
1326 | | - function getArgument( $index ) { |
1327 | | - if ( !isset( $this->args[$index] ) ) { |
1328 | | - return false; |
1329 | | - } |
1330 | | - return $this->args[$index]; |
1331 | | - } |
1332 | | -} |
1333 | | - |
1334 | | -/** |
1335 | | - * @ingroup Parser |
1336 | | - */ |
1337 | | -class PPNode_Hash_Tree implements PPNode { |
1338 | | - var $name, $firstChild, $lastChild, $nextSibling; |
1339 | | - |
1340 | | - function __construct( $name ) { |
1341 | | - $this->name = $name; |
1342 | | - $this->firstChild = $this->lastChild = $this->nextSibling = false; |
1343 | | - } |
1344 | | - |
1345 | | - function __toString() { |
1346 | | - $inner = ''; |
1347 | | - $attribs = ''; |
1348 | | - for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) { |
1349 | | - if ( $node instanceof PPNode_Hash_Attr ) { |
1350 | | - $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"'; |
1351 | | - } else { |
1352 | | - $inner .= $node->__toString(); |
1353 | | - } |
1354 | | - } |
1355 | | - if ( $inner === '' ) { |
1356 | | - return "<{$this->name}$attribs/>"; |
1357 | | - } else { |
1358 | | - return "<{$this->name}$attribs>$inner</{$this->name}>"; |
1359 | | - } |
1360 | | - } |
1361 | | - |
1362 | | - static function newWithText( $name, $text ) { |
1363 | | - $obj = new self( $name ); |
1364 | | - $obj->addChild( new PPNode_Hash_Text( $text ) ); |
1365 | | - return $obj; |
1366 | | - } |
1367 | | - |
1368 | | - function addChild( $node ) { |
1369 | | - if ( $this->lastChild === false ) { |
1370 | | - $this->firstChild = $this->lastChild = $node; |
1371 | | - } else { |
1372 | | - $this->lastChild->nextSibling = $node; |
1373 | | - $this->lastChild = $node; |
1374 | | - } |
1375 | | - } |
1376 | | - |
1377 | | - function getChildren() { |
1378 | | - $children = array(); |
1379 | | - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { |
1380 | | - $children[] = $child; |
1381 | | - } |
1382 | | - return new PPNode_Hash_Array( $children ); |
1383 | | - } |
1384 | | - |
1385 | | - function getFirstChild() { |
1386 | | - return $this->firstChild; |
1387 | | - } |
1388 | | - |
1389 | | - function getNextSibling() { |
1390 | | - return $this->nextSibling; |
1391 | | - } |
1392 | | - |
1393 | | - function getChildrenOfType( $name ) { |
1394 | | - $children = array(); |
1395 | | - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { |
1396 | | - if ( isset( $child->name ) && $child->name === $name ) { |
1397 | | - $children[] = $name; |
1398 | | - } |
1399 | | - } |
1400 | | - return $children; |
1401 | | - } |
1402 | | - |
1403 | | - function getLength() { return false; } |
1404 | | - function item( $i ) { return false; } |
1405 | | - |
1406 | | - function getName() { |
1407 | | - return $this->name; |
1408 | | - } |
1409 | | - |
1410 | | - /** |
1411 | | - * Split a <part> node into an associative array containing: |
1412 | | - * name PPNode name |
1413 | | - * index String index |
1414 | | - * value PPNode value |
1415 | | - */ |
1416 | | - function splitArg() { |
1417 | | - $bits = array(); |
1418 | | - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { |
1419 | | - if ( !isset( $child->name ) ) { |
1420 | | - continue; |
1421 | | - } |
1422 | | - if ( $child->name === 'name' ) { |
1423 | | - $bits['name'] = $child; |
1424 | | - if ( $child->firstChild instanceof PPNode_Hash_Attr |
1425 | | - && $child->firstChild->name === 'index' ) |
1426 | | - { |
1427 | | - $bits['index'] = $child->firstChild->value; |
1428 | | - } |
1429 | | - } elseif ( $child->name === 'value' ) { |
1430 | | - $bits['value'] = $child; |
1431 | | - } |
1432 | | - } |
1433 | | - |
1434 | | - if ( !isset( $bits['name'] ) ) { |
1435 | | - throw new MWException( 'Invalid brace node passed to ' . __METHOD__ ); |
1436 | | - } |
1437 | | - if ( !isset( $bits['index'] ) ) { |
1438 | | - $bits['index'] = ''; |
1439 | | - } |
1440 | | - return $bits; |
1441 | | - } |
1442 | | - |
1443 | | - /** |
1444 | | - * Split an <ext> node into an associative array containing name, attr, inner and close |
1445 | | - * All values in the resulting array are PPNodes. Inner and close are optional. |
1446 | | - */ |
1447 | | - function splitExt() { |
1448 | | - $bits = array(); |
1449 | | - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { |
1450 | | - if ( !isset( $child->name ) ) { |
1451 | | - continue; |
1452 | | - } |
1453 | | - if ( $child->name == 'name' ) { |
1454 | | - $bits['name'] = $child; |
1455 | | - } elseif ( $child->name == 'attr' ) { |
1456 | | - $bits['attr'] = $child; |
1457 | | - } elseif ( $child->name == 'inner' ) { |
1458 | | - $bits['inner'] = $child; |
1459 | | - } elseif ( $child->name == 'close' ) { |
1460 | | - $bits['close'] = $child; |
1461 | | - } |
1462 | | - } |
1463 | | - if ( !isset( $bits['name'] ) ) { |
1464 | | - throw new MWException( 'Invalid ext node passed to ' . __METHOD__ ); |
1465 | | - } |
1466 | | - return $bits; |
1467 | | - } |
1468 | | - |
1469 | | - /** |
1470 | | - * Split an <h> node |
1471 | | - */ |
1472 | | - function splitHeading() { |
1473 | | - if ( $this->name !== 'h' ) { |
1474 | | - throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); |
1475 | | - } |
1476 | | - $bits = array(); |
1477 | | - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { |
1478 | | - if ( !isset( $child->name ) ) { |
1479 | | - continue; |
1480 | | - } |
1481 | | - if ( $child->name == 'i' ) { |
1482 | | - $bits['i'] = $child->value; |
1483 | | - } elseif ( $child->name == 'level' ) { |
1484 | | - $bits['level'] = $child->value; |
1485 | | - } |
1486 | | - } |
1487 | | - if ( !isset( $bits['i'] ) ) { |
1488 | | - throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); |
1489 | | - } |
1490 | | - return $bits; |
1491 | | - } |
1492 | | - |
1493 | | - /** |
1494 | | - * Split a <template> or <tplarg> node |
1495 | | - */ |
1496 | | - function splitTemplate() { |
1497 | | - $parts = array(); |
1498 | | - $bits = array( 'lineStart' => '' ); |
1499 | | - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { |
1500 | | - if ( !isset( $child->name ) ) { |
1501 | | - continue; |
1502 | | - } |
1503 | | - if ( $child->name == 'title' ) { |
1504 | | - $bits['title'] = $child; |
1505 | | - } |
1506 | | - if ( $child->name == 'part' ) { |
1507 | | - $parts[] = $child; |
1508 | | - } |
1509 | | - if ( $child->name == 'lineStart' ) { |
1510 | | - $bits['lineStart'] = '1'; |
1511 | | - } |
1512 | | - } |
1513 | | - if ( !isset( $bits['title'] ) ) { |
1514 | | - throw new MWException( 'Invalid node passed to ' . __METHOD__ ); |
1515 | | - } |
1516 | | - $bits['parts'] = new PPNode_Hash_Array( $parts ); |
1517 | | - return $bits; |
1518 | | - } |
1519 | | -} |
1520 | | - |
1521 | | -/** |
1522 | | - * @ingroup Parser |
1523 | | - */ |
1524 | | -class PPNode_Hash_Text implements PPNode { |
1525 | | - var $value, $nextSibling; |
1526 | | - |
1527 | | - function __construct( $value ) { |
1528 | | - if ( is_object( $value ) ) { |
1529 | | - throw new MWException( __CLASS__ . ' given object instead of string' ); |
1530 | | - } |
1531 | | - $this->value = $value; |
1532 | | - } |
1533 | | - |
1534 | | - function __toString() { |
1535 | | - return htmlspecialchars( $this->value ); |
1536 | | - } |
1537 | | - |
1538 | | - function getNextSibling() { |
1539 | | - return $this->nextSibling; |
1540 | | - } |
1541 | | - |
1542 | | - function getChildren() { return false; } |
1543 | | - function getFirstChild() { return false; } |
1544 | | - function getChildrenOfType( $name ) { return false; } |
1545 | | - function getLength() { return false; } |
1546 | | - function item( $i ) { return false; } |
1547 | | - function getName() { return '#text'; } |
1548 | | - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } |
1549 | | - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } |
1550 | | - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } |
1551 | | -} |
1552 | | - |
1553 | | -/** |
1554 | | - * @ingroup Parser |
1555 | | - */ |
1556 | | -class PPNode_Hash_Array implements PPNode { |
1557 | | - var $value, $nextSibling; |
1558 | | - |
1559 | | - function __construct( $value ) { |
1560 | | - $this->value = $value; |
1561 | | - } |
1562 | | - |
1563 | | - function __toString() { |
1564 | | - return var_export( $this, true ); |
1565 | | - } |
1566 | | - |
1567 | | - function getLength() { |
1568 | | - return count( $this->value ); |
1569 | | - } |
1570 | | - |
1571 | | - function item( $i ) { |
1572 | | - return $this->value[$i]; |
1573 | | - } |
1574 | | - |
1575 | | - function getName() { return '#nodelist'; } |
1576 | | - |
1577 | | - function getNextSibling() { |
1578 | | - return $this->nextSibling; |
1579 | | - } |
1580 | | - |
1581 | | - function getChildren() { return false; } |
1582 | | - function getFirstChild() { return false; } |
1583 | | - function getChildrenOfType( $name ) { return false; } |
1584 | | - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } |
1585 | | - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } |
1586 | | - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } |
1587 | | -} |
1588 | | - |
1589 | | -/** |
1590 | | - * @ingroup Parser |
1591 | | - */ |
1592 | | -class PPNode_Hash_Attr implements PPNode { |
1593 | | - var $name, $value, $nextSibling; |
1594 | | - |
1595 | | - function __construct( $name, $value ) { |
1596 | | - $this->name = $name; |
1597 | | - $this->value = $value; |
1598 | | - } |
1599 | | - |
1600 | | - function __toString() { |
1601 | | - return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>"; |
1602 | | - } |
1603 | | - |
1604 | | - function getName() { |
1605 | | - return $this->name; |
1606 | | - } |
1607 | | - |
1608 | | - function getNextSibling() { |
1609 | | - return $this->nextSibling; |
1610 | | - } |
1611 | | - |
1612 | | - function getChildren() { return false; } |
1613 | | - function getFirstChild() { return false; } |
1614 | | - function getChildrenOfType( $name ) { return false; } |
1615 | | - function getLength() { return false; } |
1616 | | - function item( $i ) { return false; } |
1617 | | - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } |
1618 | | - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } |
1619 | | - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } |
1620 | | -} |
Index: branches/parser-work/phase3/includes/parser/Preprocessor_DOM.php |
— | — | @@ -1,899 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * @ingroup Parser |
6 | | - */ |
7 | | -class Preprocessor_DOM implements Preprocessor { |
8 | | - var $parser, $memoryLimit; |
9 | | - |
10 | | - const CACHE_VERSION = 1; |
11 | | - |
12 | | - function __construct( $parser ) { |
13 | | - $this->parser = $parser; |
14 | | - $mem = ini_get( 'memory_limit' ); |
15 | | - $this->memoryLimit = false; |
16 | | - if ( strval( $mem ) !== '' && $mem != -1 ) { |
17 | | - if ( preg_match( '/^\d+$/', $mem ) ) { |
18 | | - $this->memoryLimit = $mem; |
19 | | - } elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) { |
20 | | - $this->memoryLimit = $m[1] * 1048576; |
21 | | - } |
22 | | - } |
23 | | - } |
24 | | - |
25 | | - function newFrame() { |
26 | | - return new PPFrame_DOM( $this ); |
27 | | - } |
28 | | - |
29 | | - function newCustomFrame( $args ) { |
30 | | - return new PPCustomFrame_DOM( $this, $args ); |
31 | | - } |
32 | | - |
33 | | - function memCheck() { |
34 | | - if ( $this->memoryLimit === false ) { |
35 | | - return; |
36 | | - } |
37 | | - $usage = memory_get_usage(); |
38 | | - if ( $usage > $this->memoryLimit * 0.9 ) { |
39 | | - $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 ); |
40 | | - throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" ); |
41 | | - } |
42 | | - return $usage <= $this->memoryLimit * 0.8; |
43 | | - } |
44 | | - |
45 | | - /** |
46 | | - * Preprocess some wikitext and return the document tree. |
47 | | - * This is the ghost of Parser::replace_variables(). |
48 | | - * |
49 | | - * @param string $text The text to parse |
50 | | - * @param integer flags Bitwise combination of: |
51 | | - * Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being |
52 | | - * included. Default is to assume a direct page view. |
53 | | - * |
54 | | - * The generated DOM tree must depend only on the input text and the flags. |
55 | | - * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. |
56 | | - * |
57 | | - * Any flag added to the $flags parameter here, or any other parameter liable to cause a |
58 | | - * change in the DOM tree for a given text, must be passed through the section identifier |
59 | | - * in the section edit link and thus back to extractSections(). |
60 | | - * |
61 | | - * The output of this function is currently only cached in process memory, but a persistent |
62 | | - * cache may be implemented at a later date which takes further advantage of these strict |
63 | | - * dependency requirements. |
64 | | - * |
65 | | - * @private |
66 | | - */ |
67 | | - function preprocessToObj( $text, $flags = 0 ) { |
68 | | - wfProfileIn( __METHOD__ ); |
69 | | - global $wgMemc, $wgPreprocessorCacheThreshold; |
70 | | - |
71 | | - $xml = false; |
72 | | - $cacheable = strlen( $text ) > $wgPreprocessorCacheThreshold; |
73 | | - if ( $cacheable ) { |
74 | | - wfProfileIn( __METHOD__.'-cacheable' ); |
75 | | - |
76 | | - $cacheKey = wfMemcKey( 'preprocess-xml', md5($text), $flags ); |
77 | | - $cacheValue = $wgMemc->get( $cacheKey ); |
78 | | - if ( $cacheValue ) { |
79 | | - $version = substr( $cacheValue, 0, 8 ); |
80 | | - if ( intval( $version ) == self::CACHE_VERSION ) { |
81 | | - $xml = substr( $cacheValue, 8 ); |
82 | | - // From the cache |
83 | | - wfDebugLog( "Preprocessor", "Loaded preprocessor XML from memcached (key $cacheKey)" ); |
84 | | - } |
85 | | - } |
86 | | - } |
87 | | - $dom = false; |
88 | | - if ( $xml === false ) { |
89 | | - if ( $cacheable ) { |
90 | | - wfProfileIn( __METHOD__.'-cache-miss' ); |
91 | | - } |
92 | | - $dom = $this->preprocessToDom( $text, $flags ); |
93 | | - if ( $cacheable ) { |
94 | | - $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . $dom->saveXML(); |
95 | | - $wgMemc->set( $cacheKey, $cacheValue, 86400 ); |
96 | | - wfProfileOut( __METHOD__.'-cache-miss' ); |
97 | | - wfDebugLog( "Preprocessor", "Saved preprocessor XML to memcached (key $cacheKey)" ); |
98 | | - } |
99 | | - } else { |
100 | | - wfProfileIn( __METHOD__.'-loadXML' ); |
101 | | - $dom = new DOMDocument; |
102 | | - wfSuppressWarnings(); |
103 | | - $result = $dom->loadXML( $xml ); |
104 | | - wfRestoreWarnings(); |
105 | | - if ( !$result ) { |
106 | | - // Try running the XML through UtfNormal to get rid of invalid characters |
107 | | - $xml = UtfNormal::cleanUp( $xml ); |
108 | | - $result = $dom->loadXML( $xml ); |
109 | | - if ( !$result ) { |
110 | | - throw new MWException( __METHOD__.' generated invalid XML' ); |
111 | | - } |
112 | | - } |
113 | | - wfProfileOut( __METHOD__.'-loadXML' ); |
114 | | - } |
115 | | - $obj = new PPNode_DOM( $dom->documentElement ); |
116 | | - if ( $cacheable ) { |
117 | | - wfProfileOut( __METHOD__.'-cacheable' ); |
118 | | - } |
119 | | - wfProfileOut( __METHOD__ ); |
120 | | - return $obj; |
121 | | - } |
122 | | - |
123 | | - // Set up parser data for wikitext then feed the given text to the parser |
124 | | - private function preprocessToDom(&$text, $flags = 0) { |
125 | | - wfProfileIn( __METHOD__ ); |
126 | | - |
127 | | - $xmlishRegex = implode('|', $this->parser->getStripList()); |
128 | | - $rules = array( |
129 | | - "Root" => new ParseAssign("root", new ParseSeq(array("BOFQuant", "MainQuant"), '$')), |
130 | | - "BOFQuant" => new ParseQuant("HeadingChoice", 0, 1), |
131 | | - "DefaultPat" => new ParsePattern('/^~r/s'), |
132 | | - "SavedPat" => new ParsePattern('/^(~r)/s'), |
133 | | - "MainQuant" => new ParseQuant(new ParseChoice( |
134 | | - new ParseChoice( |
135 | | - new ParseAssign("template", new ParseSeq(array(new ParsePattern('/^{{(?!{[^{])/s'), "TemplateSeq"), '}}')), |
136 | | - new ParseAssign("tplarg", new ParseSeq(array(new ParsePattern('/^{{{/s'), "TemplateSeq"), '}}}'))), |
137 | | - new ParseChoice( |
138 | | - new ParseAssign("comment", new ParsePattern('/^(<!--.*?(?:-->|$))/s')), |
139 | | - new ParseAssign("ignore", "OnlyIncludePat"), |
140 | | - new ParseAssign("ignore", "NoIncludePat"), |
141 | | - new ParseAssign("ignore", "IncludeOnlyPat"), |
142 | | - new ParseAssign("ext", new ParseSeq(array( |
143 | | - new ParsePattern('/^<(?=(' . $xmlishRegex . '))/si'), |
144 | | - new ParseAssign("name", "SavedPat"), |
145 | | - new ParseAssign("attr", new ParsePattern('/^(.*?)(?=\/>|>)/s')), |
146 | | - new ParseChoice(new ParsePattern('/^\/>/s'), new ParseSeq(array( |
147 | | - new ParsePattern('/^>/s'), |
148 | | - new ParseAssign("inner", new ParsePattern('/^(.*?)(?=<\/~r>|$)/si')), |
149 | | - new ParseQuant(new ParseAssign("close", new ParsePattern('/^(<\/~r>)/si')), 0, 1))))), NULL, TRUE))), |
150 | | - new ParseSeq(array(new ParsePattern('/^(\n)/s'), new ParseChoice("HeadingChoice", |
151 | | - new ParseAssign("comment", new ParsePattern('/^((?:<!--.*?-->\n)+)/s'))))), |
152 | | - new ParseSeq(array(new ParsePattern('/^(\[\[)/s'), "MainQuant", "SavedPat"), ']]'), |
153 | | - new ParsePattern('/^(?!~r)(.[^{}\[\]<\n|=]*)/s'))), |
154 | | - "OnlyIncludePat" => new ParsePattern('/^(<\/?onlyinclude>)/s'), |
155 | | - "NoIncludePat" => new ParsePattern('/^(<\/?noinclude>)/s'), |
156 | | - "IncludeOnlyPat" => new ParsePattern('/^(<includeonly>.*?<\/includeonly>)/s'), |
157 | | - "HeadingChoice" => new ParseChoice( |
158 | | - new ParseAssign("h", new ParseSeq(array("DefaultPat", "HeadingSeq"), '======'), "level", "6"), |
159 | | - new ParseAssign("h", new ParseSeq(array("DefaultPat", "HeadingSeq"), '====='), "level", "5"), |
160 | | - new ParseAssign("h", new ParseSeq(array("DefaultPat", "HeadingSeq"), '===='), "level", "4"), |
161 | | - new ParseAssign("h", new ParseSeq(array("DefaultPat", "HeadingSeq"), '==='), "level", "3"), |
162 | | - new ParseAssign("h", new ParseSeq(array("DefaultPat", "HeadingSeq"), '=='), "level", "2"), |
163 | | - new ParseAssign("h", new ParseSeq(array("DefaultPat", "HeadingSeq"), '='), "level", "1")), |
164 | | - "HeadingSeq" => new ParseSeq(array("MainQuant", "DefaultPat"), '~r'), |
165 | | - "TemplateSeq" => new ParseSeq(array(new ParseAssign("title", "MainQuant"), |
166 | | - new ParseQuant(new ParseAssign("part", new ParseSeq(array(new ParsePattern('/^\|/s'), new ParseChoice(new ParseSeq(array( |
167 | | - new ParseAssign("name", new ParseSeq(array("MainQuant", new ParsePattern('/^=/s')), '~r|\||=(?!~r|\|)')), |
168 | | - "TplValue")), "TplValue"))))), "DefaultPat"), '~r|\|'), |
169 | | - "TplValue" => new ParseAssign("value", "MainQuant")); |
170 | | - if ($flags & Parser::PTD_FOR_INCLUSION) { |
171 | | - $rules["BOFQuant"] = new ParseQuant(new ParseChoice(new ParseAssign("ignore", |
172 | | - new ParsePattern('/^(.*?<onlyinclude>)/s')), "HeadingChoice"), 0, 1); |
173 | | - $rules["OnlyIncludePat"] = new ParsePattern('/^(<\/onlyinclude>.*?(?:<onlyinclude>|$))/s'); |
174 | | - $rules["NoIncludePat"] = new ParsePattern('/^(<noinclude>.*?<\/noinclude>)/s'); |
175 | | - $rules["IncludeOnlyPat"] = new ParsePattern('/^(<\/?includeonly>)/s'); |
176 | | - } |
177 | | - $parser = new ParseEngine($rules, "Root"); |
178 | | - |
179 | | - $dom = $parser->parse($text); |
180 | | - |
181 | | - wfProfileOut( __METHOD__ ); |
182 | | - return $dom; |
183 | | - } |
184 | | -} |
185 | | - |
186 | | -/** |
187 | | - * An expansion frame, used as a context to expand the result of preprocessToObj() |
188 | | - * @ingroup Parser |
189 | | - */ |
190 | | -class PPFrame_DOM implements PPFrame { |
191 | | - var $preprocessor, $parser, $title; |
192 | | - var $titleCache; |
193 | | - |
194 | | - /** |
195 | | - * Hashtable listing templates which are disallowed for expansion in this frame, |
196 | | - * having been encountered previously in parent frames. |
197 | | - */ |
198 | | - var $loopCheckHash; |
199 | | - |
200 | | - /** |
201 | | - * Recursion depth of this frame, top = 0 |
202 | | - * Note that this is NOT the same as expansion depth in expand() |
203 | | - */ |
204 | | - var $depth; |
205 | | - |
206 | | - |
207 | | - /** |
208 | | - * Construct a new preprocessor frame. |
209 | | - * @param Preprocessor $preprocessor The parent preprocessor |
210 | | - */ |
211 | | - function __construct( $preprocessor ) { |
212 | | - $this->preprocessor = $preprocessor; |
213 | | - $this->parser = $preprocessor->parser; |
214 | | - $this->title = $this->parser->mTitle; |
215 | | - $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); |
216 | | - $this->loopCheckHash = array(); |
217 | | - $this->depth = 0; |
218 | | - } |
219 | | - |
220 | | - /** |
221 | | - * Create a new child frame |
222 | | - * $args is optionally a multi-root PPNode or array containing the template arguments |
223 | | - */ |
224 | | - function newChild( $args = false, $title = false ) { |
225 | | - $namedArgs = array(); |
226 | | - $numberedArgs = array(); |
227 | | - if ( $title === false ) { |
228 | | - $title = $this->title; |
229 | | - } |
230 | | - if ( $args !== false ) { |
231 | | - $xpath = false; |
232 | | - if ( $args instanceof PPNode ) { |
233 | | - $args = $args->node; |
234 | | - } |
235 | | - foreach ( $args as $arg ) { |
236 | | - if ( !$xpath ) { |
237 | | - $xpath = new DOMXPath( $arg->ownerDocument ); |
238 | | - } |
239 | | - |
240 | | - $nameNodes = $xpath->query( 'name', $arg ); |
241 | | - $value = $xpath->query( 'value', $arg ); |
242 | | - if ( $nameNodes->item( 0 )->hasAttributes() ) { |
243 | | - // Numbered parameter |
244 | | - $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent; |
245 | | - $numberedArgs[$index] = $value->item( 0 ); |
246 | | - unset( $namedArgs[$index] ); |
247 | | - } else { |
248 | | - // Named parameter |
249 | | - $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) ); |
250 | | - $namedArgs[$name] = $value->item( 0 ); |
251 | | - unset( $numberedArgs[$name] ); |
252 | | - } |
253 | | - } |
254 | | - } |
255 | | - return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title ); |
256 | | - } |
257 | | - |
258 | | - function expand( $root, $flags = 0 ) { |
259 | | - static $expansionDepth = 0; |
260 | | - if ( is_string( $root ) ) { |
261 | | - return $root; |
262 | | - } |
263 | | - |
264 | | - if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount ) |
265 | | - { |
266 | | - return '<span class="error">Node-count limit exceeded</span>'; |
267 | | - } |
268 | | - |
269 | | - if ( $expansionDepth > $this->parser->mOptions->mMaxPPExpandDepth ) { |
270 | | - return '<span class="error">Expansion depth limit exceeded</span>'; |
271 | | - } |
272 | | - wfProfileIn( __METHOD__ ); |
273 | | - ++$expansionDepth; |
274 | | - |
275 | | - if ( $root instanceof PPNode_DOM ) { |
276 | | - $root = $root->node; |
277 | | - } |
278 | | - if ( $root instanceof DOMDocument ) { |
279 | | - $root = $root->documentElement; |
280 | | - } |
281 | | - |
282 | | - $outStack = array( '', '' ); |
283 | | - $iteratorStack = array( false, $root ); |
284 | | - $indexStack = array( 0, 0 ); |
285 | | - $headingIndex = 1; |
286 | | - |
287 | | - while ( count( $iteratorStack ) > 1 ) { |
288 | | - $level = count( $outStack ) - 1; |
289 | | - $iteratorNode =& $iteratorStack[ $level ]; |
290 | | - $out =& $outStack[$level]; |
291 | | - $index =& $indexStack[$level]; |
292 | | - |
293 | | - if ( $iteratorNode instanceof PPNode_DOM ) $iteratorNode = $iteratorNode->node; |
294 | | - |
295 | | - if ( is_array( $iteratorNode ) ) { |
296 | | - if ( $index >= count( $iteratorNode ) ) { |
297 | | - // All done with this iterator |
298 | | - $iteratorStack[$level] = false; |
299 | | - $contextNode = false; |
300 | | - } else { |
301 | | - $contextNode = $iteratorNode[$index]; |
302 | | - $index++; |
303 | | - } |
304 | | - } elseif ( $iteratorNode instanceof DOMNodeList ) { |
305 | | - if ( $index >= $iteratorNode->length ) { |
306 | | - // All done with this iterator |
307 | | - $iteratorStack[$level] = false; |
308 | | - $contextNode = false; |
309 | | - } else { |
310 | | - $contextNode = $iteratorNode->item( $index ); |
311 | | - $index++; |
312 | | - } |
313 | | - } else { |
314 | | - // Copy to $contextNode and then delete from iterator stack, |
315 | | - // because this is not an iterator but we do have to execute it once |
316 | | - $contextNode = $iteratorStack[$level]; |
317 | | - $iteratorStack[$level] = false; |
318 | | - } |
319 | | - |
320 | | - if ( $contextNode instanceof PPNode_DOM ) $contextNode = $contextNode->node; |
321 | | - |
322 | | - $newIterator = false; |
323 | | - |
324 | | - if ( $contextNode === false ) { |
325 | | - // nothing to do |
326 | | - } elseif ( is_string( $contextNode ) ) { |
327 | | - $out .= $contextNode; |
328 | | - } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) { |
329 | | - $newIterator = $contextNode; |
330 | | - } elseif ( $contextNode instanceof DOMNode ) { |
331 | | - if ( $contextNode->nodeType == XML_TEXT_NODE ) { |
332 | | - $out .= $contextNode->nodeValue; |
333 | | - } elseif ( $contextNode->nodeName == 'template' || $contextNode->nodeName == 'tplarg' ) { |
334 | | - # brace expansion |
335 | | - $xpath = new DOMXPath( $contextNode->ownerDocument ); |
336 | | - $titles = $xpath->query( 'title', $contextNode ); |
337 | | - $title = $titles->item( 0 ); |
338 | | - $parts = $xpath->query( 'part', $contextNode ); |
339 | | - $partInd = 1; |
340 | | - foreach ($parts as $part) { |
341 | | - if ($part->firstChild->nodeName != "name") { |
342 | | - $newNode = $contextNode->ownerDocument->createElement("name"); |
343 | | - $newNode->setAttribute("index", $partInd); |
344 | | - $partInd ++; |
345 | | - $part->insertBefore($newNode, $part->firstChild); |
346 | | - } else { |
347 | | - $newNode = $contextNode->ownerDocument->createTextNode("="); |
348 | | - $part->insertBefore($newNode, $part->lastChild); |
349 | | - } |
350 | | - } |
351 | | - if ( $flags & self::NO_ARGS ) { |
352 | | - if ( $contextNode->nodeName == 'template' ) { |
353 | | - $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts ); |
354 | | - } else { |
355 | | - $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts ); |
356 | | - } |
357 | | - } else { |
358 | | - $params = array( |
359 | | - 'title' => new PPNode_DOM( $title ), |
360 | | - 'parts' => new PPNode_DOM( $parts ) ); |
361 | | - if ( $contextNode->nodeName == 'template' ) { |
362 | | - $preText = $contextNode->previousSibling->wholeText; |
363 | | - $params['lineStart'] = $preText[strlen($preText) - 1] == "\n"; |
364 | | - $ret = $this->parser->braceSubstitution( $params, $this ); |
365 | | - } else { |
366 | | - $ret = $this->parser->argSubstitution( $params, $this ); |
367 | | - } |
368 | | - if ( isset( $ret['object'] ) ) { |
369 | | - $newIterator = $ret['object']; |
370 | | - } else { |
371 | | - $out .= $ret['text']; |
372 | | - } |
373 | | - } |
374 | | - } elseif ( $contextNode->nodeName == 'comment' ) { |
375 | | - # HTML-style comment |
376 | | - # Remove it in HTML, pre+remove and STRIP_COMMENTS modes |
377 | | - if ( $this->parser->ot['html'] |
378 | | - || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) |
379 | | - || ( $flags & self::STRIP_COMMENTS ) ) |
380 | | - { |
381 | | - $out .= ''; |
382 | | - } |
383 | | - # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result |
384 | | - # Not in RECOVER_COMMENTS mode (extractSections) though |
385 | | - elseif ( $this->parser->ot['wiki'] && ! ( $flags & self::RECOVER_COMMENTS ) ) { |
386 | | - $out .= $this->parser->insertStripItem( $contextNode->textContent ); |
387 | | - } |
388 | | - # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove |
389 | | - else { |
390 | | - $out .= $contextNode->textContent; |
391 | | - } |
392 | | - } elseif ( $contextNode->nodeName == 'ignore' ) { |
393 | | - # Output suppression used by <includeonly> etc. |
394 | | - # OT_WIKI will only respect <ignore> in substed templates. |
395 | | - # The other output types respect it unless NO_IGNORE is set. |
396 | | - # extractSections() sets NO_IGNORE and so never respects it. |
397 | | - if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) { |
398 | | - $out .= $contextNode->textContent; |
399 | | - } else { |
400 | | - $out .= ''; |
401 | | - } |
402 | | - } elseif ( $contextNode->nodeName == 'ext' ) { |
403 | | - # Extension tag |
404 | | - $xpath = new DOMXPath( $contextNode->ownerDocument ); |
405 | | - $names = $xpath->query( 'name', $contextNode ); |
406 | | - $attrs = $xpath->query( 'attr', $contextNode ); |
407 | | - $inners = $xpath->query( 'inner', $contextNode ); |
408 | | - $closes = $xpath->query( 'close', $contextNode ); |
409 | | - $params = array( |
410 | | - 'name' => new PPNode_DOM( $names->item( 0 ) ), |
411 | | - 'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null, |
412 | | - 'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null, |
413 | | - 'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null, |
414 | | - ); |
415 | | - $out .= $this->parser->extensionSubstitution( $params, $this ); |
416 | | - } elseif ( $contextNode->nodeName == 'h' ) { |
417 | | - # Heading |
418 | | - $s = $this->expand( $contextNode->childNodes, $flags ); |
419 | | - |
420 | | - # Insert a heading marker only for <h> children of <root> |
421 | | - # This is to stop extractSections from going over multiple tree levels |
422 | | - if ( $contextNode->parentNode->nodeName == 'root' |
423 | | - && $this->parser->ot['html'] ) |
424 | | - { |
425 | | - # Insert heading index marker |
426 | | - $titleText = $this->title->getPrefixedDBkey(); |
427 | | - $this->parser->mHeadings[] = array( $titleText, $headingIndex ); |
428 | | - $serial = count( $this->parser->mHeadings ) - 1; |
429 | | - $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX; |
430 | | - $count = $contextNode->getAttribute( 'level' ); |
431 | | - $s = $marker . $s; |
432 | | - $this->parser->mStripState->general->setPair( $marker, '' ); |
433 | | - } |
434 | | - $headerTag = str_repeat("=", $contextNode->getAttribute("level")); |
435 | | - $out .= $headerTag . $s . $headerTag; |
436 | | - $headingIndex ++; |
437 | | - $nextNode = $contextNode->nextSibling; |
438 | | - if ($nextNode != NULL && ! ($nextNode instanceof DOMText && $nextNode->substringData(0, 1) == "\n")) { |
439 | | - $out .= "\n"; |
440 | | - } |
441 | | - } else { |
442 | | - # Generic recursive expansion |
443 | | - $newIterator = $contextNode->childNodes; |
444 | | - } |
445 | | - } else { |
446 | | - wfProfileOut( __METHOD__ ); |
447 | | - throw new MWException( __METHOD__.': Invalid parameter type' ); |
448 | | - } |
449 | | - |
450 | | - if ( $newIterator !== false ) { |
451 | | - if ( $newIterator instanceof PPNode_DOM ) { |
452 | | - $newIterator = $newIterator->node; |
453 | | - } |
454 | | - $outStack[] = ''; |
455 | | - $iteratorStack[] = $newIterator; |
456 | | - $indexStack[] = 0; |
457 | | - } elseif ( $iteratorStack[$level] === false ) { |
458 | | - // Return accumulated value to parent |
459 | | - // With tail recursion |
460 | | - while ( $iteratorStack[$level] === false && $level > 0 ) { |
461 | | - $outStack[$level - 1] .= $out; |
462 | | - array_pop( $outStack ); |
463 | | - array_pop( $iteratorStack ); |
464 | | - array_pop( $indexStack ); |
465 | | - $level--; |
466 | | - } |
467 | | - } |
468 | | - } |
469 | | - --$expansionDepth; |
470 | | - wfProfileOut( __METHOD__ ); |
471 | | - return $outStack[0]; |
472 | | - } |
473 | | - |
474 | | - function implodeWithFlags( $sep, $flags /*, ... */ ) { |
475 | | - $args = array_slice( func_get_args(), 2 ); |
476 | | - |
477 | | - $first = true; |
478 | | - $s = ''; |
479 | | - foreach ( $args as $root ) { |
480 | | - if ( $root instanceof PPNode_DOM ) $root = $root->node; |
481 | | - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
482 | | - $root = array( $root ); |
483 | | - } |
484 | | - foreach ( $root as $node ) { |
485 | | - if ( $first ) { |
486 | | - $first = false; |
487 | | - } else { |
488 | | - $s .= $sep; |
489 | | - } |
490 | | - $s .= $this->expand( $node, $flags ); |
491 | | - } |
492 | | - } |
493 | | - return $s; |
494 | | - } |
495 | | - |
496 | | - /** |
497 | | - * Implode with no flags specified |
498 | | - * This previously called implodeWithFlags but has now been inlined to reduce stack depth |
499 | | - */ |
500 | | - function implode( $sep /*, ... */ ) { |
501 | | - $args = array_slice( func_get_args(), 1 ); |
502 | | - |
503 | | - $first = true; |
504 | | - $s = ''; |
505 | | - foreach ( $args as $root ) { |
506 | | - if ( $root instanceof PPNode_DOM ) $root = $root->node; |
507 | | - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
508 | | - $root = array( $root ); |
509 | | - } |
510 | | - foreach ( $root as $node ) { |
511 | | - if ( $first ) { |
512 | | - $first = false; |
513 | | - } else { |
514 | | - $s .= $sep; |
515 | | - } |
516 | | - $s .= $this->expand( $node ); |
517 | | - } |
518 | | - } |
519 | | - return $s; |
520 | | - } |
521 | | - |
522 | | - /** |
523 | | - * Makes an object that, when expand()ed, will be the same as one obtained |
524 | | - * with implode() |
525 | | - */ |
526 | | - function virtualImplode( $sep /*, ... */ ) { |
527 | | - $args = array_slice( func_get_args(), 1 ); |
528 | | - $out = array(); |
529 | | - $first = true; |
530 | | - if ( $root instanceof PPNode_DOM ) $root = $root->node; |
531 | | - |
532 | | - foreach ( $args as $root ) { |
533 | | - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
534 | | - $root = array( $root ); |
535 | | - } |
536 | | - foreach ( $root as $node ) { |
537 | | - if ( $first ) { |
538 | | - $first = false; |
539 | | - } else { |
540 | | - $out[] = $sep; |
541 | | - } |
542 | | - $out[] = $node; |
543 | | - } |
544 | | - } |
545 | | - return $out; |
546 | | - } |
547 | | - |
548 | | - /** |
549 | | - * Virtual implode with brackets |
550 | | - */ |
551 | | - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { |
552 | | - $args = array_slice( func_get_args(), 3 ); |
553 | | - $out = array( $start ); |
554 | | - $first = true; |
555 | | - |
556 | | - foreach ( $args as $root ) { |
557 | | - if ( $root instanceof PPNode_DOM ) $root = $root->node; |
558 | | - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { |
559 | | - $root = array( $root ); |
560 | | - } |
561 | | - foreach ( $root as $node ) { |
562 | | - if ( $first ) { |
563 | | - $first = false; |
564 | | - } else { |
565 | | - $out[] = $sep; |
566 | | - } |
567 | | - $out[] = $node; |
568 | | - } |
569 | | - } |
570 | | - $out[] = $end; |
571 | | - return $out; |
572 | | - } |
573 | | - |
574 | | - function __toString() { |
575 | | - return 'frame{}'; |
576 | | - } |
577 | | - |
578 | | - function getPDBK( $level = false ) { |
579 | | - if ( $level === false ) { |
580 | | - return $this->title->getPrefixedDBkey(); |
581 | | - } else { |
582 | | - return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false; |
583 | | - } |
584 | | - } |
585 | | - |
586 | | - function getArguments() { |
587 | | - return array(); |
588 | | - } |
589 | | - |
590 | | - function getNumberedArguments() { |
591 | | - return array(); |
592 | | - } |
593 | | - |
594 | | - function getNamedArguments() { |
595 | | - return array(); |
596 | | - } |
597 | | - |
598 | | - /** |
599 | | - * Returns true if there are no arguments in this frame |
600 | | - */ |
601 | | - function isEmpty() { |
602 | | - return true; |
603 | | - } |
604 | | - |
605 | | - function getArgument( $name ) { |
606 | | - return false; |
607 | | - } |
608 | | - |
609 | | - /** |
610 | | - * Returns true if the infinite loop check is OK, false if a loop is detected |
611 | | - */ |
612 | | - function loopCheck( $title ) { |
613 | | - return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); |
614 | | - } |
615 | | - |
616 | | - /** |
617 | | - * Return true if the frame is a template frame |
618 | | - */ |
619 | | - function isTemplate() { |
620 | | - return false; |
621 | | - } |
622 | | -} |
623 | | - |
624 | | -/** |
625 | | - * Expansion frame with template arguments |
626 | | - * @ingroup Parser |
627 | | - */ |
628 | | -class PPTemplateFrame_DOM extends PPFrame_DOM { |
629 | | - var $numberedArgs, $namedArgs, $parent; |
630 | | - var $numberedExpansionCache, $namedExpansionCache; |
631 | | - |
632 | | - function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) { |
633 | | - PPFrame_DOM::__construct( $preprocessor ); |
634 | | - $this->parent = $parent; |
635 | | - $this->numberedArgs = $numberedArgs; |
636 | | - $this->namedArgs = $namedArgs; |
637 | | - $this->title = $title; |
638 | | - $pdbk = $title ? $title->getPrefixedDBkey() : false; |
639 | | - $this->titleCache = $parent->titleCache; |
640 | | - $this->titleCache[] = $pdbk; |
641 | | - $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; |
642 | | - if ( $pdbk !== false ) { |
643 | | - $this->loopCheckHash[$pdbk] = true; |
644 | | - } |
645 | | - $this->depth = $parent->depth + 1; |
646 | | - $this->numberedExpansionCache = $this->namedExpansionCache = array(); |
647 | | - } |
648 | | - |
649 | | - function __toString() { |
650 | | - $s = 'tplframe{'; |
651 | | - $first = true; |
652 | | - $args = $this->numberedArgs + $this->namedArgs; |
653 | | - foreach ( $args as $name => $value ) { |
654 | | - if ( $first ) { |
655 | | - $first = false; |
656 | | - } else { |
657 | | - $s .= ', '; |
658 | | - } |
659 | | - $s .= "\"$name\":\"" . |
660 | | - str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"'; |
661 | | - } |
662 | | - $s .= '}'; |
663 | | - return $s; |
664 | | - } |
665 | | - /** |
666 | | - * Returns true if there are no arguments in this frame |
667 | | - */ |
668 | | - function isEmpty() { |
669 | | - return !count( $this->numberedArgs ) && !count( $this->namedArgs ); |
670 | | - } |
671 | | - |
672 | | - function getArguments() { |
673 | | - $arguments = array(); |
674 | | - foreach ( array_merge( |
675 | | - array_keys($this->numberedArgs), |
676 | | - array_keys($this->namedArgs)) as $key ) { |
677 | | - $arguments[$key] = $this->getArgument($key); |
678 | | - } |
679 | | - return $arguments; |
680 | | - } |
681 | | - |
682 | | - function getNumberedArguments() { |
683 | | - $arguments = array(); |
684 | | - foreach ( array_keys($this->numberedArgs) as $key ) { |
685 | | - $arguments[$key] = $this->getArgument($key); |
686 | | - } |
687 | | - return $arguments; |
688 | | - } |
689 | | - |
690 | | - function getNamedArguments() { |
691 | | - $arguments = array(); |
692 | | - foreach ( array_keys($this->namedArgs) as $key ) { |
693 | | - $arguments[$key] = $this->getArgument($key); |
694 | | - } |
695 | | - return $arguments; |
696 | | - } |
697 | | - |
698 | | - function getNumberedArgument( $index ) { |
699 | | - if ( !isset( $this->numberedArgs[$index] ) ) { |
700 | | - return false; |
701 | | - } |
702 | | - if ( !isset( $this->numberedExpansionCache[$index] ) ) { |
703 | | - # No trimming for unnamed arguments |
704 | | - $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], self::STRIP_COMMENTS ); |
705 | | - } |
706 | | - return $this->numberedExpansionCache[$index]; |
707 | | - } |
708 | | - |
709 | | - function getNamedArgument( $name ) { |
710 | | - if ( !isset( $this->namedArgs[$name] ) ) { |
711 | | - return false; |
712 | | - } |
713 | | - if ( !isset( $this->namedExpansionCache[$name] ) ) { |
714 | | - # Trim named arguments post-expand, for backwards compatibility |
715 | | - $this->namedExpansionCache[$name] = trim( |
716 | | - $this->parent->expand( $this->namedArgs[$name], self::STRIP_COMMENTS ) ); |
717 | | - } |
718 | | - return $this->namedExpansionCache[$name]; |
719 | | - } |
720 | | - |
721 | | - function getArgument( $name ) { |
722 | | - $text = $this->getNumberedArgument( $name ); |
723 | | - if ( $text === false ) { |
724 | | - $text = $this->getNamedArgument( $name ); |
725 | | - } |
726 | | - return $text; |
727 | | - } |
728 | | - |
729 | | - /** |
730 | | - * Return true if the frame is a template frame |
731 | | - */ |
732 | | - function isTemplate() { |
733 | | - return true; |
734 | | - } |
735 | | -} |
736 | | - |
737 | | -/** |
738 | | - * Expansion frame with custom arguments |
739 | | - * @ingroup Parser |
740 | | - */ |
741 | | -class PPCustomFrame_DOM extends PPFrame_DOM { |
742 | | - var $args; |
743 | | - |
744 | | - function __construct( $preprocessor, $args ) { |
745 | | - PPFrame_DOM::__construct( $preprocessor ); |
746 | | - $this->args = $args; |
747 | | - } |
748 | | - |
749 | | - function __toString() { |
750 | | - $s = 'cstmframe{'; |
751 | | - $first = true; |
752 | | - foreach ( $this->args as $name => $value ) { |
753 | | - if ( $first ) { |
754 | | - $first = false; |
755 | | - } else { |
756 | | - $s .= ', '; |
757 | | - } |
758 | | - $s .= "\"$name\":\"" . |
759 | | - str_replace( '"', '\\"', $value->__toString() ) . '"'; |
760 | | - } |
761 | | - $s .= '}'; |
762 | | - return $s; |
763 | | - } |
764 | | - |
765 | | - function isEmpty() { |
766 | | - return !count( $this->args ); |
767 | | - } |
768 | | - |
769 | | - function getArgument( $index ) { |
770 | | - if ( !isset( $this->args[$index] ) ) { |
771 | | - return false; |
772 | | - } |
773 | | - return $this->args[$index]; |
774 | | - } |
775 | | -} |
776 | | - |
777 | | -/** |
778 | | - * @ingroup Parser |
779 | | - */ |
780 | | -class PPNode_DOM implements PPNode { |
781 | | - var $node; |
782 | | - |
783 | | - function __construct( $node, $xpath = false ) { |
784 | | - $this->node = $node; |
785 | | - } |
786 | | - |
787 | | - function __get( $name ) { |
788 | | - if ( $name == 'xpath' ) { |
789 | | - $this->xpath = new DOMXPath( $this->node->ownerDocument ); |
790 | | - } |
791 | | - return $this->xpath; |
792 | | - } |
793 | | - |
794 | | - function __toString() { |
795 | | - if ( $this->node instanceof DOMNodeList ) { |
796 | | - $s = ''; |
797 | | - foreach ( $this->node as $node ) { |
798 | | - $s .= $node->ownerDocument->saveXML( $node ); |
799 | | - } |
800 | | - } else { |
801 | | - $s = $this->node->ownerDocument->saveXML( $this->node ); |
802 | | - } |
803 | | - return $s; |
804 | | - } |
805 | | - |
806 | | - function getChildren() { |
807 | | - return $this->node->childNodes ? new self( $this->node->childNodes ) : false; |
808 | | - } |
809 | | - |
810 | | - function getFirstChild() { |
811 | | - return $this->node->firstChild ? new self( $this->node->firstChild ) : false; |
812 | | - } |
813 | | - |
814 | | - function getNextSibling() { |
815 | | - return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false; |
816 | | - } |
817 | | - |
818 | | - function getChildrenOfType( $type ) { |
819 | | - return new self( $this->xpath->query( $type, $this->node ) ); |
820 | | - } |
821 | | - |
822 | | - function getLength() { |
823 | | - if ( $this->node instanceof DOMNodeList ) { |
824 | | - return $this->node->length; |
825 | | - } else { |
826 | | - return false; |
827 | | - } |
828 | | - } |
829 | | - |
830 | | - function item( $i ) { |
831 | | - $item = $this->node->item( $i ); |
832 | | - return $item ? new self( $item ) : false; |
833 | | - } |
834 | | - |
835 | | - function getName() { |
836 | | - if ( $this->node instanceof DOMNodeList ) { |
837 | | - return '#nodelist'; |
838 | | - } else { |
839 | | - return $this->node->nodeName; |
840 | | - } |
841 | | - } |
842 | | - |
843 | | - /** |
844 | | - * Split a <part> node into an associative array containing: |
845 | | - * name PPNode name |
846 | | - * index String index |
847 | | - * value PPNode value |
848 | | - */ |
849 | | - function splitArg() { |
850 | | - $names = $this->xpath->query( 'name', $this->node ); |
851 | | - $values = $this->xpath->query( 'value', $this->node ); |
852 | | - if ( !$names->length || !$values->length ) { |
853 | | - throw new MWException( 'Invalid brace node passed to ' . __METHOD__ ); |
854 | | - } |
855 | | - $name = $names->item( 0 ); |
856 | | - $index = $name->getAttribute( 'index' ); |
857 | | - return array( |
858 | | - 'name' => new self( $name ), |
859 | | - 'index' => $index, |
860 | | - 'value' => new self( $values->item( 0 ) ) ); |
861 | | - } |
862 | | - |
863 | | - /** |
864 | | - * Split an <ext> node into an associative array containing name, attr, inner and close |
865 | | - * All values in the resulting array are PPNodes. Inner and close are optional. |
866 | | - */ |
867 | | - function splitExt() { |
868 | | - $names = $this->xpath->query( 'name', $this->node ); |
869 | | - $attrs = $this->xpath->query( 'attr', $this->node ); |
870 | | - $inners = $this->xpath->query( 'inner', $this->node ); |
871 | | - $closes = $this->xpath->query( 'close', $this->node ); |
872 | | - if ( !$names->length || !$attrs->length ) { |
873 | | - throw new MWException( 'Invalid ext node passed to ' . __METHOD__ ); |
874 | | - } |
875 | | - $parts = array( |
876 | | - 'name' => new self( $names->item( 0 ) ), |
877 | | - 'attr' => new self( $attrs->item( 0 ) ) ); |
878 | | - if ( $inners->length ) { |
879 | | - $parts['inner'] = new self( $inners->item( 0 ) ); |
880 | | - } |
881 | | - if ( $closes->length ) { |
882 | | - $parts['close'] = new self( $closes->item( 0 ) ); |
883 | | - } |
884 | | - return $parts; |
885 | | - } |
886 | | - |
887 | | - /** |
888 | | - * Split a <h> node |
889 | | - */ |
890 | | - function splitHeading() { |
891 | | - if ( !$this->nodeName == 'h' ) { |
892 | | - throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); |
893 | | - } |
894 | | - return array( |
895 | | - 'i' => $this->node->getAttribute( 'i' ), |
896 | | - 'level' => $this->node->getAttribute( 'level' ), |
897 | | - 'contents' => $this->getChildren() |
898 | | - ); |
899 | | - } |
900 | | -} |