r30106 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r30105‎ | r30106 | r30107 >
Date:04:29, 24 January 2008
Author:tstarling
Status:old
Tags:
Comment:
* Factored out PPD "part" handling into its own class. Verified with differential fuzz test.
* Stabilise timestamps generated by the parser to avoid diff test false positives
* Fixed msgnw bug. Use RECOVER_ORIG.
* Fixed editintro bug. Cloning the parser in MessageCache has some side-effects that need to be corrected.
* Fixed typo in Parser_DiffTest.php
* General improvements to preprocessorFuzzTest.php
* Fixed breakage of XML output feature in Special:ExpandTemplates
Modified paths:
  • /trunk/extensions/ExpandTemplates/ExpandTemplates_body.php (modified) (history)
  • /trunk/phase3/includes/Parser.php (modified) (history)
  • /trunk/phase3/includes/ParserOptions.php (modified) (history)
  • /trunk/phase3/includes/Parser_DiffTest.php (modified) (history)
  • /trunk/phase3/includes/Preprocessor_DOM.php (modified) (history)
  • /trunk/phase3/maintenance/preprocessorFuzzTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/preprocessorFuzzTest.php
@@ -6,7 +6,7 @@
77
88 class PPFuzzTester {
99 var $hairs = array(
10 - '[[', ']]', '{{', '}}', '{{{', '}}}',
 10+ '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}',
1111 '<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>',
1212 '<!--' , '-->',
1313 "\n==", "==\n",
@@ -23,6 +23,7 @@
2424 var $maxTemplates = 5;
2525 //var $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' );
2626 var $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' );
 27+ var $verbose = false;
2728 static $currentTest = false;
2829
2930 function execute() {
@@ -33,10 +34,14 @@
3435 echo "Unable to create 'results' directory\n";
3536 exit( 1 );
3637 }
37 - for ( $i = 0; true; $i++ ) {
 38+ $overallStart = microtime( true );
 39+ $reportInterval = 1000;
 40+ for ( $i = 1; true; $i++ ) {
 41+ $t = -microtime( true );
3842 try {
3943 self::$currentTest = new PPFuzzTest( $this );
4044 self::$currentTest->execute();
 45+ $passed = 'passed';
4146 } catch ( MWException $e ) {
4247 $testReport = self::$currentTest->getReport();
4348 $exceptionReport = $e->getText();
@@ -45,8 +50,30 @@
4651 file_put_contents( "results/ppft-$hash.fail",
4752 "Input:\n$testReport\n\nException report:\n$exceptionReport\n" );
4853 print "Test $hash failed\n";
 54+ $passed = 'failed';
4955 }
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 ) {
5178 print "$i tests done\n";
5279 /*
5380 $testReport = self::$currentTest->getReport();
@@ -54,10 +81,14 @@
5582 file_put_contents( $filename, "Input:\n$testReport\n" );*/
5683 }
5784 }
 85+ wfLogProfilingData();
5886 }
5987
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 );
6293 $s = '';
6394 for ( $i = 0; $i < $length; $i++ ) {
6495 $hairIndex = mt_rand( 0, count( $this->hairs ) - 1 );
@@ -88,15 +119,16 @@
89120 }
90121
91122 class PPFuzzTest {
92 - var $templates, $mainText, $title, $entryPoint;
 123+ var $templates, $mainText, $title, $entryPoint, $output;
93124
94125 function __construct( $tester ) {
 126+ global $wgMaxSigChars;
95127 $this->parent = $tester;
96128 $this->mainText = $tester->makeInputText();
97129 $this->title = $tester->makeTitle();
98130 //$this->outputType = $tester->pickOutputType();
99131 $this->entryPoint = $tester->pickEntryPoint();
100 - $this->nickname = $tester->makeInputText();
 132+ $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10);
101133 $this->fancySig = (bool)mt_rand( 0, 1 );
102134 $this->templates = array();
103135 }
@@ -138,8 +170,9 @@
139171
140172 $options = new ParserOptions;
141173 $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;
144177 }
145178
146179 function getReport() {
@@ -156,6 +189,7 @@
157190 $s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n";
158191 }
159192 }
 193+ $s .= "Output: " . var_export( $this->output, true ) . "\n";
160194 return $s;
161195 }
162196 }
@@ -163,6 +197,14 @@
164198 class PPFuzzUser extends User {
165199 var $ppfz_test;
166200
 201+ function load() {
 202+ if ( $this->mDataLoaded ) {
 203+ return;
 204+ }
 205+ $this->mDataLoaded = true;
 206+ $this->loadDefaults( $this->mName );
 207+ }
 208+
167209 function getOption( $option, $defaultOverride = '' ) {
168210 if ( $option === 'fancysig' ) {
169211 return $this->ppfz_test->fancySig;
@@ -182,10 +224,10 @@
183225 exit( 1 );
184226 }
185227 $test = unserialize( $testText );
186 - print $test->getReport();
187228 $result = $test->execute();
188 - print "Test passed.\nResult: $result\n";
 229+ print "Test passed.\n";
189230 } else {
190231 $tester = new PPFuzzTester;
 232+ $tester->verbose = isset( $options['verbose'] );
191233 $tester->execute();
192234 }
Index: trunk/phase3/includes/Preprocessor_DOM.php
@@ -1,16 +1,37 @@
22 <?php
33
44 class Preprocessor_DOM implements Preprocessor {
5 - var $parser;
 5+ var $parser, $memoryLimit;
66
77 function __construct( $parser ) {
88 $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+ }
918 }
1019
1120 function newFrame() {
1221 return new PPFrame_DOM( $this );
1322 }
1423
 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+
1536 /**
1637 * Preprocess some wikitext and return the document tree.
1738 * This is the ghost of Parser::replace_variables().
@@ -78,11 +99,11 @@
79100
80101 $stack = new PPDStack;
81102
82 - $searchBase = '[{<';
 103+ $searchBase = '[{<'; #}
83104 $revText = strrev( $text ); // For fast reverse searches
84105
85106 $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
87108 $accum = '<root>';
88109 $findEquals = false; # True to find equals signs in arguments
89110 $findPipe = false; # True to take notice of pipe characters
@@ -93,6 +114,8 @@
94115 $fakeLineStart = true; # Do a line-start run without outputting an LF character
95116
96117 while ( true ) {
 118+ if ( ! ($i % 10) ) $this->memCheck();
 119+
97120 if ( $findOnlyinclude ) {
98121 // Ignore all input up to the next <onlyinclude>
99122 $startPos = strpos( $text, '<onlyinclude>', $i );
@@ -241,6 +264,17 @@
242265 $endPos += 2;
243266 }
244267
 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+ */
245279 $i = $endPos + 1;
246280 $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
247281 $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>';
@@ -326,7 +360,7 @@
327361 $piece = array(
328362 'open' => "\n",
329363 'close' => "\n",
330 - 'parts' => array( str_repeat( '=', $count ) ),
 364+ 'parts' => array( new PPDPart( str_repeat( '=', $count ) ) ),
331365 'startPos' => $i,
332366 'count' => $count );
333367 $stack->push( $piece );
@@ -395,8 +429,6 @@
396430 'open' => $curChar,
397431 'close' => $rule['end'],
398432 'count' => $count,
399 - 'parts' => array( '' ),
400 - 'eqpos' => array(),
401433 'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
402434 );
403435
@@ -444,14 +476,12 @@
445477 $name = $rule['names'][$matchingCount];
446478 if ( $name === null ) {
447479 // 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 );
451481 } else {
452482 # Create XML element
453483 # Note: $parts is already XML, does not need to be encoded further
454484 $parts = $piece->parts;
455 - $title = $parts[0];
 485+ $title = $parts[0]->out;
456486 unset( $parts[0] );
457487
458488 # The invocation is at the start of the line if lineStart is set in
@@ -466,13 +496,12 @@
467497 $element .= "<title>$title</title>";
468498 $argIndex = 1;
469499 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 );
474503 $element .= "<part><name>$argName</name>=<value>$argValue</value></part>";
475504 } else {
476 - $element .= "<part><name index=\"$argIndex\" /><value>$part</value></part>";
 505+ $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>";
477506 $argIndex++;
478507 }
479508 }
@@ -488,9 +517,8 @@
489518
490519 # Re-add the old stack element if it still has unmatched opening characters remaining
491520 if ($matchingCount < $piece->count) {
492 - $piece->parts = array( '' );
 521+ $piece->parts = array( new PPDPart );
493522 $piece->count -= $matchingCount;
494 - $piece->eqpos = array();
495523 # do we still qualify for any callback with remaining count?
496524 $names = $rules[$piece->open]['names'];
497525 $skippedBraces = 0;
@@ -515,15 +543,14 @@
516544
517545 elseif ( $found == 'pipe' ) {
518546 $findEquals = true; // shortcut for getFlags()
519 - $stack->top->addPart();
 547+ $stack->addPart();
520548 $accum =& $stack->getAccum();
521549 ++$i;
522550 }
523551
524552 elseif ( $found == 'equals' ) {
525553 $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 );
528555 $accum .= '=';
529556 ++$i;
530557 }
@@ -531,14 +558,10 @@
532559
533560 # Output any remaining unclosed brackets
534561 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();
540563 }
541 - $stack->topAccum .= '</root>';
542 - $xml = $stack->topAccum;
 564+ $stack->rootAccum .= '</root>';
 565+ $xml = $stack->rootAccum;
543566
544567 wfProfileOut( __METHOD__.'-makexml' );
545568 wfProfileIn( __METHOD__.'-loadXML' );
@@ -562,6 +585,156 @@
563586 }
564587
565588 /**
 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+/**
566739 * An expansion frame, used as a context to expand the result of preprocessToDom()
567740 */
568741 class PPFrame_DOM implements PPFrame {
@@ -1037,91 +1210,6 @@
10381211 }
10391212 }
10401213
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 -
11261214 class PPNode_DOM implements PPNode {
11271215 var $node;
11281216
@@ -1143,7 +1231,7 @@
11441232 $s .= $node->ownerDocument->saveXML( $node );
11451233 }
11461234 } else {
1147 - $s = $this->node->ownerDocument->saveXML( $node );
 1235+ $s = $this->node->ownerDocument->saveXML( $this->node );
11481236 }
11491237 return $s;
11501238 }
Index: trunk/phase3/includes/Parser.php
@@ -89,7 +89,7 @@
9090 # Persistent:
9191 var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables,
9292 $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerSuffix,
93 - $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList;
 93+ $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList, $mVarCache, $mConf;
9494
9595
9696 # Cleared with clearState():
@@ -118,6 +118,7 @@
119119 * @public
120120 */
121121 function __construct( $conf = array() ) {
 122+ $this->mConf = $conf;
122123 $this->mTagHooks = array();
123124 $this->mTransparentTagHooks = array();
124125 $this->mFunctionHooks = array();
@@ -126,6 +127,7 @@
127128 $this->mMarkerSuffix = "-QINU\x7f";
128129 $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
129130 '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S';
 131+ $this->mVarCache = array();
130132 if ( isset( $conf['preprocessorClass'] ) ) {
131133 $this->mPreprocessorClass = $conf['preprocessorClass'];
132134 } else {
@@ -237,6 +239,7 @@
238240 * the behaviour of <nowiki> in a link.
239241 */
240242 #$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString();
 243+ # Changed to \x7f to allow XML double-parsing -- TS
241244 $this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString();
242245
243246 # Clear these on every parse, bug 4549
@@ -252,6 +255,11 @@
253256 $this->mDefaultSort = false;
254257 $this->mHeadings = array();
255258
 259+ # Fix cloning
 260+ if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
 261+ $this->mPreprocessor = null;
 262+ }
 263+
256264 wfRunHooks( 'ParserClearState', array( &$this ) );
257265 wfProfileOut( __METHOD__ );
258266 }
@@ -2374,14 +2382,13 @@
23752383 * Some of these require message or data lookups and can be
23762384 * expensive to check many times.
23772385 */
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];
23822389 }
23832390 }
23842391
2385 - $ts = time();
 2392+ $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
23862393 wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
23872394
23882395 # Use the time zone
@@ -2408,29 +2415,29 @@
24092416
24102417 switch ( $index ) {
24112418 case 'currentmonth':
2412 - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) );
 2419+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) );
24132420 case 'currentmonthname':
2414 - return $varCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) );
 2421+ return $this->mVarCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) );
24152422 case 'currentmonthnamegen':
2416 - return $varCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) );
 2423+ return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) );
24172424 case 'currentmonthabbrev':
2418 - return $varCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
 2425+ return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
24192426 case 'currentday':
2420 - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) );
 2427+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) );
24212428 case 'currentday2':
2422 - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) );
 2429+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) );
24232430 case 'localmonth':
2424 - return $varCache[$index] = $wgContLang->formatNum( $localMonth );
 2431+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localMonth );
24252432 case 'localmonthname':
2426 - return $varCache[$index] = $wgContLang->getMonthName( $localMonthName );
 2433+ return $this->mVarCache[$index] = $wgContLang->getMonthName( $localMonthName );
24272434 case 'localmonthnamegen':
2428 - return $varCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
 2435+ return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( $localMonthName );
24292436 case 'localmonthabbrev':
2430 - return $varCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
 2437+ return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName );
24312438 case 'localday':
2432 - return $varCache[$index] = $wgContLang->formatNum( $localDay );
 2439+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay );
24332440 case 'localday2':
2434 - return $varCache[$index] = $wgContLang->formatNum( $localDay2 );
 2441+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay2 );
24352442 case 'pagename':
24362443 return wfEscapeWikiText( $this->mTitle->getText() );
24372444 case 'pagenamee':
@@ -2516,51 +2523,51 @@
25172524 case 'subjectspacee':
25182525 return( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
25192526 case 'currentdayname':
2520 - return $varCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
 2527+ return $this->mVarCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
25212528 case 'currentyear':
2522 - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true );
 2529+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true );
25232530 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 );
25252532 case 'currenthour':
2526 - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
 2533+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
25272534 case 'currentweek':
25282535 // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
25292536 // 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 ) );
25312538 case 'currentdow':
2532 - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) );
 2539+ return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) );
25332540 case 'localdayname':
2534 - return $varCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
 2541+ return $this->mVarCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
25352542 case 'localyear':
2536 - return $varCache[$index] = $wgContLang->formatNum( $localYear, true );
 2543+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localYear, true );
25372544 case 'localtime':
2538 - return $varCache[$index] = $wgContLang->time( $localTimestamp, false, false );
 2545+ return $this->mVarCache[$index] = $wgContLang->time( $localTimestamp, false, false );
25392546 case 'localhour':
2540 - return $varCache[$index] = $wgContLang->formatNum( $localHour, true );
 2547+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localHour, true );
25412548 case 'localweek':
25422549 // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
25432550 // int to remove the padding
2544 - return $varCache[$index] = $wgContLang->formatNum( (int)$localWeek );
 2551+ return $this->mVarCache[$index] = $wgContLang->formatNum( (int)$localWeek );
25452552 case 'localdow':
2546 - return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
 2553+ return $this->mVarCache[$index] = $wgContLang->formatNum( $localDayOfWeek );
25472554 case 'numberofarticles':
2548 - return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
 2555+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::articles() );
25492556 case 'numberoffiles':
2550 - return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() );
 2557+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::images() );
25512558 case 'numberofusers':
2552 - return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() );
 2559+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::users() );
25532560 case 'numberofpages':
2554 - return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
 2561+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
25552562 case 'numberofadmins':
2556 - return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
 2563+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
25572564 case 'numberofedits':
2558 - return $varCache[$index] = $wgContLang->formatNum( SiteStats::edits() );
 2565+ return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::edits() );
25592566 case 'currenttimestamp':
2560 - return $varCache[$index] = wfTimestampNow();
 2567+ return $this->mVarCache[$index] = wfTimestamp( TS_MW, $ts );
25612568 case 'localtimestamp':
2562 - return $varCache[$index] = $localTimestamp;
 2569+ return $this->mVarCache[$index] = $localTimestamp;
25632570 case 'currentversion':
2564 - return $varCache[$index] = SpecialVersion::getVersion();
 2571+ return $this->mVarCache[$index] = SpecialVersion::getVersion();
25652572 case 'sitename':
25662573 return $wgSitename;
25672574 case 'server':
@@ -2576,7 +2583,7 @@
25772584 return $wgContLanguageCode;
25782585 default:
25792586 $ret = null;
2580 - if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$varCache, &$index, &$ret ) ) )
 2587+ if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret ) ) )
25812588 return $ret;
25822589 else
25832590 return null;
@@ -2936,7 +2943,9 @@
29372944 # Clean up argument array
29382945 $newFrame = $frame->newChild( $args, $title );
29392946
2940 - if ( $titleText !== false && $newFrame->isEmpty() ) {
 2947+ if ( $nowiki ) {
 2948+ $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
 2949+ } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
29412950 # Expansion is eligible for the empty-frame cache
29422951 if ( isset( $this->mTplExpandCache[$titleText] ) ) {
29432952 $text = $this->mTplExpandCache[$titleText];
@@ -2949,6 +2958,10 @@
29502959 $text = $newFrame->expand( $text );
29512960 }
29522961 }
 2962+ if ( $isLocalObj && $nowiki ) {
 2963+ $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
 2964+ $isLocalObj = false;
 2965+ }
29532966
29542967 # Replace raw HTML by a placeholder
29552968 # Add a blank line preceding, to prevent it from mucking up
@@ -3635,7 +3648,7 @@
36363649 $oldtz = getenv( 'TZ' );
36373650 putenv( 'TZ='.$wgLocaltimezone );
36383651 }
3639 - $d = $wgContLang->timeanddate( date( 'YmdHis' ), false, false) .
 3652+ $d = $wgContLang->timeanddate( $this->mOptions->getTimestamp(), false, false) .
36403653 ' (' . date( 'T' ) . ')';
36413654 if ( isset( $wgLocaltimezone ) ) {
36423655 putenv( 'TZ='.$oldtz );
@@ -4776,6 +4789,9 @@
47774790 */
47784791 function testSrvus( $text, $title, $options, $outputType = self::OT_HTML ) {
47794792 $this->clearState();
 4793+ if ( ! ( $title instanceof Title ) ) {
 4794+ $title = Title::newFromText( $title );
 4795+ }
47804796 $this->mTitle = $title;
47814797 $this->mOptions = $options;
47824798 $this->setOutputType( $outputType );
@@ -4787,10 +4803,16 @@
47884804
47894805 function testPst( $text, $title, $options ) {
47904806 global $wgUser;
 4807+ if ( ! ( $title instanceof Title ) ) {
 4808+ $title = Title::newFromText( $title );
 4809+ }
47914810 return $this->preSaveTransform( $text, $title, $wgUser, $options );
47924811 }
47934812
47944813 function testPreprocess( $text, $title, $options ) {
 4814+ if ( ! ( $title instanceof Title ) ) {
 4815+ $title = Title::newFromText( $title );
 4816+ }
47954817 return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
47964818 }
47974819 }
Index: trunk/phase3/includes/Parser_DiffTest.php
@@ -18,7 +18,7 @@
1919 foreach ( $this->conf['parsers'] as $i => $parserConf ) {
2020 if ( !is_array( $parserConf ) ) {
2121 $class = $parserConf;
22 - $parserconf = array( 'class' => $parserConf );
 22+ $parserConf = array( 'class' => $parserConf );
2323 } else {
2424 $class = $parserConf['class'];
2525 }
Index: trunk/phase3/includes/ParserOptions.php
@@ -26,6 +26,7 @@
2727 var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
2828 var $mTemplateCallback; # Callback for template fetching
2929 var $mEnableLimitReport; # Enable limit report in an HTML comment on output
 30+ var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc.
3031
3132 var $mUser; # Stored user object, just used to initialise the skin
3233
@@ -60,6 +61,13 @@
6162 return $this->mDateFormat;
6263 }
6364
 65+ function getTimestamp() {
 66+ if ( !isset( $this->mTimestamp ) ) {
 67+ $this->mTimestamp = wfTimestampNow();
 68+ }
 69+ return $this->mTimestamp;
 70+ }
 71+
6472 function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
6573 function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
6674 function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
@@ -78,6 +86,7 @@
7987 function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
8088 function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); }
8189 function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); }
 90+ function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); }
8291
8392 function __construct( $user = null ) {
8493 $this->initialiseFromUser( $user );
Index: trunk/extensions/ExpandTemplates/ExpandTemplates_body.php
@@ -36,7 +36,11 @@
3737 if ( $this->generateXML ) {
3838 $wgParser->startExternalParse( $title, $options, OT_PREPROCESS );
3939 $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+ }
4145 }
4246 $output = $wgParser->preprocess( $input, $title, $options );
4347 } else {

Status & tagging log