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