MediaWiki
REL1_22
|
00001 <?php 00030 class Preprocessor_Hash implements Preprocessor { 00034 var $parser; 00035 00036 const CACHE_VERSION = 1; 00037 00038 function __construct( $parser ) { 00039 $this->parser = $parser; 00040 } 00041 00045 function newFrame() { 00046 return new PPFrame_Hash( $this ); 00047 } 00048 00053 function newCustomFrame( $args ) { 00054 return new PPCustomFrame_Hash( $this, $args ); 00055 } 00056 00061 function newPartNodeArray( $values ) { 00062 $list = array(); 00063 00064 foreach ( $values as $k => $val ) { 00065 $partNode = new PPNode_Hash_Tree( 'part' ); 00066 $nameNode = new PPNode_Hash_Tree( 'name' ); 00067 00068 if ( is_int( $k ) ) { 00069 $nameNode->addChild( new PPNode_Hash_Attr( 'index', $k ) ); 00070 $partNode->addChild( $nameNode ); 00071 } else { 00072 $nameNode->addChild( new PPNode_Hash_Text( $k ) ); 00073 $partNode->addChild( $nameNode ); 00074 $partNode->addChild( new PPNode_Hash_Text( '=' ) ); 00075 } 00076 00077 $valueNode = new PPNode_Hash_Tree( 'value' ); 00078 $valueNode->addChild( new PPNode_Hash_Text( $val ) ); 00079 $partNode->addChild( $valueNode ); 00080 00081 $list[] = $partNode; 00082 } 00083 00084 $node = new PPNode_Hash_Array( $list ); 00085 return $node; 00086 } 00087 00111 function preprocessToObj( $text, $flags = 0 ) { 00112 wfProfileIn( __METHOD__ ); 00113 00114 // Check cache. 00115 global $wgMemc, $wgPreprocessorCacheThreshold; 00116 00117 $cacheable = $wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold; 00118 if ( $cacheable ) { 00119 wfProfileIn( __METHOD__ . '-cacheable' ); 00120 00121 $cacheKey = wfMemcKey( 'preprocess-hash', md5( $text ), $flags ); 00122 $cacheValue = $wgMemc->get( $cacheKey ); 00123 if ( $cacheValue ) { 00124 $version = substr( $cacheValue, 0, 8 ); 00125 if ( intval( $version ) == self::CACHE_VERSION ) { 00126 $hash = unserialize( substr( $cacheValue, 8 ) ); 00127 // From the cache 00128 wfDebugLog( "Preprocessor", 00129 "Loaded preprocessor hash from memcached (key $cacheKey)" ); 00130 wfProfileOut( __METHOD__ . '-cacheable' ); 00131 wfProfileOut( __METHOD__ ); 00132 return $hash; 00133 } 00134 } 00135 wfProfileIn( __METHOD__ . '-cache-miss' ); 00136 } 00137 00138 $rules = array( 00139 '{' => array( 00140 'end' => '}', 00141 'names' => array( 00142 2 => 'template', 00143 3 => 'tplarg', 00144 ), 00145 'min' => 2, 00146 'max' => 3, 00147 ), 00148 '[' => array( 00149 'end' => ']', 00150 'names' => array( 2 => null ), 00151 'min' => 2, 00152 'max' => 2, 00153 ) 00154 ); 00155 00156 $forInclusion = $flags & Parser::PTD_FOR_INCLUSION; 00157 00158 $xmlishElements = $this->parser->getStripList(); 00159 $enableOnlyinclude = false; 00160 if ( $forInclusion ) { 00161 $ignoredTags = array( 'includeonly', '/includeonly' ); 00162 $ignoredElements = array( 'noinclude' ); 00163 $xmlishElements[] = 'noinclude'; 00164 if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) { 00165 $enableOnlyinclude = true; 00166 } 00167 } else { 00168 $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ); 00169 $ignoredElements = array( 'includeonly' ); 00170 $xmlishElements[] = 'includeonly'; 00171 } 00172 $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) ); 00173 00174 // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset 00175 $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA"; 00176 00177 $stack = new PPDStack_Hash; 00178 00179 $searchBase = "[{<\n"; 00180 $revText = strrev( $text ); // For fast reverse searches 00181 $lengthText = strlen( $text ); 00182 00183 $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start 00184 $accum =& $stack->getAccum(); # Current accumulator 00185 $findEquals = false; # True to find equals signs in arguments 00186 $findPipe = false; # True to take notice of pipe characters 00187 $headingIndex = 1; 00188 $inHeading = false; # True if $i is inside a possible heading 00189 $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i 00190 $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude> 00191 $fakeLineStart = true; # Do a line-start run without outputting an LF character 00192 00193 while ( true ) { 00194 //$this->memCheck(); 00195 00196 if ( $findOnlyinclude ) { 00197 // Ignore all input up to the next <onlyinclude> 00198 $startPos = strpos( $text, '<onlyinclude>', $i ); 00199 if ( $startPos === false ) { 00200 // Ignored section runs to the end 00201 $accum->addNodeWithText( 'ignore', substr( $text, $i ) ); 00202 break; 00203 } 00204 $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end 00205 $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i ) ); 00206 $i = $tagEndPos; 00207 $findOnlyinclude = false; 00208 } 00209 00210 if ( $fakeLineStart ) { 00211 $found = 'line-start'; 00212 $curChar = ''; 00213 } else { 00214 # Find next opening brace, closing brace or pipe 00215 $search = $searchBase; 00216 if ( $stack->top === false ) { 00217 $currentClosing = ''; 00218 } else { 00219 $currentClosing = $stack->top->close; 00220 $search .= $currentClosing; 00221 } 00222 if ( $findPipe ) { 00223 $search .= '|'; 00224 } 00225 if ( $findEquals ) { 00226 // First equals will be for the template 00227 $search .= '='; 00228 } 00229 $rule = null; 00230 # Output literal section, advance input counter 00231 $literalLength = strcspn( $text, $search, $i ); 00232 if ( $literalLength > 0 ) { 00233 $accum->addLiteral( substr( $text, $i, $literalLength ) ); 00234 $i += $literalLength; 00235 } 00236 if ( $i >= $lengthText ) { 00237 if ( $currentClosing == "\n" ) { 00238 // Do a past-the-end run to finish off the heading 00239 $curChar = ''; 00240 $found = 'line-end'; 00241 } else { 00242 # All done 00243 break; 00244 } 00245 } else { 00246 $curChar = $text[$i]; 00247 if ( $curChar == '|' ) { 00248 $found = 'pipe'; 00249 } elseif ( $curChar == '=' ) { 00250 $found = 'equals'; 00251 } elseif ( $curChar == '<' ) { 00252 $found = 'angle'; 00253 } elseif ( $curChar == "\n" ) { 00254 if ( $inHeading ) { 00255 $found = 'line-end'; 00256 } else { 00257 $found = 'line-start'; 00258 } 00259 } elseif ( $curChar == $currentClosing ) { 00260 $found = 'close'; 00261 } elseif ( isset( $rules[$curChar] ) ) { 00262 $found = 'open'; 00263 $rule = $rules[$curChar]; 00264 } else { 00265 # Some versions of PHP have a strcspn which stops on null characters 00266 # Ignore and continue 00267 ++$i; 00268 continue; 00269 } 00270 } 00271 } 00272 00273 if ( $found == 'angle' ) { 00274 $matches = false; 00275 // Handle </onlyinclude> 00276 if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) { 00277 $findOnlyinclude = true; 00278 continue; 00279 } 00280 00281 // Determine element name 00282 if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) { 00283 // Element name missing or not listed 00284 $accum->addLiteral( '<' ); 00285 ++$i; 00286 continue; 00287 } 00288 // Handle comments 00289 if ( isset( $matches[2] ) && $matches[2] == '!--' ) { 00290 00291 // To avoid leaving blank lines, when a sequence of 00292 // space-separated comments is both preceded and followed by 00293 // a newline (ignoring spaces), then 00294 // trim leading and trailing spaces and the trailing newline. 00295 00296 // Find the end 00297 $endPos = strpos( $text, '-->', $i + 4 ); 00298 if ( $endPos === false ) { 00299 // Unclosed comment in input, runs to end 00300 $inner = substr( $text, $i ); 00301 $accum->addNodeWithText( 'comment', $inner ); 00302 $i = $lengthText; 00303 } else { 00304 // Search backwards for leading whitespace 00305 $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0; 00306 00307 // Search forwards for trailing whitespace 00308 // $wsEnd will be the position of the last space (or the '>' if there's none) 00309 $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 ); 00310 00311 // Keep looking forward as long as we're finding more 00312 // comments. 00313 $comments = array( array( $wsStart, $wsEnd ) ); 00314 while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) { 00315 $c = strpos( $text, '-->', $wsEnd + 4 ); 00316 if ( $c === false ) { 00317 break; 00318 } 00319 $c = $c + 2 + strspn( $text, " \t", $c + 3 ); 00320 $comments[] = array( $wsEnd + 1, $c ); 00321 $wsEnd = $c; 00322 } 00323 00324 // Eat the line if possible 00325 // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at 00326 // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but 00327 // it's a possible beneficial b/c break. 00328 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" 00329 && substr( $text, $wsEnd + 1, 1 ) == "\n" ) 00330 { 00331 // Remove leading whitespace from the end of the accumulator 00332 // Sanity check first though 00333 $wsLength = $i - $wsStart; 00334 if ( $wsLength > 0 00335 && $accum->lastNode instanceof PPNode_Hash_Text 00336 && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength ) 00337 { 00338 $accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength ); 00339 } 00340 00341 // Dump all but the last comment to the accumulator 00342 foreach ( $comments as $j => $com ) { 00343 $startPos = $com[0]; 00344 $endPos = $com[1] + 1; 00345 if ( $j == ( count( $comments ) - 1 ) ) { 00346 break; 00347 } 00348 $inner = substr( $text, $startPos, $endPos - $startPos ); 00349 $accum->addNodeWithText( 'comment', $inner ); 00350 } 00351 00352 // Do a line-start run next time to look for headings after the comment 00353 $fakeLineStart = true; 00354 } else { 00355 // No line to eat, just take the comment itself 00356 $startPos = $i; 00357 $endPos += 2; 00358 } 00359 00360 if ( $stack->top ) { 00361 $part = $stack->top->getCurrentPart(); 00362 if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) { 00363 $part->visualEnd = $wsStart; 00364 } 00365 // Else comments abutting, no change in visual end 00366 $part->commentEnd = $endPos; 00367 } 00368 $i = $endPos + 1; 00369 $inner = substr( $text, $startPos, $endPos - $startPos + 1 ); 00370 $accum->addNodeWithText( 'comment', $inner ); 00371 } 00372 continue; 00373 } 00374 $name = $matches[1]; 00375 $lowerName = strtolower( $name ); 00376 $attrStart = $i + strlen( $name ) + 1; 00377 00378 // Find end of tag 00379 $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart ); 00380 if ( $tagEndPos === false ) { 00381 // Infinite backtrack 00382 // Disable tag search to prevent worst-case O(N^2) performance 00383 $noMoreGT = true; 00384 $accum->addLiteral( '<' ); 00385 ++$i; 00386 continue; 00387 } 00388 00389 // Handle ignored tags 00390 if ( in_array( $lowerName, $ignoredTags ) ) { 00391 $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i + 1 ) ); 00392 $i = $tagEndPos + 1; 00393 continue; 00394 } 00395 00396 $tagStartPos = $i; 00397 if ( $text[$tagEndPos - 1] == '/' ) { 00398 // Short end tag 00399 $attrEnd = $tagEndPos - 1; 00400 $inner = null; 00401 $i = $tagEndPos + 1; 00402 $close = null; 00403 } else { 00404 $attrEnd = $tagEndPos; 00405 // Find closing tag 00406 if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", 00407 $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) 00408 { 00409 $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); 00410 $i = $matches[0][1] + strlen( $matches[0][0] ); 00411 $close = $matches[0][0]; 00412 } else { 00413 // No end tag -- let it run out to the end of the text. 00414 $inner = substr( $text, $tagEndPos + 1 ); 00415 $i = $lengthText; 00416 $close = null; 00417 } 00418 } 00419 // <includeonly> and <noinclude> just become <ignore> tags 00420 if ( in_array( $lowerName, $ignoredElements ) ) { 00421 $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) ); 00422 continue; 00423 } 00424 00425 if ( $attrEnd <= $attrStart ) { 00426 $attr = ''; 00427 } else { 00428 // Note that the attr element contains the whitespace between name and attribute, 00429 // this is necessary for precise reconstruction during pre-save transform. 00430 $attr = substr( $text, $attrStart, $attrEnd - $attrStart ); 00431 } 00432 00433 $extNode = new PPNode_Hash_Tree( 'ext' ); 00434 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'name', $name ) ); 00435 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'attr', $attr ) ); 00436 if ( $inner !== null ) { 00437 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'inner', $inner ) ); 00438 } 00439 if ( $close !== null ) { 00440 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) ); 00441 } 00442 $accum->addNode( $extNode ); 00443 } 00444 00445 elseif ( $found == 'line-start' ) { 00446 // Is this the start of a heading? 00447 // Line break belongs before the heading element in any case 00448 if ( $fakeLineStart ) { 00449 $fakeLineStart = false; 00450 } else { 00451 $accum->addLiteral( $curChar ); 00452 $i++; 00453 } 00454 00455 $count = strspn( $text, '=', $i, 6 ); 00456 if ( $count == 1 && $findEquals ) { 00457 // DWIM: This looks kind of like a name/value separator 00458 // Let's let the equals handler have it and break the potential heading 00459 // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex. 00460 } elseif ( $count > 0 ) { 00461 $piece = array( 00462 'open' => "\n", 00463 'close' => "\n", 00464 'parts' => array( new PPDPart_Hash( str_repeat( '=', $count ) ) ), 00465 'startPos' => $i, 00466 'count' => $count ); 00467 $stack->push( $piece ); 00468 $accum =& $stack->getAccum(); 00469 extract( $stack->getFlags() ); 00470 $i += $count; 00471 } 00472 } elseif ( $found == 'line-end' ) { 00473 $piece = $stack->top; 00474 // A heading must be open, otherwise \n wouldn't have been in the search list 00475 assert( '$piece->open == "\n"' ); 00476 $part = $piece->getCurrentPart(); 00477 // Search back through the input to see if it has a proper close 00478 // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient 00479 $wsLength = strspn( $revText, " \t", $lengthText - $i ); 00480 $searchStart = $i - $wsLength; 00481 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { 00482 // Comment found at line end 00483 // Search for equals signs before the comment 00484 $searchStart = $part->visualEnd; 00485 $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart ); 00486 } 00487 $count = $piece->count; 00488 $equalsLength = strspn( $revText, '=', $lengthText - $searchStart ); 00489 if ( $equalsLength > 0 ) { 00490 if ( $searchStart - $equalsLength == $piece->startPos ) { 00491 // This is just a single string of equals signs on its own line 00492 // Replicate the doHeadings behavior /={count}(.+)={count}/ 00493 // First find out how many equals signs there really are (don't stop at 6) 00494 $count = $equalsLength; 00495 if ( $count < 3 ) { 00496 $count = 0; 00497 } else { 00498 $count = min( 6, intval( ( $count - 1 ) / 2 ) ); 00499 } 00500 } else { 00501 $count = min( $equalsLength, $count ); 00502 } 00503 if ( $count > 0 ) { 00504 // Normal match, output <h> 00505 $element = new PPNode_Hash_Tree( 'possible-h' ); 00506 $element->addChild( new PPNode_Hash_Attr( 'level', $count ) ); 00507 $element->addChild( new PPNode_Hash_Attr( 'i', $headingIndex++ ) ); 00508 $element->lastChild->nextSibling = $accum->firstNode; 00509 $element->lastChild = $accum->lastNode; 00510 } else { 00511 // Single equals sign on its own line, count=0 00512 $element = $accum; 00513 } 00514 } else { 00515 // No match, no <h>, just pass down the inner text 00516 $element = $accum; 00517 } 00518 // Unwind the stack 00519 $stack->pop(); 00520 $accum =& $stack->getAccum(); 00521 extract( $stack->getFlags() ); 00522 00523 // Append the result to the enclosing accumulator 00524 if ( $element instanceof PPNode ) { 00525 $accum->addNode( $element ); 00526 } else { 00527 $accum->addAccum( $element ); 00528 } 00529 // Note that we do NOT increment the input pointer. 00530 // This is because the closing linebreak could be the opening linebreak of 00531 // another heading. Infinite loops are avoided because the next iteration MUST 00532 // hit the heading open case above, which unconditionally increments the 00533 // input pointer. 00534 } elseif ( $found == 'open' ) { 00535 # count opening brace characters 00536 $count = strspn( $text, $curChar, $i ); 00537 00538 # we need to add to stack only if opening brace count is enough for one of the rules 00539 if ( $count >= $rule['min'] ) { 00540 # Add it to the stack 00541 $piece = array( 00542 'open' => $curChar, 00543 'close' => $rule['end'], 00544 'count' => $count, 00545 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ), 00546 ); 00547 00548 $stack->push( $piece ); 00549 $accum =& $stack->getAccum(); 00550 extract( $stack->getFlags() ); 00551 } else { 00552 # Add literal brace(s) 00553 $accum->addLiteral( str_repeat( $curChar, $count ) ); 00554 } 00555 $i += $count; 00556 } elseif ( $found == 'close' ) { 00557 $piece = $stack->top; 00558 # lets check if there are enough characters for closing brace 00559 $maxCount = $piece->count; 00560 $count = strspn( $text, $curChar, $i, $maxCount ); 00561 00562 # check for maximum matching characters (if there are 5 closing 00563 # characters, we will probably need only 3 - depending on the rules) 00564 $rule = $rules[$piece->open]; 00565 if ( $count > $rule['max'] ) { 00566 # The specified maximum exists in the callback array, unless the caller 00567 # has made an error 00568 $matchingCount = $rule['max']; 00569 } else { 00570 # Count is less than the maximum 00571 # Skip any gaps in the callback array to find the true largest match 00572 # Need to use array_key_exists not isset because the callback can be null 00573 $matchingCount = $count; 00574 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) { 00575 --$matchingCount; 00576 } 00577 } 00578 00579 if ( $matchingCount <= 0 ) { 00580 # No matching element found in callback array 00581 # Output a literal closing brace and continue 00582 $accum->addLiteral( str_repeat( $curChar, $count ) ); 00583 $i += $count; 00584 continue; 00585 } 00586 $name = $rule['names'][$matchingCount]; 00587 if ( $name === null ) { 00588 // No element, just literal text 00589 $element = $piece->breakSyntax( $matchingCount ); 00590 $element->addLiteral( str_repeat( $rule['end'], $matchingCount ) ); 00591 } else { 00592 # Create XML element 00593 # Note: $parts is already XML, does not need to be encoded further 00594 $parts = $piece->parts; 00595 $titleAccum = $parts[0]->out; 00596 unset( $parts[0] ); 00597 00598 $element = new PPNode_Hash_Tree( $name ); 00599 00600 # The invocation is at the start of the line if lineStart is set in 00601 # the stack, and all opening brackets are used up. 00602 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) { 00603 $element->addChild( new PPNode_Hash_Attr( 'lineStart', 1 ) ); 00604 } 00605 $titleNode = new PPNode_Hash_Tree( 'title' ); 00606 $titleNode->firstChild = $titleAccum->firstNode; 00607 $titleNode->lastChild = $titleAccum->lastNode; 00608 $element->addChild( $titleNode ); 00609 $argIndex = 1; 00610 foreach ( $parts as $part ) { 00611 if ( isset( $part->eqpos ) ) { 00612 // Find equals 00613 $lastNode = false; 00614 for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) { 00615 if ( $node === $part->eqpos ) { 00616 break; 00617 } 00618 $lastNode = $node; 00619 } 00620 if ( !$node ) { 00621 if ( $cacheable ) { 00622 wfProfileOut( __METHOD__ . '-cache-miss' ); 00623 wfProfileOut( __METHOD__ . '-cacheable' ); 00624 } 00625 wfProfileOut( __METHOD__ ); 00626 throw new MWException( __METHOD__ . ': eqpos not found' ); 00627 } 00628 if ( $node->name !== 'equals' ) { 00629 if ( $cacheable ) { 00630 wfProfileOut( __METHOD__ . '-cache-miss' ); 00631 wfProfileOut( __METHOD__ . '-cacheable' ); 00632 } 00633 wfProfileOut( __METHOD__ ); 00634 throw new MWException( __METHOD__ . ': eqpos is not equals' ); 00635 } 00636 $equalsNode = $node; 00637 00638 // Construct name node 00639 $nameNode = new PPNode_Hash_Tree( 'name' ); 00640 if ( $lastNode !== false ) { 00641 $lastNode->nextSibling = false; 00642 $nameNode->firstChild = $part->out->firstNode; 00643 $nameNode->lastChild = $lastNode; 00644 } 00645 00646 // Construct value node 00647 $valueNode = new PPNode_Hash_Tree( 'value' ); 00648 if ( $equalsNode->nextSibling !== false ) { 00649 $valueNode->firstChild = $equalsNode->nextSibling; 00650 $valueNode->lastChild = $part->out->lastNode; 00651 } 00652 $partNode = new PPNode_Hash_Tree( 'part' ); 00653 $partNode->addChild( $nameNode ); 00654 $partNode->addChild( $equalsNode->firstChild ); 00655 $partNode->addChild( $valueNode ); 00656 $element->addChild( $partNode ); 00657 } else { 00658 $partNode = new PPNode_Hash_Tree( 'part' ); 00659 $nameNode = new PPNode_Hash_Tree( 'name' ); 00660 $nameNode->addChild( new PPNode_Hash_Attr( 'index', $argIndex++ ) ); 00661 $valueNode = new PPNode_Hash_Tree( 'value' ); 00662 $valueNode->firstChild = $part->out->firstNode; 00663 $valueNode->lastChild = $part->out->lastNode; 00664 $partNode->addChild( $nameNode ); 00665 $partNode->addChild( $valueNode ); 00666 $element->addChild( $partNode ); 00667 } 00668 } 00669 } 00670 00671 # Advance input pointer 00672 $i += $matchingCount; 00673 00674 # Unwind the stack 00675 $stack->pop(); 00676 $accum =& $stack->getAccum(); 00677 00678 # Re-add the old stack element if it still has unmatched opening characters remaining 00679 if ( $matchingCount < $piece->count ) { 00680 $piece->parts = array( new PPDPart_Hash ); 00681 $piece->count -= $matchingCount; 00682 # do we still qualify for any callback with remaining count? 00683 $min = $rules[$piece->open]['min']; 00684 if ( $piece->count >= $min ) { 00685 $stack->push( $piece ); 00686 $accum =& $stack->getAccum(); 00687 } else { 00688 $accum->addLiteral( str_repeat( $piece->open, $piece->count ) ); 00689 } 00690 } 00691 00692 extract( $stack->getFlags() ); 00693 00694 # Add XML element to the enclosing accumulator 00695 if ( $element instanceof PPNode ) { 00696 $accum->addNode( $element ); 00697 } else { 00698 $accum->addAccum( $element ); 00699 } 00700 } elseif ( $found == 'pipe' ) { 00701 $findEquals = true; // shortcut for getFlags() 00702 $stack->addPart(); 00703 $accum =& $stack->getAccum(); 00704 ++$i; 00705 } elseif ( $found == 'equals' ) { 00706 $findEquals = false; // shortcut for getFlags() 00707 $accum->addNodeWithText( 'equals', '=' ); 00708 $stack->getCurrentPart()->eqpos = $accum->lastNode; 00709 ++$i; 00710 } 00711 } 00712 00713 # Output any remaining unclosed brackets 00714 foreach ( $stack->stack as $piece ) { 00715 $stack->rootAccum->addAccum( $piece->breakSyntax() ); 00716 } 00717 00718 # Enable top-level headings 00719 for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) { 00720 if ( isset( $node->name ) && $node->name === 'possible-h' ) { 00721 $node->name = 'h'; 00722 } 00723 } 00724 00725 $rootNode = new PPNode_Hash_Tree( 'root' ); 00726 $rootNode->firstChild = $stack->rootAccum->firstNode; 00727 $rootNode->lastChild = $stack->rootAccum->lastNode; 00728 00729 // Cache 00730 if ( $cacheable ) { 00731 $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode ); 00732 $wgMemc->set( $cacheKey, $cacheValue, 86400 ); 00733 wfProfileOut( __METHOD__ . '-cache-miss' ); 00734 wfProfileOut( __METHOD__ . '-cacheable' ); 00735 wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" ); 00736 } 00737 00738 wfProfileOut( __METHOD__ ); 00739 return $rootNode; 00740 } 00741 } 00742 00747 class PPDStack_Hash extends PPDStack { 00748 function __construct() { 00749 $this->elementClass = 'PPDStackElement_Hash'; 00750 parent::__construct(); 00751 $this->rootAccum = new PPDAccum_Hash; 00752 } 00753 } 00754 00758 class PPDStackElement_Hash extends PPDStackElement { 00759 function __construct( $data = array() ) { 00760 $this->partClass = 'PPDPart_Hash'; 00761 parent::__construct( $data ); 00762 } 00763 00769 function breakSyntax( $openingCount = false ) { 00770 if ( $this->open == "\n" ) { 00771 $accum = $this->parts[0]->out; 00772 } else { 00773 if ( $openingCount === false ) { 00774 $openingCount = $this->count; 00775 } 00776 $accum = new PPDAccum_Hash; 00777 $accum->addLiteral( str_repeat( $this->open, $openingCount ) ); 00778 $first = true; 00779 foreach ( $this->parts as $part ) { 00780 if ( $first ) { 00781 $first = false; 00782 } else { 00783 $accum->addLiteral( '|' ); 00784 } 00785 $accum->addAccum( $part->out ); 00786 } 00787 } 00788 return $accum; 00789 } 00790 } 00791 00795 class PPDPart_Hash extends PPDPart { 00796 function __construct( $out = '' ) { 00797 $accum = new PPDAccum_Hash; 00798 if ( $out !== '' ) { 00799 $accum->addLiteral( $out ); 00800 } 00801 parent::__construct( $accum ); 00802 } 00803 } 00804 00808 class PPDAccum_Hash { 00809 var $firstNode, $lastNode; 00810 00811 function __construct() { 00812 $this->firstNode = $this->lastNode = false; 00813 } 00814 00818 function addLiteral( $s ) { 00819 if ( $this->lastNode === false ) { 00820 $this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s ); 00821 } elseif ( $this->lastNode instanceof PPNode_Hash_Text ) { 00822 $this->lastNode->value .= $s; 00823 } else { 00824 $this->lastNode->nextSibling = new PPNode_Hash_Text( $s ); 00825 $this->lastNode = $this->lastNode->nextSibling; 00826 } 00827 } 00828 00832 function addNode( PPNode $node ) { 00833 if ( $this->lastNode === false ) { 00834 $this->firstNode = $this->lastNode = $node; 00835 } else { 00836 $this->lastNode->nextSibling = $node; 00837 $this->lastNode = $node; 00838 } 00839 } 00840 00844 function addNodeWithText( $name, $value ) { 00845 $node = PPNode_Hash_Tree::newWithText( $name, $value ); 00846 $this->addNode( $node ); 00847 } 00848 00854 function addAccum( $accum ) { 00855 if ( $accum->lastNode === false ) { 00856 // nothing to add 00857 } elseif ( $this->lastNode === false ) { 00858 $this->firstNode = $accum->firstNode; 00859 $this->lastNode = $accum->lastNode; 00860 } else { 00861 $this->lastNode->nextSibling = $accum->firstNode; 00862 $this->lastNode = $accum->lastNode; 00863 } 00864 } 00865 } 00866 00871 class PPFrame_Hash implements PPFrame { 00872 00876 var $parser; 00877 00881 var $preprocessor; 00882 00886 var $title; 00887 var $titleCache; 00888 00893 var $loopCheckHash; 00894 00899 var $depth; 00900 00905 function __construct( $preprocessor ) { 00906 $this->preprocessor = $preprocessor; 00907 $this->parser = $preprocessor->parser; 00908 $this->title = $this->parser->mTitle; 00909 $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); 00910 $this->loopCheckHash = array(); 00911 $this->depth = 0; 00912 } 00913 00925 function newChild( $args = false, $title = false, $indexOffset = 0 ) { 00926 $namedArgs = array(); 00927 $numberedArgs = array(); 00928 if ( $title === false ) { 00929 $title = $this->title; 00930 } 00931 if ( $args !== false ) { 00932 if ( $args instanceof PPNode_Hash_Array ) { 00933 $args = $args->value; 00934 } elseif ( !is_array( $args ) ) { 00935 throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' ); 00936 } 00937 foreach ( $args as $arg ) { 00938 $bits = $arg->splitArg(); 00939 if ( $bits['index'] !== '' ) { 00940 // Numbered parameter 00941 $index = $bits['index'] - $indexOffset; 00942 $numberedArgs[$index] = $bits['value']; 00943 unset( $namedArgs[$index] ); 00944 } else { 00945 // Named parameter 00946 $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) ); 00947 $namedArgs[$name] = $bits['value']; 00948 unset( $numberedArgs[$name] ); 00949 } 00950 } 00951 } 00952 return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title ); 00953 } 00954 00961 function expand( $root, $flags = 0 ) { 00962 static $expansionDepth = 0; 00963 if ( is_string( $root ) ) { 00964 return $root; 00965 } 00966 00967 if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) { 00968 $this->parser->limitationWarn( 'node-count-exceeded', 00969 $this->parser->mPPNodeCount, 00970 $this->parser->mOptions->getMaxPPNodeCount() 00971 ); 00972 return '<span class="error">Node-count limit exceeded</span>'; 00973 } 00974 if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) { 00975 $this->parser->limitationWarn( 'expansion-depth-exceeded', 00976 $expansionDepth, 00977 $this->parser->mOptions->getMaxPPExpandDepth() 00978 ); 00979 return '<span class="error">Expansion depth limit exceeded</span>'; 00980 } 00981 ++$expansionDepth; 00982 if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) { 00983 $this->parser->mHighestExpansionDepth = $expansionDepth; 00984 } 00985 00986 $outStack = array( '', '' ); 00987 $iteratorStack = array( false, $root ); 00988 $indexStack = array( 0, 0 ); 00989 00990 while ( count( $iteratorStack ) > 1 ) { 00991 $level = count( $outStack ) - 1; 00992 $iteratorNode =& $iteratorStack[$level]; 00993 $out =& $outStack[$level]; 00994 $index =& $indexStack[$level]; 00995 00996 if ( is_array( $iteratorNode ) ) { 00997 if ( $index >= count( $iteratorNode ) ) { 00998 // All done with this iterator 00999 $iteratorStack[$level] = false; 01000 $contextNode = false; 01001 } else { 01002 $contextNode = $iteratorNode[$index]; 01003 $index++; 01004 } 01005 } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) { 01006 if ( $index >= $iteratorNode->getLength() ) { 01007 // All done with this iterator 01008 $iteratorStack[$level] = false; 01009 $contextNode = false; 01010 } else { 01011 $contextNode = $iteratorNode->item( $index ); 01012 $index++; 01013 } 01014 } else { 01015 // Copy to $contextNode and then delete from iterator stack, 01016 // because this is not an iterator but we do have to execute it once 01017 $contextNode = $iteratorStack[$level]; 01018 $iteratorStack[$level] = false; 01019 } 01020 01021 $newIterator = false; 01022 01023 if ( $contextNode === false ) { 01024 // nothing to do 01025 } elseif ( is_string( $contextNode ) ) { 01026 $out .= $contextNode; 01027 } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_Hash_Array ) { 01028 $newIterator = $contextNode; 01029 } elseif ( $contextNode instanceof PPNode_Hash_Attr ) { 01030 // No output 01031 } elseif ( $contextNode instanceof PPNode_Hash_Text ) { 01032 $out .= $contextNode->value; 01033 } elseif ( $contextNode instanceof PPNode_Hash_Tree ) { 01034 if ( $contextNode->name == 'template' ) { 01035 # Double-brace expansion 01036 $bits = $contextNode->splitTemplate(); 01037 if ( $flags & PPFrame::NO_TEMPLATES ) { 01038 $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] ); 01039 } else { 01040 $ret = $this->parser->braceSubstitution( $bits, $this ); 01041 if ( isset( $ret['object'] ) ) { 01042 $newIterator = $ret['object']; 01043 } else { 01044 $out .= $ret['text']; 01045 } 01046 } 01047 } elseif ( $contextNode->name == 'tplarg' ) { 01048 # Triple-brace expansion 01049 $bits = $contextNode->splitTemplate(); 01050 if ( $flags & PPFrame::NO_ARGS ) { 01051 $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] ); 01052 } else { 01053 $ret = $this->parser->argSubstitution( $bits, $this ); 01054 if ( isset( $ret['object'] ) ) { 01055 $newIterator = $ret['object']; 01056 } else { 01057 $out .= $ret['text']; 01058 } 01059 } 01060 } elseif ( $contextNode->name == 'comment' ) { 01061 # HTML-style comment 01062 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes 01063 if ( $this->parser->ot['html'] 01064 || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) 01065 || ( $flags & PPFrame::STRIP_COMMENTS ) ) 01066 { 01067 $out .= ''; 01068 } 01069 # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result 01070 # Not in RECOVER_COMMENTS mode (extractSections) though 01071 elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) { 01072 $out .= $this->parser->insertStripItem( $contextNode->firstChild->value ); 01073 } 01074 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove 01075 else { 01076 $out .= $contextNode->firstChild->value; 01077 } 01078 } elseif ( $contextNode->name == 'ignore' ) { 01079 # Output suppression used by <includeonly> etc. 01080 # OT_WIKI will only respect <ignore> in substed templates. 01081 # The other output types respect it unless NO_IGNORE is set. 01082 # extractSections() sets NO_IGNORE and so never respects it. 01083 if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) { 01084 $out .= $contextNode->firstChild->value; 01085 } else { 01086 //$out .= ''; 01087 } 01088 } elseif ( $contextNode->name == 'ext' ) { 01089 # Extension tag 01090 $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null ); 01091 $out .= $this->parser->extensionSubstitution( $bits, $this ); 01092 } elseif ( $contextNode->name == 'h' ) { 01093 # Heading 01094 if ( $this->parser->ot['html'] ) { 01095 # Expand immediately and insert heading index marker 01096 $s = ''; 01097 for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) { 01098 $s .= $this->expand( $node, $flags ); 01099 } 01100 01101 $bits = $contextNode->splitHeading(); 01102 $titleText = $this->title->getPrefixedDBkey(); 01103 $this->parser->mHeadings[] = array( $titleText, $bits['i'] ); 01104 $serial = count( $this->parser->mHeadings ) - 1; 01105 $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX; 01106 $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] ); 01107 $this->parser->mStripState->addGeneral( $marker, '' ); 01108 $out .= $s; 01109 } else { 01110 # Expand in virtual stack 01111 $newIterator = $contextNode->getChildren(); 01112 } 01113 } else { 01114 # Generic recursive expansion 01115 $newIterator = $contextNode->getChildren(); 01116 } 01117 } else { 01118 throw new MWException( __METHOD__ . ': Invalid parameter type' ); 01119 } 01120 01121 if ( $newIterator !== false ) { 01122 $outStack[] = ''; 01123 $iteratorStack[] = $newIterator; 01124 $indexStack[] = 0; 01125 } elseif ( $iteratorStack[$level] === false ) { 01126 // Return accumulated value to parent 01127 // With tail recursion 01128 while ( $iteratorStack[$level] === false && $level > 0 ) { 01129 $outStack[$level - 1] .= $out; 01130 array_pop( $outStack ); 01131 array_pop( $iteratorStack ); 01132 array_pop( $indexStack ); 01133 $level--; 01134 } 01135 } 01136 } 01137 --$expansionDepth; 01138 return $outStack[0]; 01139 } 01140 01146 function implodeWithFlags( $sep, $flags /*, ... */ ) { 01147 $args = array_slice( func_get_args(), 2 ); 01148 01149 $first = true; 01150 $s = ''; 01151 foreach ( $args as $root ) { 01152 if ( $root instanceof PPNode_Hash_Array ) { 01153 $root = $root->value; 01154 } 01155 if ( !is_array( $root ) ) { 01156 $root = array( $root ); 01157 } 01158 foreach ( $root as $node ) { 01159 if ( $first ) { 01160 $first = false; 01161 } else { 01162 $s .= $sep; 01163 } 01164 $s .= $this->expand( $node, $flags ); 01165 } 01166 } 01167 return $s; 01168 } 01169 01175 function implode( $sep /*, ... */ ) { 01176 $args = array_slice( func_get_args(), 1 ); 01177 01178 $first = true; 01179 $s = ''; 01180 foreach ( $args as $root ) { 01181 if ( $root instanceof PPNode_Hash_Array ) { 01182 $root = $root->value; 01183 } 01184 if ( !is_array( $root ) ) { 01185 $root = array( $root ); 01186 } 01187 foreach ( $root as $node ) { 01188 if ( $first ) { 01189 $first = false; 01190 } else { 01191 $s .= $sep; 01192 } 01193 $s .= $this->expand( $node ); 01194 } 01195 } 01196 return $s; 01197 } 01198 01205 function virtualImplode( $sep /*, ... */ ) { 01206 $args = array_slice( func_get_args(), 1 ); 01207 $out = array(); 01208 $first = true; 01209 01210 foreach ( $args as $root ) { 01211 if ( $root instanceof PPNode_Hash_Array ) { 01212 $root = $root->value; 01213 } 01214 if ( !is_array( $root ) ) { 01215 $root = array( $root ); 01216 } 01217 foreach ( $root as $node ) { 01218 if ( $first ) { 01219 $first = false; 01220 } else { 01221 $out[] = $sep; 01222 } 01223 $out[] = $node; 01224 } 01225 } 01226 return new PPNode_Hash_Array( $out ); 01227 } 01228 01234 function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { 01235 $args = array_slice( func_get_args(), 3 ); 01236 $out = array( $start ); 01237 $first = true; 01238 01239 foreach ( $args as $root ) { 01240 if ( $root instanceof PPNode_Hash_Array ) { 01241 $root = $root->value; 01242 } 01243 if ( !is_array( $root ) ) { 01244 $root = array( $root ); 01245 } 01246 foreach ( $root as $node ) { 01247 if ( $first ) { 01248 $first = false; 01249 } else { 01250 $out[] = $sep; 01251 } 01252 $out[] = $node; 01253 } 01254 } 01255 $out[] = $end; 01256 return new PPNode_Hash_Array( $out ); 01257 } 01258 01259 function __toString() { 01260 return 'frame{}'; 01261 } 01262 01267 function getPDBK( $level = false ) { 01268 if ( $level === false ) { 01269 return $this->title->getPrefixedDBkey(); 01270 } else { 01271 return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false; 01272 } 01273 } 01274 01278 function getArguments() { 01279 return array(); 01280 } 01281 01285 function getNumberedArguments() { 01286 return array(); 01287 } 01288 01292 function getNamedArguments() { 01293 return array(); 01294 } 01295 01301 function isEmpty() { 01302 return true; 01303 } 01304 01309 function getArgument( $name ) { 01310 return false; 01311 } 01312 01320 function loopCheck( $title ) { 01321 return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); 01322 } 01323 01329 function isTemplate() { 01330 return false; 01331 } 01332 01338 function getTitle() { 01339 return $this->title; 01340 } 01341 } 01342 01347 class PPTemplateFrame_Hash extends PPFrame_Hash { 01348 var $numberedArgs, $namedArgs, $parent; 01349 var $numberedExpansionCache, $namedExpansionCache; 01350 01358 function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) { 01359 parent::__construct( $preprocessor ); 01360 01361 $this->parent = $parent; 01362 $this->numberedArgs = $numberedArgs; 01363 $this->namedArgs = $namedArgs; 01364 $this->title = $title; 01365 $pdbk = $title ? $title->getPrefixedDBkey() : false; 01366 $this->titleCache = $parent->titleCache; 01367 $this->titleCache[] = $pdbk; 01368 $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; 01369 if ( $pdbk !== false ) { 01370 $this->loopCheckHash[$pdbk] = true; 01371 } 01372 $this->depth = $parent->depth + 1; 01373 $this->numberedExpansionCache = $this->namedExpansionCache = array(); 01374 } 01375 01376 function __toString() { 01377 $s = 'tplframe{'; 01378 $first = true; 01379 $args = $this->numberedArgs + $this->namedArgs; 01380 foreach ( $args as $name => $value ) { 01381 if ( $first ) { 01382 $first = false; 01383 } else { 01384 $s .= ', '; 01385 } 01386 $s .= "\"$name\":\"" . 01387 str_replace( '"', '\\"', $value->__toString() ) . '"'; 01388 } 01389 $s .= '}'; 01390 return $s; 01391 } 01397 function isEmpty() { 01398 return !count( $this->numberedArgs ) && !count( $this->namedArgs ); 01399 } 01400 01404 function getArguments() { 01405 $arguments = array(); 01406 foreach ( array_merge( 01407 array_keys( $this->numberedArgs ), 01408 array_keys( $this->namedArgs ) ) as $key ) { 01409 $arguments[$key] = $this->getArgument( $key ); 01410 } 01411 return $arguments; 01412 } 01413 01417 function getNumberedArguments() { 01418 $arguments = array(); 01419 foreach ( array_keys( $this->numberedArgs ) as $key ) { 01420 $arguments[$key] = $this->getArgument( $key ); 01421 } 01422 return $arguments; 01423 } 01424 01428 function getNamedArguments() { 01429 $arguments = array(); 01430 foreach ( array_keys( $this->namedArgs ) as $key ) { 01431 $arguments[$key] = $this->getArgument( $key ); 01432 } 01433 return $arguments; 01434 } 01435 01440 function getNumberedArgument( $index ) { 01441 if ( !isset( $this->numberedArgs[$index] ) ) { 01442 return false; 01443 } 01444 if ( !isset( $this->numberedExpansionCache[$index] ) ) { 01445 # No trimming for unnamed arguments 01446 $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS ); 01447 } 01448 return $this->numberedExpansionCache[$index]; 01449 } 01450 01455 function getNamedArgument( $name ) { 01456 if ( !isset( $this->namedArgs[$name] ) ) { 01457 return false; 01458 } 01459 if ( !isset( $this->namedExpansionCache[$name] ) ) { 01460 # Trim named arguments post-expand, for backwards compatibility 01461 $this->namedExpansionCache[$name] = trim( 01462 $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) ); 01463 } 01464 return $this->namedExpansionCache[$name]; 01465 } 01466 01471 function getArgument( $name ) { 01472 $text = $this->getNumberedArgument( $name ); 01473 if ( $text === false ) { 01474 $text = $this->getNamedArgument( $name ); 01475 } 01476 return $text; 01477 } 01478 01484 function isTemplate() { 01485 return true; 01486 } 01487 } 01488 01493 class PPCustomFrame_Hash extends PPFrame_Hash { 01494 var $args; 01495 01496 function __construct( $preprocessor, $args ) { 01497 parent::__construct( $preprocessor ); 01498 $this->args = $args; 01499 } 01500 01501 function __toString() { 01502 $s = 'cstmframe{'; 01503 $first = true; 01504 foreach ( $this->args as $name => $value ) { 01505 if ( $first ) { 01506 $first = false; 01507 } else { 01508 $s .= ', '; 01509 } 01510 $s .= "\"$name\":\"" . 01511 str_replace( '"', '\\"', $value->__toString() ) . '"'; 01512 } 01513 $s .= '}'; 01514 return $s; 01515 } 01516 01520 function isEmpty() { 01521 return !count( $this->args ); 01522 } 01523 01528 function getArgument( $index ) { 01529 if ( !isset( $this->args[$index] ) ) { 01530 return false; 01531 } 01532 return $this->args[$index]; 01533 } 01534 01535 function getArguments() { 01536 return $this->args; 01537 } 01538 } 01539 01543 class PPNode_Hash_Tree implements PPNode { 01544 var $name, $firstChild, $lastChild, $nextSibling; 01545 01546 function __construct( $name ) { 01547 $this->name = $name; 01548 $this->firstChild = $this->lastChild = $this->nextSibling = false; 01549 } 01550 01551 function __toString() { 01552 $inner = ''; 01553 $attribs = ''; 01554 for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) { 01555 if ( $node instanceof PPNode_Hash_Attr ) { 01556 $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"'; 01557 } else { 01558 $inner .= $node->__toString(); 01559 } 01560 } 01561 if ( $inner === '' ) { 01562 return "<{$this->name}$attribs/>"; 01563 } else { 01564 return "<{$this->name}$attribs>$inner</{$this->name}>"; 01565 } 01566 } 01567 01573 static function newWithText( $name, $text ) { 01574 $obj = new self( $name ); 01575 $obj->addChild( new PPNode_Hash_Text( $text ) ); 01576 return $obj; 01577 } 01578 01579 function addChild( $node ) { 01580 if ( $this->lastChild === false ) { 01581 $this->firstChild = $this->lastChild = $node; 01582 } else { 01583 $this->lastChild->nextSibling = $node; 01584 $this->lastChild = $node; 01585 } 01586 } 01587 01591 function getChildren() { 01592 $children = array(); 01593 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01594 $children[] = $child; 01595 } 01596 return new PPNode_Hash_Array( $children ); 01597 } 01598 01599 function getFirstChild() { 01600 return $this->firstChild; 01601 } 01602 01603 function getNextSibling() { 01604 return $this->nextSibling; 01605 } 01606 01607 function getChildrenOfType( $name ) { 01608 $children = array(); 01609 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01610 if ( isset( $child->name ) && $child->name === $name ) { 01611 $children[] = $child; 01612 } 01613 } 01614 return $children; 01615 } 01616 01620 function getLength() { 01621 return false; 01622 } 01623 01628 function item( $i ) { 01629 return false; 01630 } 01631 01635 function getName() { 01636 return $this->name; 01637 } 01638 01648 function splitArg() { 01649 $bits = array(); 01650 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01651 if ( !isset( $child->name ) ) { 01652 continue; 01653 } 01654 if ( $child->name === 'name' ) { 01655 $bits['name'] = $child; 01656 if ( $child->firstChild instanceof PPNode_Hash_Attr 01657 && $child->firstChild->name === 'index' ) 01658 { 01659 $bits['index'] = $child->firstChild->value; 01660 } 01661 } elseif ( $child->name === 'value' ) { 01662 $bits['value'] = $child; 01663 } 01664 } 01665 01666 if ( !isset( $bits['name'] ) ) { 01667 throw new MWException( 'Invalid brace node passed to ' . __METHOD__ ); 01668 } 01669 if ( !isset( $bits['index'] ) ) { 01670 $bits['index'] = ''; 01671 } 01672 return $bits; 01673 } 01674 01682 function splitExt() { 01683 $bits = array(); 01684 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01685 if ( !isset( $child->name ) ) { 01686 continue; 01687 } 01688 if ( $child->name == 'name' ) { 01689 $bits['name'] = $child; 01690 } elseif ( $child->name == 'attr' ) { 01691 $bits['attr'] = $child; 01692 } elseif ( $child->name == 'inner' ) { 01693 $bits['inner'] = $child; 01694 } elseif ( $child->name == 'close' ) { 01695 $bits['close'] = $child; 01696 } 01697 } 01698 if ( !isset( $bits['name'] ) ) { 01699 throw new MWException( 'Invalid ext node passed to ' . __METHOD__ ); 01700 } 01701 return $bits; 01702 } 01703 01710 function splitHeading() { 01711 if ( $this->name !== 'h' ) { 01712 throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); 01713 } 01714 $bits = array(); 01715 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01716 if ( !isset( $child->name ) ) { 01717 continue; 01718 } 01719 if ( $child->name == 'i' ) { 01720 $bits['i'] = $child->value; 01721 } elseif ( $child->name == 'level' ) { 01722 $bits['level'] = $child->value; 01723 } 01724 } 01725 if ( !isset( $bits['i'] ) ) { 01726 throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); 01727 } 01728 return $bits; 01729 } 01730 01737 function splitTemplate() { 01738 $parts = array(); 01739 $bits = array( 'lineStart' => '' ); 01740 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01741 if ( !isset( $child->name ) ) { 01742 continue; 01743 } 01744 if ( $child->name == 'title' ) { 01745 $bits['title'] = $child; 01746 } 01747 if ( $child->name == 'part' ) { 01748 $parts[] = $child; 01749 } 01750 if ( $child->name == 'lineStart' ) { 01751 $bits['lineStart'] = '1'; 01752 } 01753 } 01754 if ( !isset( $bits['title'] ) ) { 01755 throw new MWException( 'Invalid node passed to ' . __METHOD__ ); 01756 } 01757 $bits['parts'] = new PPNode_Hash_Array( $parts ); 01758 return $bits; 01759 } 01760 } 01761 01765 class PPNode_Hash_Text implements PPNode { 01766 var $value, $nextSibling; 01767 01768 function __construct( $value ) { 01769 if ( is_object( $value ) ) { 01770 throw new MWException( __CLASS__ . ' given object instead of string' ); 01771 } 01772 $this->value = $value; 01773 } 01774 01775 function __toString() { 01776 return htmlspecialchars( $this->value ); 01777 } 01778 01779 function getNextSibling() { 01780 return $this->nextSibling; 01781 } 01782 01783 function getChildren() { return false; } 01784 function getFirstChild() { return false; } 01785 function getChildrenOfType( $name ) { return false; } 01786 function getLength() { return false; } 01787 function item( $i ) { return false; } 01788 function getName() { return '#text'; } 01789 function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } 01790 function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } 01791 function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } 01792 } 01793 01797 class PPNode_Hash_Array implements PPNode { 01798 var $value, $nextSibling; 01799 01800 function __construct( $value ) { 01801 $this->value = $value; 01802 } 01803 01804 function __toString() { 01805 return var_export( $this, true ); 01806 } 01807 01808 function getLength() { 01809 return count( $this->value ); 01810 } 01811 01812 function item( $i ) { 01813 return $this->value[$i]; 01814 } 01815 01816 function getName() { return '#nodelist'; } 01817 01818 function getNextSibling() { 01819 return $this->nextSibling; 01820 } 01821 01822 function getChildren() { return false; } 01823 function getFirstChild() { return false; } 01824 function getChildrenOfType( $name ) { return false; } 01825 function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } 01826 function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } 01827 function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } 01828 } 01829 01833 class PPNode_Hash_Attr implements PPNode { 01834 var $name, $value, $nextSibling; 01835 01836 function __construct( $name, $value ) { 01837 $this->name = $name; 01838 $this->value = $value; 01839 } 01840 01841 function __toString() { 01842 return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>"; 01843 } 01844 01845 function getName() { 01846 return $this->name; 01847 } 01848 01849 function getNextSibling() { 01850 return $this->nextSibling; 01851 } 01852 01853 function getChildren() { return false; } 01854 function getFirstChild() { return false; } 01855 function getChildrenOfType( $name ) { return false; } 01856 function getLength() { return false; } 01857 function item( $i ) { return false; } 01858 function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } 01859 function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } 01860 function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } 01861 }