Index: trunk/phase3/maintenance/preprocessorFuzzTest.php |
— | — | @@ -6,7 +6,7 @@ |
7 | 7 | |
8 | 8 | class PPFuzzTester { |
9 | 9 | var $hairs = array( |
10 | | - '[[', ']]', '{{', '}}', '{{{', '}}}', |
| 10 | + '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}', |
11 | 11 | '<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>', |
12 | 12 | '<!--' , '-->', |
13 | 13 | "\n==", "==\n", |
— | — | @@ -23,6 +23,7 @@ |
24 | 24 | var $maxTemplates = 5; |
25 | 25 | //var $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' ); |
26 | 26 | var $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' ); |
| 27 | + var $verbose = false; |
27 | 28 | static $currentTest = false; |
28 | 29 | |
29 | 30 | function execute() { |
— | — | @@ -33,10 +34,14 @@ |
34 | 35 | echo "Unable to create 'results' directory\n"; |
35 | 36 | exit( 1 ); |
36 | 37 | } |
37 | | - for ( $i = 0; true; $i++ ) { |
| 38 | + $overallStart = microtime( true ); |
| 39 | + $reportInterval = 1000; |
| 40 | + for ( $i = 1; true; $i++ ) { |
| 41 | + $t = -microtime( true ); |
38 | 42 | try { |
39 | 43 | self::$currentTest = new PPFuzzTest( $this ); |
40 | 44 | self::$currentTest->execute(); |
| 45 | + $passed = 'passed'; |
41 | 46 | } catch ( MWException $e ) { |
42 | 47 | $testReport = self::$currentTest->getReport(); |
43 | 48 | $exceptionReport = $e->getText(); |
— | — | @@ -45,8 +50,30 @@ |
46 | 51 | file_put_contents( "results/ppft-$hash.fail", |
47 | 52 | "Input:\n$testReport\n\nException report:\n$exceptionReport\n" ); |
48 | 53 | print "Test $hash failed\n"; |
| 54 | + $passed = 'failed'; |
49 | 55 | } |
50 | | - if ( $i % 1000 == 0 ) { |
| 56 | + $t += microtime( true ); |
| 57 | + |
| 58 | + if ( $this->verbose ) { |
| 59 | + printf( "Test $passed in %.3f seconds\n", $t ); |
| 60 | + print self::$currentTest->getReport(); |
| 61 | + } |
| 62 | + |
| 63 | + $reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval; |
| 64 | + if ( $reportMetric > 25 ) { |
| 65 | + if ( substr( $reportInterval, 0, 1 ) === '1' ) { |
| 66 | + $reportInterval /= 2; |
| 67 | + } else { |
| 68 | + $reportInterval /= 5; |
| 69 | + } |
| 70 | + } elseif ( $reportMetric < 4 ) { |
| 71 | + if ( substr( $reportInterval, 0, 1 ) === '1' ) { |
| 72 | + $reportInterval *= 5; |
| 73 | + } else { |
| 74 | + $reportInterval *= 2; |
| 75 | + } |
| 76 | + } |
| 77 | + if ( $i % $reportInterval == 0 ) { |
51 | 78 | print "$i tests done\n"; |
52 | 79 | /* |
53 | 80 | $testReport = self::$currentTest->getReport(); |
— | — | @@ -54,10 +81,14 @@ |
55 | 82 | file_put_contents( $filename, "Input:\n$testReport\n" );*/ |
56 | 83 | } |
57 | 84 | } |
| 85 | + wfLogProfilingData(); |
58 | 86 | } |
59 | 87 | |
60 | | - function makeInputText() { |
61 | | - $length = mt_rand( $this->minLength, $this->maxLength ); |
| 88 | + function makeInputText( $max = false ) { |
| 89 | + if ( $max === false ) { |
| 90 | + $max = $this->maxLength; |
| 91 | + } |
| 92 | + $length = mt_rand( $this->minLength, $max ); |
62 | 93 | $s = ''; |
63 | 94 | for ( $i = 0; $i < $length; $i++ ) { |
64 | 95 | $hairIndex = mt_rand( 0, count( $this->hairs ) - 1 ); |
— | — | @@ -88,15 +119,16 @@ |
89 | 120 | } |
90 | 121 | |
91 | 122 | class PPFuzzTest { |
92 | | - var $templates, $mainText, $title, $entryPoint; |
| 123 | + var $templates, $mainText, $title, $entryPoint, $output; |
93 | 124 | |
94 | 125 | function __construct( $tester ) { |
| 126 | + global $wgMaxSigChars; |
95 | 127 | $this->parent = $tester; |
96 | 128 | $this->mainText = $tester->makeInputText(); |
97 | 129 | $this->title = $tester->makeTitle(); |
98 | 130 | //$this->outputType = $tester->pickOutputType(); |
99 | 131 | $this->entryPoint = $tester->pickEntryPoint(); |
100 | | - $this->nickname = $tester->makeInputText(); |
| 132 | + $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10); |
101 | 133 | $this->fancySig = (bool)mt_rand( 0, 1 ); |
102 | 134 | $this->templates = array(); |
103 | 135 | } |
— | — | @@ -138,8 +170,9 @@ |
139 | 171 | |
140 | 172 | $options = new ParserOptions; |
141 | 173 | $options->setTemplateCallback( array( $this, 'templateHook' ) ); |
142 | | - //$wgParser->startExternalParse( $this->title, $options, constant( $this->outputType ) ); |
143 | | - return call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title, $options ); |
| 174 | + $options->setTimestamp( wfTimestampNow() ); |
| 175 | + $this->output = call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title->getPrefixedText(), $options ); |
| 176 | + return $this->output; |
144 | 177 | } |
145 | 178 | |
146 | 179 | function getReport() { |
— | — | @@ -156,6 +189,7 @@ |
157 | 190 | $s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n"; |
158 | 191 | } |
159 | 192 | } |
| 193 | + $s .= "Output: " . var_export( $this->output, true ) . "\n"; |
160 | 194 | return $s; |
161 | 195 | } |
162 | 196 | } |
— | — | @@ -163,6 +197,14 @@ |
164 | 198 | class PPFuzzUser extends User { |
165 | 199 | var $ppfz_test; |
166 | 200 | |
| 201 | + function load() { |
| 202 | + if ( $this->mDataLoaded ) { |
| 203 | + return; |
| 204 | + } |
| 205 | + $this->mDataLoaded = true; |
| 206 | + $this->loadDefaults( $this->mName ); |
| 207 | + } |
| 208 | + |
167 | 209 | function getOption( $option, $defaultOverride = '' ) { |
168 | 210 | if ( $option === 'fancysig' ) { |
169 | 211 | return $this->ppfz_test->fancySig; |
— | — | @@ -182,10 +224,10 @@ |
183 | 225 | exit( 1 ); |
184 | 226 | } |
185 | 227 | $test = unserialize( $testText ); |
186 | | - print $test->getReport(); |
187 | 228 | $result = $test->execute(); |
188 | | - print "Test passed.\nResult: $result\n"; |
| 229 | + print "Test passed.\n"; |
189 | 230 | } else { |
190 | 231 | $tester = new PPFuzzTester; |
| 232 | + $tester->verbose = isset( $options['verbose'] ); |
191 | 233 | $tester->execute(); |
192 | 234 | } |
Index: trunk/phase3/includes/Preprocessor_DOM.php |
— | — | @@ -1,16 +1,37 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | class Preprocessor_DOM implements Preprocessor { |
5 | | - var $parser; |
| 5 | + var $parser, $memoryLimit; |
6 | 6 | |
7 | 7 | function __construct( $parser ) { |
8 | 8 | $this->parser = $parser; |
| 9 | + $mem = ini_get( 'memory_limit' ); |
| 10 | + $this->memoryLimit = false; |
| 11 | + if ( strval( $mem ) !== '' && $mem != -1 ) { |
| 12 | + if ( preg_match( '/^\d+$/', $mem ) ) { |
| 13 | + $this->memoryLimit = $mem; |
| 14 | + } elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) { |
| 15 | + $this->memoryLimit = $m[1] * 1048576; |
| 16 | + } |
| 17 | + } |
9 | 18 | } |
10 | 19 | |
11 | 20 | function newFrame() { |
12 | 21 | return new PPFrame_DOM( $this ); |
13 | 22 | } |
14 | 23 | |
| 24 | + function memCheck() { |
| 25 | + if ( $this->memoryLimit === false ) { |
| 26 | + return; |
| 27 | + } |
| 28 | + $usage = memory_get_usage(); |
| 29 | + if ( $usage > $this->memoryLimit * 0.9 ) { |
| 30 | + $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 ); |
| 31 | + throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" ); |
| 32 | + } |
| 33 | + return $usage <= $this->memoryLimit * 0.8; |
| 34 | + } |
| 35 | + |
15 | 36 | /** |
16 | 37 | * Preprocess some wikitext and return the document tree. |
17 | 38 | * This is the ghost of Parser::replace_variables(). |
— | — | @@ -78,11 +99,11 @@ |
79 | 100 | |
80 | 101 | $stack = new PPDStack; |
81 | 102 | |
82 | | - $searchBase = '[{<'; |
| 103 | + $searchBase = '[{<'; #} |
83 | 104 | $revText = strrev( $text ); // For fast reverse searches |
84 | 105 | |
85 | 106 | $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start |
86 | | - $accum =& $stack->getAccum(); # Current text accumulator |
| 107 | + $accum =& $stack->getAccum(); # Current accumulator |
87 | 108 | $accum = '<root>'; |
88 | 109 | $findEquals = false; # True to find equals signs in arguments |
89 | 110 | $findPipe = false; # True to take notice of pipe characters |
— | — | @@ -93,6 +114,8 @@ |
94 | 115 | $fakeLineStart = true; # Do a line-start run without outputting an LF character |
95 | 116 | |
96 | 117 | while ( true ) { |
| 118 | + if ( ! ($i % 10) ) $this->memCheck(); |
| 119 | + |
97 | 120 | if ( $findOnlyinclude ) { |
98 | 121 | // Ignore all input up to the next <onlyinclude> |
99 | 122 | $startPos = strpos( $text, '<onlyinclude>', $i ); |
— | — | @@ -241,6 +264,17 @@ |
242 | 265 | $endPos += 2; |
243 | 266 | } |
244 | 267 | |
| 268 | + /* |
| 269 | + if ( $stack->top ) { |
| 270 | + if ( $stack->top->commentEndPos !== false && $stack->top->commentEndPos == $wsStart ) { |
| 271 | + // Comments abutting, no change in visual end |
| 272 | + $stack->top->commentEndPos = $wsEnd; |
| 273 | + } else { |
| 274 | + $stack->top->visualEndPos = $wsStart; |
| 275 | + $stack->top->commentEndPos = $wsEnd; |
| 276 | + } |
| 277 | + } |
| 278 | + */ |
245 | 279 | $i = $endPos + 1; |
246 | 280 | $inner = substr( $text, $startPos, $endPos - $startPos + 1 ); |
247 | 281 | $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>'; |
— | — | @@ -326,7 +360,7 @@ |
327 | 361 | $piece = array( |
328 | 362 | 'open' => "\n", |
329 | 363 | 'close' => "\n", |
330 | | - 'parts' => array( str_repeat( '=', $count ) ), |
| 364 | + 'parts' => array( new PPDPart( str_repeat( '=', $count ) ) ), |
331 | 365 | 'startPos' => $i, |
332 | 366 | 'count' => $count ); |
333 | 367 | $stack->push( $piece ); |
— | — | @@ -395,8 +429,6 @@ |
396 | 430 | 'open' => $curChar, |
397 | 431 | 'close' => $rule['end'], |
398 | 432 | 'count' => $count, |
399 | | - 'parts' => array( '' ), |
400 | | - 'eqpos' => array(), |
401 | 433 | 'lineStart' => ($i > 0 && $text[$i-1] == "\n"), |
402 | 434 | ); |
403 | 435 | |
— | — | @@ -444,14 +476,12 @@ |
445 | 477 | $name = $rule['names'][$matchingCount]; |
446 | 478 | if ( $name === null ) { |
447 | 479 | // No element, just literal text |
448 | | - $element = str_repeat( $piece->open, $matchingCount ) . |
449 | | - implode( '|', $piece->parts ) . |
450 | | - str_repeat( $rule['end'], $matchingCount ); |
| 480 | + $element = $piece->breakSyntax( $matchingCount ) . str_repeat( $rule['end'], $matchingCount ); |
451 | 481 | } else { |
452 | 482 | # Create XML element |
453 | 483 | # Note: $parts is already XML, does not need to be encoded further |
454 | 484 | $parts = $piece->parts; |
455 | | - $title = $parts[0]; |
| 485 | + $title = $parts[0]->out; |
456 | 486 | unset( $parts[0] ); |
457 | 487 | |
458 | 488 | # The invocation is at the start of the line if lineStart is set in |
— | — | @@ -466,13 +496,12 @@ |
467 | 497 | $element .= "<title>$title</title>"; |
468 | 498 | $argIndex = 1; |
469 | 499 | foreach ( $parts as $partIndex => $part ) { |
470 | | - if ( isset( $piece->eqpos[$partIndex] ) ) { |
471 | | - $eqpos = $piece->eqpos[$partIndex]; |
472 | | - $argName = substr( $part, 0, $eqpos ); |
473 | | - $argValue = substr( $part, $eqpos + 1 ); |
| 500 | + if ( isset( $part->eqpos ) ) { |
| 501 | + $argName = substr( $part->out, 0, $part->eqpos ); |
| 502 | + $argValue = substr( $part->out, $part->eqpos + 1 ); |
474 | 503 | $element .= "<part><name>$argName</name>=<value>$argValue</value></part>"; |
475 | 504 | } else { |
476 | | - $element .= "<part><name index=\"$argIndex\" /><value>$part</value></part>"; |
| 505 | + $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>"; |
477 | 506 | $argIndex++; |
478 | 507 | } |
479 | 508 | } |
— | — | @@ -488,9 +517,8 @@ |
489 | 518 | |
490 | 519 | # Re-add the old stack element if it still has unmatched opening characters remaining |
491 | 520 | if ($matchingCount < $piece->count) { |
492 | | - $piece->parts = array( '' ); |
| 521 | + $piece->parts = array( new PPDPart ); |
493 | 522 | $piece->count -= $matchingCount; |
494 | | - $piece->eqpos = array(); |
495 | 523 | # do we still qualify for any callback with remaining count? |
496 | 524 | $names = $rules[$piece->open]['names']; |
497 | 525 | $skippedBraces = 0; |
— | — | @@ -515,15 +543,14 @@ |
516 | 544 | |
517 | 545 | elseif ( $found == 'pipe' ) { |
518 | 546 | $findEquals = true; // shortcut for getFlags() |
519 | | - $stack->top->addPart(); |
| 547 | + $stack->addPart(); |
520 | 548 | $accum =& $stack->getAccum(); |
521 | 549 | ++$i; |
522 | 550 | } |
523 | 551 | |
524 | 552 | elseif ( $found == 'equals' ) { |
525 | 553 | $findEquals = false; // shortcut for getFlags() |
526 | | - $partsCount = count( $stack->top->parts ); |
527 | | - $stack->top->eqpos[$partsCount - 1] = strlen( $accum ); |
| 554 | + $stack->getCurrentPart()->eqpos = strlen( $accum ); |
528 | 555 | $accum .= '='; |
529 | 556 | ++$i; |
530 | 557 | } |
— | — | @@ -531,14 +558,10 @@ |
532 | 559 | |
533 | 560 | # Output any remaining unclosed brackets |
534 | 561 | foreach ( $stack->stack as $piece ) { |
535 | | - if ( $piece->open == "\n" ) { |
536 | | - $stack->topAccum .= $piece->parts[0]; |
537 | | - } else { |
538 | | - $stack->topAccum .= str_repeat( $piece->open, $piece->count ) . implode( '|', $piece->parts ); |
539 | | - } |
| 562 | + $stack->rootAccum .= $piece->breakSyntax(); |
540 | 563 | } |
541 | | - $stack->topAccum .= '</root>'; |
542 | | - $xml = $stack->topAccum; |
| 564 | + $stack->rootAccum .= '</root>'; |
| 565 | + $xml = $stack->rootAccum; |
543 | 566 | |
544 | 567 | wfProfileOut( __METHOD__.'-makexml' ); |
545 | 568 | wfProfileIn( __METHOD__.'-loadXML' ); |
— | — | @@ -562,6 +585,156 @@ |
563 | 586 | } |
564 | 587 | |
565 | 588 | /** |
| 589 | + * Stack class to help Preprocessor::preprocessToObj() |
| 590 | + */ |
| 591 | +class PPDStack { |
| 592 | + var $stack, $rootAccum, $top; |
| 593 | + var $out; |
| 594 | + static $false = false; |
| 595 | + |
| 596 | + function __construct() { |
| 597 | + $this->stack = array(); |
| 598 | + $this->top = false; |
| 599 | + $this->rootAccum = ''; |
| 600 | + $this->accum =& $this->rootAccum; |
| 601 | + } |
| 602 | + |
| 603 | + function count() { |
| 604 | + return count( $this->stack ); |
| 605 | + } |
| 606 | + |
| 607 | + function &getAccum() { |
| 608 | + return $this->accum; |
| 609 | + } |
| 610 | + |
| 611 | + function getCurrentPart() { |
| 612 | + if ( $this->top === false ) { |
| 613 | + return false; |
| 614 | + } else { |
| 615 | + return $this->top->getCurrentPart(); |
| 616 | + } |
| 617 | + } |
| 618 | + |
| 619 | + function push( $data ) { |
| 620 | + if ( $data instanceof PPDStackElement ) { |
| 621 | + $this->stack[] = $data; |
| 622 | + } else { |
| 623 | + $this->stack[] = new PPDStackElement( $data ); |
| 624 | + } |
| 625 | + $this->top = $this->stack[ count( $this->stack ) - 1 ]; |
| 626 | + $this->accum =& $this->top->getAccum(); |
| 627 | + } |
| 628 | + |
| 629 | + function pop() { |
| 630 | + if ( !count( $this->stack ) ) { |
| 631 | + throw new MWException( __METHOD__.': no elements remaining' ); |
| 632 | + } |
| 633 | + $temp = array_pop( $this->stack ); |
| 634 | + |
| 635 | + if ( count( $this->stack ) ) { |
| 636 | + $this->top = $this->stack[ count( $this->stack ) - 1 ]; |
| 637 | + $this->accum =& $this->top->getAccum(); |
| 638 | + } else { |
| 639 | + $this->top = self::$false; |
| 640 | + $this->accum =& $this->rootAccum; |
| 641 | + } |
| 642 | + return $temp; |
| 643 | + } |
| 644 | + |
| 645 | + function addPart( $s = '' ) { |
| 646 | + $this->top->addPart( $s ); |
| 647 | + $this->accum =& $this->top->getAccum(); |
| 648 | + } |
| 649 | + |
| 650 | + function getFlags() { |
| 651 | + if ( !count( $this->stack ) ) { |
| 652 | + return array( |
| 653 | + 'findEquals' => false, |
| 654 | + 'findPipe' => false, |
| 655 | + 'inHeading' => false, |
| 656 | + ); |
| 657 | + } else { |
| 658 | + return $this->top->getFlags(); |
| 659 | + } |
| 660 | + } |
| 661 | +} |
| 662 | + |
| 663 | +class PPDStackElement { |
| 664 | + var $open, // Opening character (\n for heading) |
| 665 | + $close, // Matching closing character |
| 666 | + $count, // Number of opening characters found (number of "=" for heading) |
| 667 | + $parts, // Array of PPDPart objects describing pipe-separated parts. |
| 668 | + $lineStart; // True if the open char appeared at the start of the input line. Not set for headings. |
| 669 | + |
| 670 | + function __construct( $data = array() ) { |
| 671 | + $this->parts = array( new PPDPart ); |
| 672 | + |
| 673 | + foreach ( $data as $name => $value ) { |
| 674 | + $this->$name = $value; |
| 675 | + } |
| 676 | + } |
| 677 | + |
| 678 | + function &getAccum() { |
| 679 | + return $this->parts[count($this->parts) - 1]->out; |
| 680 | + } |
| 681 | + |
| 682 | + function addPart( $s = '' ) { |
| 683 | + $this->parts[] = new PPDPart( $s ); |
| 684 | + } |
| 685 | + |
| 686 | + function getCurrentPart() { |
| 687 | + return $this->parts[count($this->parts) - 1]; |
| 688 | + } |
| 689 | + |
| 690 | + function getFlags() { |
| 691 | + $partCount = count( $this->parts ); |
| 692 | + $findPipe = $this->open != "\n" && $this->open != '['; |
| 693 | + return array( |
| 694 | + 'findPipe' => $findPipe, |
| 695 | + 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ), |
| 696 | + 'inHeading' => $this->open == "\n", |
| 697 | + ); |
| 698 | + } |
| 699 | + |
| 700 | + /** |
| 701 | + * Get the output string that would result if the close is not found. |
| 702 | + */ |
| 703 | + function breakSyntax( $openingCount = false ) { |
| 704 | + if ( $this->open == "\n" ) { |
| 705 | + $s = $this->parts[0]->out; |
| 706 | + } else { |
| 707 | + if ( $openingCount === false ) { |
| 708 | + $openingCount = $this->count; |
| 709 | + } |
| 710 | + $s = str_repeat( $this->open, $openingCount ); |
| 711 | + $first = true; |
| 712 | + foreach ( $this->parts as $part ) { |
| 713 | + if ( $first ) { |
| 714 | + $first = false; |
| 715 | + } else { |
| 716 | + $s .= '|'; |
| 717 | + } |
| 718 | + $s .= $part->out; |
| 719 | + } |
| 720 | + } |
| 721 | + return $s; |
| 722 | + } |
| 723 | +} |
| 724 | + |
| 725 | +class PPDPart { |
| 726 | + var $out; // Output accumulator string |
| 727 | + |
| 728 | + // Optional member variables: |
| 729 | + // eqpos Position of equals sign in output accumulator |
| 730 | + // commentEnd Past-the-end input pointer for the last comment encountered |
| 731 | + // visualEnd Past-the-end input pointer for the end of the accumulator minus comments |
| 732 | + |
| 733 | + function __construct( $out = '' ) { |
| 734 | + $this->out = $out; |
| 735 | + } |
| 736 | +} |
| 737 | + |
| 738 | +/** |
566 | 739 | * An expansion frame, used as a context to expand the result of preprocessToDom() |
567 | 740 | */ |
568 | 741 | class PPFrame_DOM implements PPFrame { |
— | — | @@ -1037,91 +1210,6 @@ |
1038 | 1211 | } |
1039 | 1212 | } |
1040 | 1213 | |
1041 | | -/** |
1042 | | - * Stack class to help Parser::preprocessToDom() |
1043 | | - */ |
1044 | | -class PPDStack { |
1045 | | - var $stack, $topAccum, $top; |
1046 | | - |
1047 | | - function __construct() { |
1048 | | - $this->stack = array(); |
1049 | | - $this->topAccum = ''; |
1050 | | - $this->top = false; |
1051 | | - } |
1052 | | - |
1053 | | - function &getAccum() { |
1054 | | - if ( count( $this->stack ) ) { |
1055 | | - return $this->top->getAccum(); |
1056 | | - } else { |
1057 | | - return $this->topAccum; |
1058 | | - } |
1059 | | - } |
1060 | | - |
1061 | | - function push( $data ) { |
1062 | | - if ( $data instanceof PPDStackElement ) { |
1063 | | - $this->stack[] = $data; |
1064 | | - } else { |
1065 | | - $this->stack[] = new PPDStackElement( $data ); |
1066 | | - } |
1067 | | - $this->top =& $this->stack[ count( $this->stack ) - 1 ]; |
1068 | | - } |
1069 | | - |
1070 | | - function pop() { |
1071 | | - if ( !count( $this->stack ) ) { |
1072 | | - throw new MWException( __METHOD__.': no elements remaining' ); |
1073 | | - } |
1074 | | - $temp = array_pop( $this->stack ); |
1075 | | - if ( count( $this->stack ) ) { |
1076 | | - $this->top =& $this->stack[ count( $this->stack ) - 1 ]; |
1077 | | - } else { |
1078 | | - $this->top = false; |
1079 | | - } |
1080 | | - } |
1081 | | - |
1082 | | - function getFlags() { |
1083 | | - if ( !count( $this->stack ) ) { |
1084 | | - return array( |
1085 | | - 'findEquals' => false, |
1086 | | - 'findPipe' => false, |
1087 | | - 'inHeading' => false, |
1088 | | - ); |
1089 | | - } else { |
1090 | | - return $this->top->getFlags(); |
1091 | | - } |
1092 | | - } |
1093 | | -} |
1094 | | - |
1095 | | -class PPDStackElement { |
1096 | | - var $open, $close, $count, $parts, $eqpos, $lineStart; |
1097 | | - |
1098 | | - function __construct( $data = array() ) { |
1099 | | - $this->parts = array( '' ); |
1100 | | - $this->eqpos = array(); |
1101 | | - |
1102 | | - foreach ( $data as $name => $value ) { |
1103 | | - $this->$name = $value; |
1104 | | - } |
1105 | | - } |
1106 | | - |
1107 | | - function &getAccum() { |
1108 | | - return $this->parts[count($this->parts) - 1]; |
1109 | | - } |
1110 | | - |
1111 | | - function addPart( $s = '' ) { |
1112 | | - $this->parts[] = $s; |
1113 | | - } |
1114 | | - |
1115 | | - function getFlags() { |
1116 | | - $partCount = count( $this->parts ); |
1117 | | - $findPipe = $this->open != "\n" && $this->open != '['; |
1118 | | - return array( |
1119 | | - 'findPipe' => $findPipe, |
1120 | | - 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->eqpos[$partCount - 1] ), |
1121 | | - 'inHeading' => $this->open == "\n", |
1122 | | - ); |
1123 | | - } |
1124 | | -} |
1125 | | - |
1126 | 1214 | class PPNode_DOM implements PPNode { |
1127 | 1215 | var $node; |
1128 | 1216 | |
— | — | @@ -1143,7 +1231,7 @@ |
1144 | 1232 | $s .= $node->ownerDocument->saveXML( $node ); |
1145 | 1233 | } |
1146 | 1234 | } else { |
1147 | | - $s = $this->node->ownerDocument->saveXML( $node ); |
| 1235 | + $s = $this->node->ownerDocument->saveXML( $this->node ); |
1148 | 1236 | } |
1149 | 1237 | return $s; |
1150 | 1238 | } |
Index: trunk/phase3/includes/Parser.php |
— | — | @@ -89,7 +89,7 @@ |
90 | 90 | # Persistent: |
91 | 91 | var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables, |
92 | 92 | $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerSuffix, |
93 | | - $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList; |
| 93 | + $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList, $mVarCache, $mConf; |
94 | 94 | |
95 | 95 | |
96 | 96 | # Cleared with clearState(): |
— | — | @@ -118,6 +118,7 @@ |
119 | 119 | * @public |
120 | 120 | */ |
121 | 121 | function __construct( $conf = array() ) { |
| 122 | + $this->mConf = $conf; |
122 | 123 | $this->mTagHooks = array(); |
123 | 124 | $this->mTransparentTagHooks = array(); |
124 | 125 | $this->mFunctionHooks = array(); |
— | — | @@ -126,6 +127,7 @@ |
127 | 128 | $this->mMarkerSuffix = "-QINU\x7f"; |
128 | 129 | $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'. |
129 | 130 | '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S'; |
| 131 | + $this->mVarCache = array(); |
130 | 132 | if ( isset( $conf['preprocessorClass'] ) ) { |
131 | 133 | $this->mPreprocessorClass = $conf['preprocessorClass']; |
132 | 134 | } else { |
— | — | @@ -237,6 +239,7 @@ |
238 | 240 | * the behaviour of <nowiki> in a link. |
239 | 241 | */ |
240 | 242 | #$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString(); |
| 243 | + # Changed to \x7f to allow XML double-parsing -- TS |
241 | 244 | $this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString(); |
242 | 245 | |
243 | 246 | # Clear these on every parse, bug 4549 |
— | — | @@ -252,6 +255,11 @@ |
253 | 256 | $this->mDefaultSort = false; |
254 | 257 | $this->mHeadings = array(); |
255 | 258 | |
| 259 | + # Fix cloning |
| 260 | + if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) { |
| 261 | + $this->mPreprocessor = null; |
| 262 | + } |
| 263 | + |
256 | 264 | wfRunHooks( 'ParserClearState', array( &$this ) ); |
257 | 265 | wfProfileOut( __METHOD__ ); |
258 | 266 | } |
— | — | @@ -2374,14 +2382,13 @@ |
2375 | 2383 | * Some of these require message or data lookups and can be |
2376 | 2384 | * expensive to check many times. |
2377 | 2385 | */ |
2378 | | - static $varCache = array(); |
2379 | | - if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) { |
2380 | | - if ( isset( $varCache[$index] ) ) { |
2381 | | - return $varCache[$index]; |
| 2386 | + if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) { |
| 2387 | + if ( isset( $this->mVarCache[$index] ) ) { |
| 2388 | + return $this->mVarCache[$index]; |
2382 | 2389 | } |
2383 | 2390 | } |
2384 | 2391 | |
2385 | | - $ts = time(); |
| 2392 | + $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() ); |
2386 | 2393 | wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) ); |
2387 | 2394 | |
2388 | 2395 | # Use the time zone |
— | — | @@ -2408,29 +2415,29 @@ |
2409 | 2416 | |
2410 | 2417 | switch ( $index ) { |
2411 | 2418 | case 'currentmonth': |
2412 | | - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) ); |
| 2419 | + return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) ); |
2413 | 2420 | case 'currentmonthname': |
2414 | | - return $varCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) ); |
| 2421 | + return $this->mVarCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) ); |
2415 | 2422 | case 'currentmonthnamegen': |
2416 | | - return $varCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) ); |
| 2423 | + return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) ); |
2417 | 2424 | case 'currentmonthabbrev': |
2418 | | - return $varCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) ); |
| 2425 | + return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) ); |
2419 | 2426 | case 'currentday': |
2420 | | - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) ); |
| 2427 | + return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) ); |
2421 | 2428 | case 'currentday2': |
2422 | | - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) ); |
| 2429 | + return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) ); |
2423 | 2430 | case 'localmonth': |
2424 | | - return $varCache[$index] = $wgContLang->formatNum( $localMonth ); |
| 2431 | + return $this->mVarCache[$index] = $wgContLang->formatNum( $localMonth ); |
2425 | 2432 | case 'localmonthname': |
2426 | | - return $varCache[$index] = $wgContLang->getMonthName( $localMonthName ); |
| 2433 | + return $this->mVarCache[$index] = $wgContLang->getMonthName( $localMonthName ); |
2427 | 2434 | case 'localmonthnamegen': |
2428 | | - return $varCache[$index] = $wgContLang->getMonthNameGen( $localMonthName ); |
| 2435 | + return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( $localMonthName ); |
2429 | 2436 | case 'localmonthabbrev': |
2430 | | - return $varCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName ); |
| 2437 | + return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName ); |
2431 | 2438 | case 'localday': |
2432 | | - return $varCache[$index] = $wgContLang->formatNum( $localDay ); |
| 2439 | + return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay ); |
2433 | 2440 | case 'localday2': |
2434 | | - return $varCache[$index] = $wgContLang->formatNum( $localDay2 ); |
| 2441 | + return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay2 ); |
2435 | 2442 | case 'pagename': |
2436 | 2443 | return wfEscapeWikiText( $this->mTitle->getText() ); |
2437 | 2444 | case 'pagenamee': |
— | — | @@ -2516,51 +2523,51 @@ |
2517 | 2524 | case 'subjectspacee': |
2518 | 2525 | return( wfUrlencode( $this->mTitle->getSubjectNsText() ) ); |
2519 | 2526 | case 'currentdayname': |
2520 | | - return $varCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 ); |
| 2527 | + return $this->mVarCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 ); |
2521 | 2528 | case 'currentyear': |
2522 | | - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true ); |
| 2529 | + return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true ); |
2523 | 2530 | case 'currenttime': |
2524 | | - return $varCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false ); |
| 2531 | + return $this->mVarCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false ); |
2525 | 2532 | case 'currenthour': |
2526 | | - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true ); |
| 2533 | + return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true ); |
2527 | 2534 | case 'currentweek': |
2528 | 2535 | // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to |
2529 | 2536 | // int to remove the padding |
2530 | | - return $varCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) ); |
| 2537 | + return $this->mVarCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) ); |
2531 | 2538 | case 'currentdow': |
2532 | | - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) ); |
| 2539 | + return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) ); |
2533 | 2540 | case 'localdayname': |
2534 | | - return $varCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 ); |
| 2541 | + return $this->mVarCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 ); |
2535 | 2542 | case 'localyear': |
2536 | | - return $varCache[$index] = $wgContLang->formatNum( $localYear, true ); |
| 2543 | + return $this->mVarCache[$index] = $wgContLang->formatNum( $localYear, true ); |
2537 | 2544 | case 'localtime': |
2538 | | - return $varCache[$index] = $wgContLang->time( $localTimestamp, false, false ); |
| 2545 | + return $this->mVarCache[$index] = $wgContLang->time( $localTimestamp, false, false ); |
2539 | 2546 | case 'localhour': |
2540 | | - return $varCache[$index] = $wgContLang->formatNum( $localHour, true ); |
| 2547 | + return $this->mVarCache[$index] = $wgContLang->formatNum( $localHour, true ); |
2541 | 2548 | case 'localweek': |
2542 | 2549 | // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to |
2543 | 2550 | // int to remove the padding |
2544 | | - return $varCache[$index] = $wgContLang->formatNum( (int)$localWeek ); |
| 2551 | + return $this->mVarCache[$index] = $wgContLang->formatNum( (int)$localWeek ); |
2545 | 2552 | case 'localdow': |
2546 | | - return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek ); |
| 2553 | + return $this->mVarCache[$index] = $wgContLang->formatNum( $localDayOfWeek ); |
2547 | 2554 | case 'numberofarticles': |
2548 | | - return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() ); |
| 2555 | + return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::articles() ); |
2549 | 2556 | case 'numberoffiles': |
2550 | | - return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() ); |
| 2557 | + return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::images() ); |
2551 | 2558 | case 'numberofusers': |
2552 | | - return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() ); |
| 2559 | + return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::users() ); |
2553 | 2560 | case 'numberofpages': |
2554 | | - return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() ); |
| 2561 | + return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::pages() ); |
2555 | 2562 | case 'numberofadmins': |
2556 | | - return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() ); |
| 2563 | + return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::admins() ); |
2557 | 2564 | case 'numberofedits': |
2558 | | - return $varCache[$index] = $wgContLang->formatNum( SiteStats::edits() ); |
| 2565 | + return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::edits() ); |
2559 | 2566 | case 'currenttimestamp': |
2560 | | - return $varCache[$index] = wfTimestampNow(); |
| 2567 | + return $this->mVarCache[$index] = wfTimestamp( TS_MW, $ts ); |
2561 | 2568 | case 'localtimestamp': |
2562 | | - return $varCache[$index] = $localTimestamp; |
| 2569 | + return $this->mVarCache[$index] = $localTimestamp; |
2563 | 2570 | case 'currentversion': |
2564 | | - return $varCache[$index] = SpecialVersion::getVersion(); |
| 2571 | + return $this->mVarCache[$index] = SpecialVersion::getVersion(); |
2565 | 2572 | case 'sitename': |
2566 | 2573 | return $wgSitename; |
2567 | 2574 | case 'server': |
— | — | @@ -2576,7 +2583,7 @@ |
2577 | 2584 | return $wgContLanguageCode; |
2578 | 2585 | default: |
2579 | 2586 | $ret = null; |
2580 | | - if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$varCache, &$index, &$ret ) ) ) |
| 2587 | + if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret ) ) ) |
2581 | 2588 | return $ret; |
2582 | 2589 | else |
2583 | 2590 | return null; |
— | — | @@ -2936,7 +2943,9 @@ |
2937 | 2944 | # Clean up argument array |
2938 | 2945 | $newFrame = $frame->newChild( $args, $title ); |
2939 | 2946 | |
2940 | | - if ( $titleText !== false && $newFrame->isEmpty() ) { |
| 2947 | + if ( $nowiki ) { |
| 2948 | + $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG ); |
| 2949 | + } elseif ( $titleText !== false && $newFrame->isEmpty() ) { |
2941 | 2950 | # Expansion is eligible for the empty-frame cache |
2942 | 2951 | if ( isset( $this->mTplExpandCache[$titleText] ) ) { |
2943 | 2952 | $text = $this->mTplExpandCache[$titleText]; |
— | — | @@ -2949,6 +2958,10 @@ |
2950 | 2959 | $text = $newFrame->expand( $text ); |
2951 | 2960 | } |
2952 | 2961 | } |
| 2962 | + if ( $isLocalObj && $nowiki ) { |
| 2963 | + $text = $frame->expand( $text, PPFrame::RECOVER_ORIG ); |
| 2964 | + $isLocalObj = false; |
| 2965 | + } |
2953 | 2966 | |
2954 | 2967 | # Replace raw HTML by a placeholder |
2955 | 2968 | # Add a blank line preceding, to prevent it from mucking up |
— | — | @@ -3635,7 +3648,7 @@ |
3636 | 3649 | $oldtz = getenv( 'TZ' ); |
3637 | 3650 | putenv( 'TZ='.$wgLocaltimezone ); |
3638 | 3651 | } |
3639 | | - $d = $wgContLang->timeanddate( date( 'YmdHis' ), false, false) . |
| 3652 | + $d = $wgContLang->timeanddate( $this->mOptions->getTimestamp(), false, false) . |
3640 | 3653 | ' (' . date( 'T' ) . ')'; |
3641 | 3654 | if ( isset( $wgLocaltimezone ) ) { |
3642 | 3655 | putenv( 'TZ='.$oldtz ); |
— | — | @@ -4776,6 +4789,9 @@ |
4777 | 4790 | */ |
4778 | 4791 | function testSrvus( $text, $title, $options, $outputType = self::OT_HTML ) { |
4779 | 4792 | $this->clearState(); |
| 4793 | + if ( ! ( $title instanceof Title ) ) { |
| 4794 | + $title = Title::newFromText( $title ); |
| 4795 | + } |
4780 | 4796 | $this->mTitle = $title; |
4781 | 4797 | $this->mOptions = $options; |
4782 | 4798 | $this->setOutputType( $outputType ); |
— | — | @@ -4787,10 +4803,16 @@ |
4788 | 4804 | |
4789 | 4805 | function testPst( $text, $title, $options ) { |
4790 | 4806 | global $wgUser; |
| 4807 | + if ( ! ( $title instanceof Title ) ) { |
| 4808 | + $title = Title::newFromText( $title ); |
| 4809 | + } |
4791 | 4810 | return $this->preSaveTransform( $text, $title, $wgUser, $options ); |
4792 | 4811 | } |
4793 | 4812 | |
4794 | 4813 | function testPreprocess( $text, $title, $options ) { |
| 4814 | + if ( ! ( $title instanceof Title ) ) { |
| 4815 | + $title = Title::newFromText( $title ); |
| 4816 | + } |
4795 | 4817 | return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS ); |
4796 | 4818 | } |
4797 | 4819 | } |
Index: trunk/phase3/includes/Parser_DiffTest.php |
— | — | @@ -18,7 +18,7 @@ |
19 | 19 | foreach ( $this->conf['parsers'] as $i => $parserConf ) { |
20 | 20 | if ( !is_array( $parserConf ) ) { |
21 | 21 | $class = $parserConf; |
22 | | - $parserconf = array( 'class' => $parserConf ); |
| 22 | + $parserConf = array( 'class' => $parserConf ); |
23 | 23 | } else { |
24 | 24 | $class = $parserConf['class']; |
25 | 25 | } |
Index: trunk/phase3/includes/ParserOptions.php |
— | — | @@ -26,6 +26,7 @@ |
27 | 27 | var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS |
28 | 28 | var $mTemplateCallback; # Callback for template fetching |
29 | 29 | var $mEnableLimitReport; # Enable limit report in an HTML comment on output |
| 30 | + var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc. |
30 | 31 | |
31 | 32 | var $mUser; # Stored user object, just used to initialise the skin |
32 | 33 | |
— | — | @@ -60,6 +61,13 @@ |
61 | 62 | return $this->mDateFormat; |
62 | 63 | } |
63 | 64 | |
| 65 | + function getTimestamp() { |
| 66 | + if ( !isset( $this->mTimestamp ) ) { |
| 67 | + $this->mTimestamp = wfTimestampNow(); |
| 68 | + } |
| 69 | + return $this->mTimestamp; |
| 70 | + } |
| 71 | + |
64 | 72 | function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); } |
65 | 73 | function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); } |
66 | 74 | function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); } |
— | — | @@ -78,6 +86,7 @@ |
79 | 87 | function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); } |
80 | 88 | function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); } |
81 | 89 | function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); } |
| 90 | + function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); } |
82 | 91 | |
83 | 92 | function __construct( $user = null ) { |
84 | 93 | $this->initialiseFromUser( $user ); |
Index: trunk/extensions/ExpandTemplates/ExpandTemplates_body.php |
— | — | @@ -36,7 +36,11 @@ |
37 | 37 | if ( $this->generateXML ) { |
38 | 38 | $wgParser->startExternalParse( $title, $options, OT_PREPROCESS ); |
39 | 39 | $dom = $wgParser->preprocessToDom( $input ); |
40 | | - $xml = $dom->saveXML(); |
| 40 | + if ( is_callable( array( $dom, 'saveXML' ) ) ) { |
| 41 | + $xml = $dom->saveXML(); |
| 42 | + } else { |
| 43 | + $xml = $dom->__toString(); |
| 44 | + } |
41 | 45 | } |
42 | 46 | $output = $wgParser->preprocess( $input, $title, $options ); |
43 | 47 | } else { |