r64370 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r64369‎ | r64370 | r64371 >
Date:00:06, 30 March 2010
Author:than4213
Status:deferred (Comments)
Tags:
Comment:
This should have been checked in with change #64369.
Modified paths:
  • /branches/parser-work/phase3/includes/parser/Preprocessor_DOM.php (deleted) (history)
  • /branches/parser-work/phase3/includes/parser/Preprocessor_Hash.php (deleted) (history)

Diff [purge]

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 -}

Comments

#Comment by Reedy (talk | contribs)   06:42, 30 March 2010

Just so you know, if you use r64369 it will link it, and show the link on the other revision too :)

Status & tagging log