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