MediaWiki
REL1_24
|
00001 <?php 00031 class Preprocessor_Hash implements Preprocessor { 00032 // @codingStandardsIgnoreEnd 00033 00037 public $parser; 00038 00039 const CACHE_VERSION = 1; 00040 00041 public function __construct( $parser ) { 00042 $this->parser = $parser; 00043 } 00044 00048 public function newFrame() { 00049 return new PPFrame_Hash( $this ); 00050 } 00051 00056 public function newCustomFrame( $args ) { 00057 return new PPCustomFrame_Hash( $this, $args ); 00058 } 00059 00064 public function newPartNodeArray( $values ) { 00065 $list = array(); 00066 00067 foreach ( $values as $k => $val ) { 00068 $partNode = new PPNode_Hash_Tree( 'part' ); 00069 $nameNode = new PPNode_Hash_Tree( 'name' ); 00070 00071 if ( is_int( $k ) ) { 00072 $nameNode->addChild( new PPNode_Hash_Attr( 'index', $k ) ); 00073 $partNode->addChild( $nameNode ); 00074 } else { 00075 $nameNode->addChild( new PPNode_Hash_Text( $k ) ); 00076 $partNode->addChild( $nameNode ); 00077 $partNode->addChild( new PPNode_Hash_Text( '=' ) ); 00078 } 00079 00080 $valueNode = new PPNode_Hash_Tree( 'value' ); 00081 $valueNode->addChild( new PPNode_Hash_Text( $val ) ); 00082 $partNode->addChild( $valueNode ); 00083 00084 $list[] = $partNode; 00085 } 00086 00087 $node = new PPNode_Hash_Array( $list ); 00088 return $node; 00089 } 00090 00114 public function preprocessToObj( $text, $flags = 0 ) { 00115 wfProfileIn( __METHOD__ ); 00116 00117 // Check cache. 00118 global $wgMemc, $wgPreprocessorCacheThreshold; 00119 00120 $cacheable = $wgPreprocessorCacheThreshold !== false 00121 && strlen( $text ) > $wgPreprocessorCacheThreshold; 00122 00123 if ( $cacheable ) { 00124 wfProfileIn( __METHOD__ . '-cacheable' ); 00125 00126 $cacheKey = wfMemcKey( 'preprocess-hash', md5( $text ), $flags ); 00127 $cacheValue = $wgMemc->get( $cacheKey ); 00128 if ( $cacheValue ) { 00129 $version = substr( $cacheValue, 0, 8 ); 00130 if ( intval( $version ) == self::CACHE_VERSION ) { 00131 $hash = unserialize( substr( $cacheValue, 8 ) ); 00132 // From the cache 00133 wfDebugLog( "Preprocessor", 00134 "Loaded preprocessor hash from memcached (key $cacheKey)" ); 00135 wfProfileOut( __METHOD__ . '-cacheable' ); 00136 wfProfileOut( __METHOD__ ); 00137 return $hash; 00138 } 00139 } 00140 wfProfileIn( __METHOD__ . '-cache-miss' ); 00141 } 00142 00143 $rules = array( 00144 '{' => array( 00145 'end' => '}', 00146 'names' => array( 00147 2 => 'template', 00148 3 => 'tplarg', 00149 ), 00150 'min' => 2, 00151 'max' => 3, 00152 ), 00153 '[' => array( 00154 'end' => ']', 00155 'names' => array( 2 => null ), 00156 'min' => 2, 00157 'max' => 2, 00158 ) 00159 ); 00160 00161 $forInclusion = $flags & Parser::PTD_FOR_INCLUSION; 00162 00163 $xmlishElements = $this->parser->getStripList(); 00164 $enableOnlyinclude = false; 00165 if ( $forInclusion ) { 00166 $ignoredTags = array( 'includeonly', '/includeonly' ); 00167 $ignoredElements = array( 'noinclude' ); 00168 $xmlishElements[] = 'noinclude'; 00169 if ( strpos( $text, '<onlyinclude>' ) !== false 00170 && strpos( $text, '</onlyinclude>' ) !== false 00171 ) { 00172 $enableOnlyinclude = true; 00173 } 00174 } else { 00175 $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ); 00176 $ignoredElements = array( 'includeonly' ); 00177 $xmlishElements[] = 'includeonly'; 00178 } 00179 $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) ); 00180 00181 // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset 00182 $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA"; 00183 00184 $stack = new PPDStack_Hash; 00185 00186 $searchBase = "[{<\n"; 00187 // For fast reverse searches 00188 $revText = strrev( $text ); 00189 $lengthText = strlen( $text ); 00190 00191 // Input pointer, starts out pointing to a pseudo-newline before the start 00192 $i = 0; 00193 // Current accumulator 00194 $accum =& $stack->getAccum(); 00195 // True to find equals signs in arguments 00196 $findEquals = false; 00197 // True to take notice of pipe characters 00198 $findPipe = false; 00199 $headingIndex = 1; 00200 // True if $i is inside a possible heading 00201 $inHeading = false; 00202 // True if there are no more greater-than (>) signs right of $i 00203 $noMoreGT = false; 00204 // True to ignore all input up to the next <onlyinclude> 00205 $findOnlyinclude = $enableOnlyinclude; 00206 // Do a line-start run without outputting an LF character 00207 $fakeLineStart = true; 00208 00209 while ( true ) { 00210 //$this->memCheck(); 00211 00212 if ( $findOnlyinclude ) { 00213 // Ignore all input up to the next <onlyinclude> 00214 $startPos = strpos( $text, '<onlyinclude>', $i ); 00215 if ( $startPos === false ) { 00216 // Ignored section runs to the end 00217 $accum->addNodeWithText( 'ignore', substr( $text, $i ) ); 00218 break; 00219 } 00220 $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end 00221 $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i ) ); 00222 $i = $tagEndPos; 00223 $findOnlyinclude = false; 00224 } 00225 00226 if ( $fakeLineStart ) { 00227 $found = 'line-start'; 00228 $curChar = ''; 00229 } else { 00230 # Find next opening brace, closing brace or pipe 00231 $search = $searchBase; 00232 if ( $stack->top === false ) { 00233 $currentClosing = ''; 00234 } else { 00235 $currentClosing = $stack->top->close; 00236 $search .= $currentClosing; 00237 } 00238 if ( $findPipe ) { 00239 $search .= '|'; 00240 } 00241 if ( $findEquals ) { 00242 // First equals will be for the template 00243 $search .= '='; 00244 } 00245 $rule = null; 00246 # Output literal section, advance input counter 00247 $literalLength = strcspn( $text, $search, $i ); 00248 if ( $literalLength > 0 ) { 00249 $accum->addLiteral( substr( $text, $i, $literalLength ) ); 00250 $i += $literalLength; 00251 } 00252 if ( $i >= $lengthText ) { 00253 if ( $currentClosing == "\n" ) { 00254 // Do a past-the-end run to finish off the heading 00255 $curChar = ''; 00256 $found = 'line-end'; 00257 } else { 00258 # All done 00259 break; 00260 } 00261 } else { 00262 $curChar = $text[$i]; 00263 if ( $curChar == '|' ) { 00264 $found = 'pipe'; 00265 } elseif ( $curChar == '=' ) { 00266 $found = 'equals'; 00267 } elseif ( $curChar == '<' ) { 00268 $found = 'angle'; 00269 } elseif ( $curChar == "\n" ) { 00270 if ( $inHeading ) { 00271 $found = 'line-end'; 00272 } else { 00273 $found = 'line-start'; 00274 } 00275 } elseif ( $curChar == $currentClosing ) { 00276 $found = 'close'; 00277 } elseif ( isset( $rules[$curChar] ) ) { 00278 $found = 'open'; 00279 $rule = $rules[$curChar]; 00280 } else { 00281 # Some versions of PHP have a strcspn which stops on null characters 00282 # Ignore and continue 00283 ++$i; 00284 continue; 00285 } 00286 } 00287 } 00288 00289 if ( $found == 'angle' ) { 00290 $matches = false; 00291 // Handle </onlyinclude> 00292 if ( $enableOnlyinclude 00293 && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' 00294 ) { 00295 $findOnlyinclude = true; 00296 continue; 00297 } 00298 00299 // Determine element name 00300 if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) { 00301 // Element name missing or not listed 00302 $accum->addLiteral( '<' ); 00303 ++$i; 00304 continue; 00305 } 00306 // Handle comments 00307 if ( isset( $matches[2] ) && $matches[2] == '!--' ) { 00308 00309 // To avoid leaving blank lines, when a sequence of 00310 // space-separated comments is both preceded and followed by 00311 // a newline (ignoring spaces), then 00312 // trim leading and trailing spaces and the trailing newline. 00313 00314 // Find the end 00315 $endPos = strpos( $text, '-->', $i + 4 ); 00316 if ( $endPos === false ) { 00317 // Unclosed comment in input, runs to end 00318 $inner = substr( $text, $i ); 00319 $accum->addNodeWithText( 'comment', $inner ); 00320 $i = $lengthText; 00321 } else { 00322 // Search backwards for leading whitespace 00323 $wsStart = $i ? ( $i - strspn( $revText, " \t", $lengthText - $i ) ) : 0; 00324 00325 // Search forwards for trailing whitespace 00326 // $wsEnd will be the position of the last space (or the '>' if there's none) 00327 $wsEnd = $endPos + 2 + strspn( $text, " \t", $endPos + 3 ); 00328 00329 // Keep looking forward as long as we're finding more 00330 // comments. 00331 $comments = array( array( $wsStart, $wsEnd ) ); 00332 while ( substr( $text, $wsEnd + 1, 4 ) == '<!--' ) { 00333 $c = strpos( $text, '-->', $wsEnd + 4 ); 00334 if ( $c === false ) { 00335 break; 00336 } 00337 $c = $c + 2 + strspn( $text, " \t", $c + 3 ); 00338 $comments[] = array( $wsEnd + 1, $c ); 00339 $wsEnd = $c; 00340 } 00341 00342 // Eat the line if possible 00343 // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at 00344 // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but 00345 // it's a possible beneficial b/c break. 00346 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" 00347 && substr( $text, $wsEnd + 1, 1 ) == "\n" 00348 ) { 00349 // Remove leading whitespace from the end of the accumulator 00350 // Sanity check first though 00351 $wsLength = $i - $wsStart; 00352 if ( $wsLength > 0 00353 && $accum->lastNode instanceof PPNode_Hash_Text 00354 && strspn( $accum->lastNode->value, " \t", -$wsLength ) === $wsLength 00355 ) { 00356 $accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength ); 00357 } 00358 00359 // Dump all but the last comment to the accumulator 00360 foreach ( $comments as $j => $com ) { 00361 $startPos = $com[0]; 00362 $endPos = $com[1] + 1; 00363 if ( $j == ( count( $comments ) - 1 ) ) { 00364 break; 00365 } 00366 $inner = substr( $text, $startPos, $endPos - $startPos ); 00367 $accum->addNodeWithText( 'comment', $inner ); 00368 } 00369 00370 // Do a line-start run next time to look for headings after the comment 00371 $fakeLineStart = true; 00372 } else { 00373 // No line to eat, just take the comment itself 00374 $startPos = $i; 00375 $endPos += 2; 00376 } 00377 00378 if ( $stack->top ) { 00379 $part = $stack->top->getCurrentPart(); 00380 if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) { 00381 $part->visualEnd = $wsStart; 00382 } 00383 // Else comments abutting, no change in visual end 00384 $part->commentEnd = $endPos; 00385 } 00386 $i = $endPos + 1; 00387 $inner = substr( $text, $startPos, $endPos - $startPos + 1 ); 00388 $accum->addNodeWithText( 'comment', $inner ); 00389 } 00390 continue; 00391 } 00392 $name = $matches[1]; 00393 $lowerName = strtolower( $name ); 00394 $attrStart = $i + strlen( $name ) + 1; 00395 00396 // Find end of tag 00397 $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart ); 00398 if ( $tagEndPos === false ) { 00399 // Infinite backtrack 00400 // Disable tag search to prevent worst-case O(N^2) performance 00401 $noMoreGT = true; 00402 $accum->addLiteral( '<' ); 00403 ++$i; 00404 continue; 00405 } 00406 00407 // Handle ignored tags 00408 if ( in_array( $lowerName, $ignoredTags ) ) { 00409 $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i + 1 ) ); 00410 $i = $tagEndPos + 1; 00411 continue; 00412 } 00413 00414 $tagStartPos = $i; 00415 if ( $text[$tagEndPos - 1] == '/' ) { 00416 // Short end tag 00417 $attrEnd = $tagEndPos - 1; 00418 $inner = null; 00419 $i = $tagEndPos + 1; 00420 $close = null; 00421 } else { 00422 $attrEnd = $tagEndPos; 00423 // Find closing tag 00424 if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", 00425 $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) 00426 ) { 00427 $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); 00428 $i = $matches[0][1] + strlen( $matches[0][0] ); 00429 $close = $matches[0][0]; 00430 } else { 00431 // No end tag -- let it run out to the end of the text. 00432 $inner = substr( $text, $tagEndPos + 1 ); 00433 $i = $lengthText; 00434 $close = null; 00435 } 00436 } 00437 // <includeonly> and <noinclude> just become <ignore> tags 00438 if ( in_array( $lowerName, $ignoredElements ) ) { 00439 $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) ); 00440 continue; 00441 } 00442 00443 if ( $attrEnd <= $attrStart ) { 00444 $attr = ''; 00445 } else { 00446 // Note that the attr element contains the whitespace between name and attribute, 00447 // this is necessary for precise reconstruction during pre-save transform. 00448 $attr = substr( $text, $attrStart, $attrEnd - $attrStart ); 00449 } 00450 00451 $extNode = new PPNode_Hash_Tree( 'ext' ); 00452 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'name', $name ) ); 00453 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'attr', $attr ) ); 00454 if ( $inner !== null ) { 00455 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'inner', $inner ) ); 00456 } 00457 if ( $close !== null ) { 00458 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) ); 00459 } 00460 $accum->addNode( $extNode ); 00461 } elseif ( $found == 'line-start' ) { 00462 // Is this the start of a heading? 00463 // Line break belongs before the heading element in any case 00464 if ( $fakeLineStart ) { 00465 $fakeLineStart = false; 00466 } else { 00467 $accum->addLiteral( $curChar ); 00468 $i++; 00469 } 00470 00471 $count = strspn( $text, '=', $i, 6 ); 00472 if ( $count == 1 && $findEquals ) { 00473 // DWIM: This looks kind of like a name/value separator. 00474 // Let's let the equals handler have it and break the potential 00475 // heading. This is heuristic, but AFAICT the methods for 00476 // completely correct disambiguation are very complex. 00477 } elseif ( $count > 0 ) { 00478 $piece = array( 00479 'open' => "\n", 00480 'close' => "\n", 00481 'parts' => array( new PPDPart_Hash( str_repeat( '=', $count ) ) ), 00482 'startPos' => $i, 00483 'count' => $count ); 00484 $stack->push( $piece ); 00485 $accum =& $stack->getAccum(); 00486 extract( $stack->getFlags() ); 00487 $i += $count; 00488 } 00489 } elseif ( $found == 'line-end' ) { 00490 $piece = $stack->top; 00491 // A heading must be open, otherwise \n wouldn't have been in the search list 00492 assert( '$piece->open == "\n"' ); 00493 $part = $piece->getCurrentPart(); 00494 // Search back through the input to see if it has a proper close. 00495 // Do this using the reversed string since the other solutions 00496 // (end anchor, etc.) are inefficient. 00497 $wsLength = strspn( $revText, " \t", $lengthText - $i ); 00498 $searchStart = $i - $wsLength; 00499 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { 00500 // Comment found at line end 00501 // Search for equals signs before the comment 00502 $searchStart = $part->visualEnd; 00503 $searchStart -= strspn( $revText, " \t", $lengthText - $searchStart ); 00504 } 00505 $count = $piece->count; 00506 $equalsLength = strspn( $revText, '=', $lengthText - $searchStart ); 00507 if ( $equalsLength > 0 ) { 00508 if ( $searchStart - $equalsLength == $piece->startPos ) { 00509 // This is just a single string of equals signs on its own line 00510 // Replicate the doHeadings behavior /={count}(.+)={count}/ 00511 // First find out how many equals signs there really are (don't stop at 6) 00512 $count = $equalsLength; 00513 if ( $count < 3 ) { 00514 $count = 0; 00515 } else { 00516 $count = min( 6, intval( ( $count - 1 ) / 2 ) ); 00517 } 00518 } else { 00519 $count = min( $equalsLength, $count ); 00520 } 00521 if ( $count > 0 ) { 00522 // Normal match, output <h> 00523 $element = new PPNode_Hash_Tree( 'possible-h' ); 00524 $element->addChild( new PPNode_Hash_Attr( 'level', $count ) ); 00525 $element->addChild( new PPNode_Hash_Attr( 'i', $headingIndex++ ) ); 00526 $element->lastChild->nextSibling = $accum->firstNode; 00527 $element->lastChild = $accum->lastNode; 00528 } else { 00529 // Single equals sign on its own line, count=0 00530 $element = $accum; 00531 } 00532 } else { 00533 // No match, no <h>, just pass down the inner text 00534 $element = $accum; 00535 } 00536 // Unwind the stack 00537 $stack->pop(); 00538 $accum =& $stack->getAccum(); 00539 extract( $stack->getFlags() ); 00540 00541 // Append the result to the enclosing accumulator 00542 if ( $element instanceof PPNode ) { 00543 $accum->addNode( $element ); 00544 } else { 00545 $accum->addAccum( $element ); 00546 } 00547 // Note that we do NOT increment the input pointer. 00548 // This is because the closing linebreak could be the opening linebreak of 00549 // another heading. Infinite loops are avoided because the next iteration MUST 00550 // hit the heading open case above, which unconditionally increments the 00551 // input pointer. 00552 } elseif ( $found == 'open' ) { 00553 # count opening brace characters 00554 $count = strspn( $text, $curChar, $i ); 00555 00556 # we need to add to stack only if opening brace count is enough for one of the rules 00557 if ( $count >= $rule['min'] ) { 00558 # Add it to the stack 00559 $piece = array( 00560 'open' => $curChar, 00561 'close' => $rule['end'], 00562 'count' => $count, 00563 'lineStart' => ( $i > 0 && $text[$i - 1] == "\n" ), 00564 ); 00565 00566 $stack->push( $piece ); 00567 $accum =& $stack->getAccum(); 00568 extract( $stack->getFlags() ); 00569 } else { 00570 # Add literal brace(s) 00571 $accum->addLiteral( str_repeat( $curChar, $count ) ); 00572 } 00573 $i += $count; 00574 } elseif ( $found == 'close' ) { 00575 $piece = $stack->top; 00576 # lets check if there are enough characters for closing brace 00577 $maxCount = $piece->count; 00578 $count = strspn( $text, $curChar, $i, $maxCount ); 00579 00580 # check for maximum matching characters (if there are 5 closing 00581 # characters, we will probably need only 3 - depending on the rules) 00582 $rule = $rules[$piece->open]; 00583 if ( $count > $rule['max'] ) { 00584 # The specified maximum exists in the callback array, unless the caller 00585 # has made an error 00586 $matchingCount = $rule['max']; 00587 } else { 00588 # Count is less than the maximum 00589 # Skip any gaps in the callback array to find the true largest match 00590 # Need to use array_key_exists not isset because the callback can be null 00591 $matchingCount = $count; 00592 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) { 00593 --$matchingCount; 00594 } 00595 } 00596 00597 if ( $matchingCount <= 0 ) { 00598 # No matching element found in callback array 00599 # Output a literal closing brace and continue 00600 $accum->addLiteral( str_repeat( $curChar, $count ) ); 00601 $i += $count; 00602 continue; 00603 } 00604 $name = $rule['names'][$matchingCount]; 00605 if ( $name === null ) { 00606 // No element, just literal text 00607 $element = $piece->breakSyntax( $matchingCount ); 00608 $element->addLiteral( str_repeat( $rule['end'], $matchingCount ) ); 00609 } else { 00610 # Create XML element 00611 # Note: $parts is already XML, does not need to be encoded further 00612 $parts = $piece->parts; 00613 $titleAccum = $parts[0]->out; 00614 unset( $parts[0] ); 00615 00616 $element = new PPNode_Hash_Tree( $name ); 00617 00618 # The invocation is at the start of the line if lineStart is set in 00619 # the stack, and all opening brackets are used up. 00620 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) { 00621 $element->addChild( new PPNode_Hash_Attr( 'lineStart', 1 ) ); 00622 } 00623 $titleNode = new PPNode_Hash_Tree( 'title' ); 00624 $titleNode->firstChild = $titleAccum->firstNode; 00625 $titleNode->lastChild = $titleAccum->lastNode; 00626 $element->addChild( $titleNode ); 00627 $argIndex = 1; 00628 foreach ( $parts as $part ) { 00629 if ( isset( $part->eqpos ) ) { 00630 // Find equals 00631 $lastNode = false; 00632 for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) { 00633 if ( $node === $part->eqpos ) { 00634 break; 00635 } 00636 $lastNode = $node; 00637 } 00638 if ( !$node ) { 00639 if ( $cacheable ) { 00640 wfProfileOut( __METHOD__ . '-cache-miss' ); 00641 wfProfileOut( __METHOD__ . '-cacheable' ); 00642 } 00643 wfProfileOut( __METHOD__ ); 00644 throw new MWException( __METHOD__ . ': eqpos not found' ); 00645 } 00646 if ( $node->name !== 'equals' ) { 00647 if ( $cacheable ) { 00648 wfProfileOut( __METHOD__ . '-cache-miss' ); 00649 wfProfileOut( __METHOD__ . '-cacheable' ); 00650 } 00651 wfProfileOut( __METHOD__ ); 00652 throw new MWException( __METHOD__ . ': eqpos is not equals' ); 00653 } 00654 $equalsNode = $node; 00655 00656 // Construct name node 00657 $nameNode = new PPNode_Hash_Tree( 'name' ); 00658 if ( $lastNode !== false ) { 00659 $lastNode->nextSibling = false; 00660 $nameNode->firstChild = $part->out->firstNode; 00661 $nameNode->lastChild = $lastNode; 00662 } 00663 00664 // Construct value node 00665 $valueNode = new PPNode_Hash_Tree( 'value' ); 00666 if ( $equalsNode->nextSibling !== false ) { 00667 $valueNode->firstChild = $equalsNode->nextSibling; 00668 $valueNode->lastChild = $part->out->lastNode; 00669 } 00670 $partNode = new PPNode_Hash_Tree( 'part' ); 00671 $partNode->addChild( $nameNode ); 00672 $partNode->addChild( $equalsNode->firstChild ); 00673 $partNode->addChild( $valueNode ); 00674 $element->addChild( $partNode ); 00675 } else { 00676 $partNode = new PPNode_Hash_Tree( 'part' ); 00677 $nameNode = new PPNode_Hash_Tree( 'name' ); 00678 $nameNode->addChild( new PPNode_Hash_Attr( 'index', $argIndex++ ) ); 00679 $valueNode = new PPNode_Hash_Tree( 'value' ); 00680 $valueNode->firstChild = $part->out->firstNode; 00681 $valueNode->lastChild = $part->out->lastNode; 00682 $partNode->addChild( $nameNode ); 00683 $partNode->addChild( $valueNode ); 00684 $element->addChild( $partNode ); 00685 } 00686 } 00687 } 00688 00689 # Advance input pointer 00690 $i += $matchingCount; 00691 00692 # Unwind the stack 00693 $stack->pop(); 00694 $accum =& $stack->getAccum(); 00695 00696 # Re-add the old stack element if it still has unmatched opening characters remaining 00697 if ( $matchingCount < $piece->count ) { 00698 $piece->parts = array( new PPDPart_Hash ); 00699 $piece->count -= $matchingCount; 00700 # do we still qualify for any callback with remaining count? 00701 $min = $rules[$piece->open]['min']; 00702 if ( $piece->count >= $min ) { 00703 $stack->push( $piece ); 00704 $accum =& $stack->getAccum(); 00705 } else { 00706 $accum->addLiteral( str_repeat( $piece->open, $piece->count ) ); 00707 } 00708 } 00709 00710 extract( $stack->getFlags() ); 00711 00712 # Add XML element to the enclosing accumulator 00713 if ( $element instanceof PPNode ) { 00714 $accum->addNode( $element ); 00715 } else { 00716 $accum->addAccum( $element ); 00717 } 00718 } elseif ( $found == 'pipe' ) { 00719 $findEquals = true; // shortcut for getFlags() 00720 $stack->addPart(); 00721 $accum =& $stack->getAccum(); 00722 ++$i; 00723 } elseif ( $found == 'equals' ) { 00724 $findEquals = false; // shortcut for getFlags() 00725 $accum->addNodeWithText( 'equals', '=' ); 00726 $stack->getCurrentPart()->eqpos = $accum->lastNode; 00727 ++$i; 00728 } 00729 } 00730 00731 # Output any remaining unclosed brackets 00732 foreach ( $stack->stack as $piece ) { 00733 $stack->rootAccum->addAccum( $piece->breakSyntax() ); 00734 } 00735 00736 # Enable top-level headings 00737 for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) { 00738 if ( isset( $node->name ) && $node->name === 'possible-h' ) { 00739 $node->name = 'h'; 00740 } 00741 } 00742 00743 $rootNode = new PPNode_Hash_Tree( 'root' ); 00744 $rootNode->firstChild = $stack->rootAccum->firstNode; 00745 $rootNode->lastChild = $stack->rootAccum->lastNode; 00746 00747 // Cache 00748 if ( $cacheable ) { 00749 $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode ); 00750 $wgMemc->set( $cacheKey, $cacheValue, 86400 ); 00751 wfProfileOut( __METHOD__ . '-cache-miss' ); 00752 wfProfileOut( __METHOD__ . '-cacheable' ); 00753 wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" ); 00754 } 00755 00756 wfProfileOut( __METHOD__ ); 00757 return $rootNode; 00758 } 00759 } 00760 00766 class PPDStack_Hash extends PPDStack { 00767 // @codingStandardsIgnoreEnd 00768 00769 public function __construct() { 00770 $this->elementClass = 'PPDStackElement_Hash'; 00771 parent::__construct(); 00772 $this->rootAccum = new PPDAccum_Hash; 00773 } 00774 } 00775 00780 class PPDStackElement_Hash extends PPDStackElement { 00781 // @codingStandardsIgnoreENd 00782 00783 public function __construct( $data = array() ) { 00784 $this->partClass = 'PPDPart_Hash'; 00785 parent::__construct( $data ); 00786 } 00787 00794 public function breakSyntax( $openingCount = false ) { 00795 if ( $this->open == "\n" ) { 00796 $accum = $this->parts[0]->out; 00797 } else { 00798 if ( $openingCount === false ) { 00799 $openingCount = $this->count; 00800 } 00801 $accum = new PPDAccum_Hash; 00802 $accum->addLiteral( str_repeat( $this->open, $openingCount ) ); 00803 $first = true; 00804 foreach ( $this->parts as $part ) { 00805 if ( $first ) { 00806 $first = false; 00807 } else { 00808 $accum->addLiteral( '|' ); 00809 } 00810 $accum->addAccum( $part->out ); 00811 } 00812 } 00813 return $accum; 00814 } 00815 } 00816 00821 class PPDPart_Hash extends PPDPart { 00822 // @codingStandardsIgnoreEnd 00823 00824 public function __construct( $out = '' ) { 00825 $accum = new PPDAccum_Hash; 00826 if ( $out !== '' ) { 00827 $accum->addLiteral( $out ); 00828 } 00829 parent::__construct( $accum ); 00830 } 00831 } 00832 00837 class PPDAccum_Hash { 00838 // @codingStandardsIgnoreEnd 00839 00840 public $firstNode, $lastNode; 00841 00842 public function __construct() { 00843 $this->firstNode = $this->lastNode = false; 00844 } 00845 00850 public function addLiteral( $s ) { 00851 if ( $this->lastNode === false ) { 00852 $this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s ); 00853 } elseif ( $this->lastNode instanceof PPNode_Hash_Text ) { 00854 $this->lastNode->value .= $s; 00855 } else { 00856 $this->lastNode->nextSibling = new PPNode_Hash_Text( $s ); 00857 $this->lastNode = $this->lastNode->nextSibling; 00858 } 00859 } 00860 00865 public function addNode( PPNode $node ) { 00866 if ( $this->lastNode === false ) { 00867 $this->firstNode = $this->lastNode = $node; 00868 } else { 00869 $this->lastNode->nextSibling = $node; 00870 $this->lastNode = $node; 00871 } 00872 } 00873 00879 public function addNodeWithText( $name, $value ) { 00880 $node = PPNode_Hash_Tree::newWithText( $name, $value ); 00881 $this->addNode( $node ); 00882 } 00883 00890 public function addAccum( $accum ) { 00891 if ( $accum->lastNode === false ) { 00892 // nothing to add 00893 } elseif ( $this->lastNode === false ) { 00894 $this->firstNode = $accum->firstNode; 00895 $this->lastNode = $accum->lastNode; 00896 } else { 00897 $this->lastNode->nextSibling = $accum->firstNode; 00898 $this->lastNode = $accum->lastNode; 00899 } 00900 } 00901 } 00902 00908 class PPFrame_Hash implements PPFrame { 00909 // @codingStandardsIgnoreEnd 00910 00914 public $parser; 00915 00919 public $preprocessor; 00920 00924 public $title; 00925 public $titleCache; 00926 00931 public $loopCheckHash; 00932 00937 public $depth; 00938 00939 private $volatile = false; 00940 private $ttl = null; 00941 00945 protected $childExpansionCache; 00946 00951 public function __construct( $preprocessor ) { 00952 $this->preprocessor = $preprocessor; 00953 $this->parser = $preprocessor->parser; 00954 $this->title = $this->parser->mTitle; 00955 $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); 00956 $this->loopCheckHash = array(); 00957 $this->depth = 0; 00958 $this->childExpansionCache = array(); 00959 } 00960 00971 public function newChild( $args = false, $title = false, $indexOffset = 0 ) { 00972 $namedArgs = array(); 00973 $numberedArgs = array(); 00974 if ( $title === false ) { 00975 $title = $this->title; 00976 } 00977 if ( $args !== false ) { 00978 if ( $args instanceof PPNode_Hash_Array ) { 00979 $args = $args->value; 00980 } elseif ( !is_array( $args ) ) { 00981 throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' ); 00982 } 00983 foreach ( $args as $arg ) { 00984 $bits = $arg->splitArg(); 00985 if ( $bits['index'] !== '' ) { 00986 // Numbered parameter 00987 $index = $bits['index'] - $indexOffset; 00988 $numberedArgs[$index] = $bits['value']; 00989 unset( $namedArgs[$index] ); 00990 } else { 00991 // Named parameter 00992 $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) ); 00993 $namedArgs[$name] = $bits['value']; 00994 unset( $numberedArgs[$name] ); 00995 } 00996 } 00997 } 00998 return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title ); 00999 } 01000 01008 public function cachedExpand( $key, $root, $flags = 0 ) { 01009 // we don't have a parent, so we don't have a cache 01010 return $this->expand( $root, $flags ); 01011 } 01012 01019 public function expand( $root, $flags = 0 ) { 01020 static $expansionDepth = 0; 01021 if ( is_string( $root ) ) { 01022 return $root; 01023 } 01024 01025 if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) { 01026 $this->parser->limitationWarn( 'node-count-exceeded', 01027 $this->parser->mPPNodeCount, 01028 $this->parser->mOptions->getMaxPPNodeCount() 01029 ); 01030 return '<span class="error">Node-count limit exceeded</span>'; 01031 } 01032 if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) { 01033 $this->parser->limitationWarn( 'expansion-depth-exceeded', 01034 $expansionDepth, 01035 $this->parser->mOptions->getMaxPPExpandDepth() 01036 ); 01037 return '<span class="error">Expansion depth limit exceeded</span>'; 01038 } 01039 ++$expansionDepth; 01040 if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) { 01041 $this->parser->mHighestExpansionDepth = $expansionDepth; 01042 } 01043 01044 $outStack = array( '', '' ); 01045 $iteratorStack = array( false, $root ); 01046 $indexStack = array( 0, 0 ); 01047 01048 while ( count( $iteratorStack ) > 1 ) { 01049 $level = count( $outStack ) - 1; 01050 $iteratorNode =& $iteratorStack[$level]; 01051 $out =& $outStack[$level]; 01052 $index =& $indexStack[$level]; 01053 01054 if ( is_array( $iteratorNode ) ) { 01055 if ( $index >= count( $iteratorNode ) ) { 01056 // All done with this iterator 01057 $iteratorStack[$level] = false; 01058 $contextNode = false; 01059 } else { 01060 $contextNode = $iteratorNode[$index]; 01061 $index++; 01062 } 01063 } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) { 01064 if ( $index >= $iteratorNode->getLength() ) { 01065 // All done with this iterator 01066 $iteratorStack[$level] = false; 01067 $contextNode = false; 01068 } else { 01069 $contextNode = $iteratorNode->item( $index ); 01070 $index++; 01071 } 01072 } else { 01073 // Copy to $contextNode and then delete from iterator stack, 01074 // because this is not an iterator but we do have to execute it once 01075 $contextNode = $iteratorStack[$level]; 01076 $iteratorStack[$level] = false; 01077 } 01078 01079 $newIterator = false; 01080 01081 if ( $contextNode === false ) { 01082 // nothing to do 01083 } elseif ( is_string( $contextNode ) ) { 01084 $out .= $contextNode; 01085 } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_Hash_Array ) { 01086 $newIterator = $contextNode; 01087 } elseif ( $contextNode instanceof PPNode_Hash_Attr ) { 01088 // No output 01089 } elseif ( $contextNode instanceof PPNode_Hash_Text ) { 01090 $out .= $contextNode->value; 01091 } elseif ( $contextNode instanceof PPNode_Hash_Tree ) { 01092 if ( $contextNode->name == 'template' ) { 01093 # Double-brace expansion 01094 $bits = $contextNode->splitTemplate(); 01095 if ( $flags & PPFrame::NO_TEMPLATES ) { 01096 $newIterator = $this->virtualBracketedImplode( 01097 '{{', '|', '}}', 01098 $bits['title'], 01099 $bits['parts'] 01100 ); 01101 } else { 01102 $ret = $this->parser->braceSubstitution( $bits, $this ); 01103 if ( isset( $ret['object'] ) ) { 01104 $newIterator = $ret['object']; 01105 } else { 01106 $out .= $ret['text']; 01107 } 01108 } 01109 } elseif ( $contextNode->name == 'tplarg' ) { 01110 # Triple-brace expansion 01111 $bits = $contextNode->splitTemplate(); 01112 if ( $flags & PPFrame::NO_ARGS ) { 01113 $newIterator = $this->virtualBracketedImplode( 01114 '{{{', '|', '}}}', 01115 $bits['title'], 01116 $bits['parts'] 01117 ); 01118 } else { 01119 $ret = $this->parser->argSubstitution( $bits, $this ); 01120 if ( isset( $ret['object'] ) ) { 01121 $newIterator = $ret['object']; 01122 } else { 01123 $out .= $ret['text']; 01124 } 01125 } 01126 } elseif ( $contextNode->name == 'comment' ) { 01127 # HTML-style comment 01128 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes 01129 if ( $this->parser->ot['html'] 01130 || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) 01131 || ( $flags & PPFrame::STRIP_COMMENTS ) 01132 ) { 01133 $out .= ''; 01134 } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) { 01135 # Add a strip marker in PST mode so that pstPass2() can 01136 # run some old-fashioned regexes on the result. 01137 # Not in RECOVER_COMMENTS mode (extractSections) though. 01138 $out .= $this->parser->insertStripItem( $contextNode->firstChild->value ); 01139 } else { 01140 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove 01141 $out .= $contextNode->firstChild->value; 01142 } 01143 } elseif ( $contextNode->name == 'ignore' ) { 01144 # Output suppression used by <includeonly> etc. 01145 # OT_WIKI will only respect <ignore> in substed templates. 01146 # The other output types respect it unless NO_IGNORE is set. 01147 # extractSections() sets NO_IGNORE and so never respects it. 01148 if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) 01149 || ( $flags & PPFrame::NO_IGNORE ) 01150 ) { 01151 $out .= $contextNode->firstChild->value; 01152 } else { 01153 //$out .= ''; 01154 } 01155 } elseif ( $contextNode->name == 'ext' ) { 01156 # Extension tag 01157 $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null ); 01158 if ( $flags & PPFrame::NO_TAGS ) { 01159 $s = '<' . $bits['name']->firstChild->value; 01160 if ( $bits['attr'] ) { 01161 $s .= $bits['attr']->firstChild->value; 01162 } 01163 if ( $bits['inner'] ) { 01164 $s .= '>' . $bits['inner']->firstChild->value; 01165 if ( $bits['close'] ) { 01166 $s .= $bits['close']->firstChild->value; 01167 } 01168 } else { 01169 $s .= '/>'; 01170 } 01171 $out .= $s; 01172 } else { 01173 $out .= $this->parser->extensionSubstitution( $bits, $this ); 01174 } 01175 } elseif ( $contextNode->name == 'h' ) { 01176 # Heading 01177 if ( $this->parser->ot['html'] ) { 01178 # Expand immediately and insert heading index marker 01179 $s = ''; 01180 for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) { 01181 $s .= $this->expand( $node, $flags ); 01182 } 01183 01184 $bits = $contextNode->splitHeading(); 01185 $titleText = $this->title->getPrefixedDBkey(); 01186 $this->parser->mHeadings[] = array( $titleText, $bits['i'] ); 01187 $serial = count( $this->parser->mHeadings ) - 1; 01188 $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX; 01189 $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] ); 01190 $this->parser->mStripState->addGeneral( $marker, '' ); 01191 $out .= $s; 01192 } else { 01193 # Expand in virtual stack 01194 $newIterator = $contextNode->getChildren(); 01195 } 01196 } else { 01197 # Generic recursive expansion 01198 $newIterator = $contextNode->getChildren(); 01199 } 01200 } else { 01201 throw new MWException( __METHOD__ . ': Invalid parameter type' ); 01202 } 01203 01204 if ( $newIterator !== false ) { 01205 $outStack[] = ''; 01206 $iteratorStack[] = $newIterator; 01207 $indexStack[] = 0; 01208 } elseif ( $iteratorStack[$level] === false ) { 01209 // Return accumulated value to parent 01210 // With tail recursion 01211 while ( $iteratorStack[$level] === false && $level > 0 ) { 01212 $outStack[$level - 1] .= $out; 01213 array_pop( $outStack ); 01214 array_pop( $iteratorStack ); 01215 array_pop( $indexStack ); 01216 $level--; 01217 } 01218 } 01219 } 01220 --$expansionDepth; 01221 return $outStack[0]; 01222 } 01223 01230 public function implodeWithFlags( $sep, $flags /*, ... */ ) { 01231 $args = array_slice( func_get_args(), 2 ); 01232 01233 $first = true; 01234 $s = ''; 01235 foreach ( $args as $root ) { 01236 if ( $root instanceof PPNode_Hash_Array ) { 01237 $root = $root->value; 01238 } 01239 if ( !is_array( $root ) ) { 01240 $root = array( $root ); 01241 } 01242 foreach ( $root as $node ) { 01243 if ( $first ) { 01244 $first = false; 01245 } else { 01246 $s .= $sep; 01247 } 01248 $s .= $this->expand( $node, $flags ); 01249 } 01250 } 01251 return $s; 01252 } 01253 01261 public function implode( $sep /*, ... */ ) { 01262 $args = array_slice( func_get_args(), 1 ); 01263 01264 $first = true; 01265 $s = ''; 01266 foreach ( $args as $root ) { 01267 if ( $root instanceof PPNode_Hash_Array ) { 01268 $root = $root->value; 01269 } 01270 if ( !is_array( $root ) ) { 01271 $root = array( $root ); 01272 } 01273 foreach ( $root as $node ) { 01274 if ( $first ) { 01275 $first = false; 01276 } else { 01277 $s .= $sep; 01278 } 01279 $s .= $this->expand( $node ); 01280 } 01281 } 01282 return $s; 01283 } 01284 01293 public function virtualImplode( $sep /*, ... */ ) { 01294 $args = array_slice( func_get_args(), 1 ); 01295 $out = array(); 01296 $first = true; 01297 01298 foreach ( $args as $root ) { 01299 if ( $root instanceof PPNode_Hash_Array ) { 01300 $root = $root->value; 01301 } 01302 if ( !is_array( $root ) ) { 01303 $root = array( $root ); 01304 } 01305 foreach ( $root as $node ) { 01306 if ( $first ) { 01307 $first = false; 01308 } else { 01309 $out[] = $sep; 01310 } 01311 $out[] = $node; 01312 } 01313 } 01314 return new PPNode_Hash_Array( $out ); 01315 } 01316 01326 public function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { 01327 $args = array_slice( func_get_args(), 3 ); 01328 $out = array( $start ); 01329 $first = true; 01330 01331 foreach ( $args as $root ) { 01332 if ( $root instanceof PPNode_Hash_Array ) { 01333 $root = $root->value; 01334 } 01335 if ( !is_array( $root ) ) { 01336 $root = array( $root ); 01337 } 01338 foreach ( $root as $node ) { 01339 if ( $first ) { 01340 $first = false; 01341 } else { 01342 $out[] = $sep; 01343 } 01344 $out[] = $node; 01345 } 01346 } 01347 $out[] = $end; 01348 return new PPNode_Hash_Array( $out ); 01349 } 01350 01351 public function __toString() { 01352 return 'frame{}'; 01353 } 01354 01359 public function getPDBK( $level = false ) { 01360 if ( $level === false ) { 01361 return $this->title->getPrefixedDBkey(); 01362 } else { 01363 return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false; 01364 } 01365 } 01366 01370 public function getArguments() { 01371 return array(); 01372 } 01373 01377 public function getNumberedArguments() { 01378 return array(); 01379 } 01380 01384 public function getNamedArguments() { 01385 return array(); 01386 } 01387 01393 public function isEmpty() { 01394 return true; 01395 } 01396 01401 public function getArgument( $name ) { 01402 return false; 01403 } 01404 01412 public function loopCheck( $title ) { 01413 return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); 01414 } 01415 01421 public function isTemplate() { 01422 return false; 01423 } 01424 01430 public function getTitle() { 01431 return $this->title; 01432 } 01433 01439 public function setVolatile( $flag = true ) { 01440 $this->volatile = $flag; 01441 } 01442 01448 public function isVolatile() { 01449 return $this->volatile; 01450 } 01451 01457 public function setTTL( $ttl ) { 01458 if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) { 01459 $this->ttl = $ttl; 01460 } 01461 } 01462 01468 public function getTTL() { 01469 return $this->ttl; 01470 } 01471 } 01472 01478 class PPTemplateFrame_Hash extends PPFrame_Hash { 01479 // @codingStandardsIgnoreEnd 01480 01481 public $numberedArgs, $namedArgs, $parent; 01482 public $numberedExpansionCache, $namedExpansionCache; 01483 01491 public function __construct( $preprocessor, $parent = false, $numberedArgs = array(), 01492 $namedArgs = array(), $title = false 01493 ) { 01494 parent::__construct( $preprocessor ); 01495 01496 $this->parent = $parent; 01497 $this->numberedArgs = $numberedArgs; 01498 $this->namedArgs = $namedArgs; 01499 $this->title = $title; 01500 $pdbk = $title ? $title->getPrefixedDBkey() : false; 01501 $this->titleCache = $parent->titleCache; 01502 $this->titleCache[] = $pdbk; 01503 $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; 01504 if ( $pdbk !== false ) { 01505 $this->loopCheckHash[$pdbk] = true; 01506 } 01507 $this->depth = $parent->depth + 1; 01508 $this->numberedExpansionCache = $this->namedExpansionCache = array(); 01509 } 01510 01511 public function __toString() { 01512 $s = 'tplframe{'; 01513 $first = true; 01514 $args = $this->numberedArgs + $this->namedArgs; 01515 foreach ( $args as $name => $value ) { 01516 if ( $first ) { 01517 $first = false; 01518 } else { 01519 $s .= ', '; 01520 } 01521 $s .= "\"$name\":\"" . 01522 str_replace( '"', '\\"', $value->__toString() ) . '"'; 01523 } 01524 $s .= '}'; 01525 return $s; 01526 } 01527 01535 public function cachedExpand( $key, $root, $flags = 0 ) { 01536 if ( isset( $this->parent->childExpansionCache[$key] ) ) { 01537 return $this->parent->childExpansionCache[$key]; 01538 } 01539 $retval = $this->expand( $root, $flags ); 01540 if ( !$this->isVolatile() ) { 01541 $this->parent->childExpansionCache[$key] = $retval; 01542 } 01543 return $retval; 01544 } 01545 01551 public function isEmpty() { 01552 return !count( $this->numberedArgs ) && !count( $this->namedArgs ); 01553 } 01554 01558 public function getArguments() { 01559 $arguments = array(); 01560 foreach ( array_merge( 01561 array_keys( $this->numberedArgs ), 01562 array_keys( $this->namedArgs ) ) as $key ) { 01563 $arguments[$key] = $this->getArgument( $key ); 01564 } 01565 return $arguments; 01566 } 01567 01571 public function getNumberedArguments() { 01572 $arguments = array(); 01573 foreach ( array_keys( $this->numberedArgs ) as $key ) { 01574 $arguments[$key] = $this->getArgument( $key ); 01575 } 01576 return $arguments; 01577 } 01578 01582 public function getNamedArguments() { 01583 $arguments = array(); 01584 foreach ( array_keys( $this->namedArgs ) as $key ) { 01585 $arguments[$key] = $this->getArgument( $key ); 01586 } 01587 return $arguments; 01588 } 01589 01594 public function getNumberedArgument( $index ) { 01595 if ( !isset( $this->numberedArgs[$index] ) ) { 01596 return false; 01597 } 01598 if ( !isset( $this->numberedExpansionCache[$index] ) ) { 01599 # No trimming for unnamed arguments 01600 $this->numberedExpansionCache[$index] = $this->parent->expand( 01601 $this->numberedArgs[$index], 01602 PPFrame::STRIP_COMMENTS 01603 ); 01604 } 01605 return $this->numberedExpansionCache[$index]; 01606 } 01607 01612 public function getNamedArgument( $name ) { 01613 if ( !isset( $this->namedArgs[$name] ) ) { 01614 return false; 01615 } 01616 if ( !isset( $this->namedExpansionCache[$name] ) ) { 01617 # Trim named arguments post-expand, for backwards compatibility 01618 $this->namedExpansionCache[$name] = trim( 01619 $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) ); 01620 } 01621 return $this->namedExpansionCache[$name]; 01622 } 01623 01628 public function getArgument( $name ) { 01629 $text = $this->getNumberedArgument( $name ); 01630 if ( $text === false ) { 01631 $text = $this->getNamedArgument( $name ); 01632 } 01633 return $text; 01634 } 01635 01641 public function isTemplate() { 01642 return true; 01643 } 01644 01645 public function setVolatile( $flag = true ) { 01646 parent::setVolatile( $flag ); 01647 $this->parent->setVolatile( $flag ); 01648 } 01649 01650 public function setTTL( $ttl ) { 01651 parent::setTTL( $ttl ); 01652 $this->parent->setTTL( $ttl ); 01653 } 01654 } 01655 01661 class PPCustomFrame_Hash extends PPFrame_Hash { 01662 // @codingStandardsIgnoreEnd 01663 01664 public $args; 01665 01666 public function __construct( $preprocessor, $args ) { 01667 parent::__construct( $preprocessor ); 01668 $this->args = $args; 01669 } 01670 01671 public function __toString() { 01672 $s = 'cstmframe{'; 01673 $first = true; 01674 foreach ( $this->args as $name => $value ) { 01675 if ( $first ) { 01676 $first = false; 01677 } else { 01678 $s .= ', '; 01679 } 01680 $s .= "\"$name\":\"" . 01681 str_replace( '"', '\\"', $value->__toString() ) . '"'; 01682 } 01683 $s .= '}'; 01684 return $s; 01685 } 01686 01690 public function isEmpty() { 01691 return !count( $this->args ); 01692 } 01693 01698 public function getArgument( $index ) { 01699 if ( !isset( $this->args[$index] ) ) { 01700 return false; 01701 } 01702 return $this->args[$index]; 01703 } 01704 01705 public function getArguments() { 01706 return $this->args; 01707 } 01708 } 01709 01714 class PPNode_Hash_Tree implements PPNode { 01715 // @codingStandardsIgnoreEnd 01716 01717 public $name, $firstChild, $lastChild, $nextSibling; 01718 01719 public function __construct( $name ) { 01720 $this->name = $name; 01721 $this->firstChild = $this->lastChild = $this->nextSibling = false; 01722 } 01723 01724 public function __toString() { 01725 $inner = ''; 01726 $attribs = ''; 01727 for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) { 01728 if ( $node instanceof PPNode_Hash_Attr ) { 01729 $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"'; 01730 } else { 01731 $inner .= $node->__toString(); 01732 } 01733 } 01734 if ( $inner === '' ) { 01735 return "<{$this->name}$attribs/>"; 01736 } else { 01737 return "<{$this->name}$attribs>$inner</{$this->name}>"; 01738 } 01739 } 01740 01746 public static function newWithText( $name, $text ) { 01747 $obj = new self( $name ); 01748 $obj->addChild( new PPNode_Hash_Text( $text ) ); 01749 return $obj; 01750 } 01751 01752 public function addChild( $node ) { 01753 if ( $this->lastChild === false ) { 01754 $this->firstChild = $this->lastChild = $node; 01755 } else { 01756 $this->lastChild->nextSibling = $node; 01757 $this->lastChild = $node; 01758 } 01759 } 01760 01764 public function getChildren() { 01765 $children = array(); 01766 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01767 $children[] = $child; 01768 } 01769 return new PPNode_Hash_Array( $children ); 01770 } 01771 01772 public function getFirstChild() { 01773 return $this->firstChild; 01774 } 01775 01776 public function getNextSibling() { 01777 return $this->nextSibling; 01778 } 01779 01780 public function getChildrenOfType( $name ) { 01781 $children = array(); 01782 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01783 if ( isset( $child->name ) && $child->name === $name ) { 01784 $children[] = $child; 01785 } 01786 } 01787 return $children; 01788 } 01789 01793 public function getLength() { 01794 return false; 01795 } 01796 01801 public function item( $i ) { 01802 return false; 01803 } 01804 01808 public function getName() { 01809 return $this->name; 01810 } 01811 01821 public function splitArg() { 01822 $bits = array(); 01823 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01824 if ( !isset( $child->name ) ) { 01825 continue; 01826 } 01827 if ( $child->name === 'name' ) { 01828 $bits['name'] = $child; 01829 if ( $child->firstChild instanceof PPNode_Hash_Attr 01830 && $child->firstChild->name === 'index' 01831 ) { 01832 $bits['index'] = $child->firstChild->value; 01833 } 01834 } elseif ( $child->name === 'value' ) { 01835 $bits['value'] = $child; 01836 } 01837 } 01838 01839 if ( !isset( $bits['name'] ) ) { 01840 throw new MWException( 'Invalid brace node passed to ' . __METHOD__ ); 01841 } 01842 if ( !isset( $bits['index'] ) ) { 01843 $bits['index'] = ''; 01844 } 01845 return $bits; 01846 } 01847 01855 public function splitExt() { 01856 $bits = array(); 01857 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01858 if ( !isset( $child->name ) ) { 01859 continue; 01860 } 01861 if ( $child->name == 'name' ) { 01862 $bits['name'] = $child; 01863 } elseif ( $child->name == 'attr' ) { 01864 $bits['attr'] = $child; 01865 } elseif ( $child->name == 'inner' ) { 01866 $bits['inner'] = $child; 01867 } elseif ( $child->name == 'close' ) { 01868 $bits['close'] = $child; 01869 } 01870 } 01871 if ( !isset( $bits['name'] ) ) { 01872 throw new MWException( 'Invalid ext node passed to ' . __METHOD__ ); 01873 } 01874 return $bits; 01875 } 01876 01883 public function splitHeading() { 01884 if ( $this->name !== 'h' ) { 01885 throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); 01886 } 01887 $bits = array(); 01888 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01889 if ( !isset( $child->name ) ) { 01890 continue; 01891 } 01892 if ( $child->name == 'i' ) { 01893 $bits['i'] = $child->value; 01894 } elseif ( $child->name == 'level' ) { 01895 $bits['level'] = $child->value; 01896 } 01897 } 01898 if ( !isset( $bits['i'] ) ) { 01899 throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); 01900 } 01901 return $bits; 01902 } 01903 01910 public function splitTemplate() { 01911 $parts = array(); 01912 $bits = array( 'lineStart' => '' ); 01913 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { 01914 if ( !isset( $child->name ) ) { 01915 continue; 01916 } 01917 if ( $child->name == 'title' ) { 01918 $bits['title'] = $child; 01919 } 01920 if ( $child->name == 'part' ) { 01921 $parts[] = $child; 01922 } 01923 if ( $child->name == 'lineStart' ) { 01924 $bits['lineStart'] = '1'; 01925 } 01926 } 01927 if ( !isset( $bits['title'] ) ) { 01928 throw new MWException( 'Invalid node passed to ' . __METHOD__ ); 01929 } 01930 $bits['parts'] = new PPNode_Hash_Array( $parts ); 01931 return $bits; 01932 } 01933 } 01934 01939 class PPNode_Hash_Text implements PPNode { 01940 // @codingStandardsIgnoreEnd 01941 01942 public $value, $nextSibling; 01943 01944 public function __construct( $value ) { 01945 if ( is_object( $value ) ) { 01946 throw new MWException( __CLASS__ . ' given object instead of string' ); 01947 } 01948 $this->value = $value; 01949 } 01950 01951 public function __toString() { 01952 return htmlspecialchars( $this->value ); 01953 } 01954 01955 public function getNextSibling() { 01956 return $this->nextSibling; 01957 } 01958 01959 public function getChildren() { 01960 return false; 01961 } 01962 01963 public function getFirstChild() { 01964 return false; 01965 } 01966 01967 public function getChildrenOfType( $name ) { 01968 return false; 01969 } 01970 01971 public function getLength() { 01972 return false; 01973 } 01974 01975 public function item( $i ) { 01976 return false; 01977 } 01978 01979 public function getName() { 01980 return '#text'; 01981 } 01982 01983 public function splitArg() { 01984 throw new MWException( __METHOD__ . ': not supported' ); 01985 } 01986 01987 public function splitExt() { 01988 throw new MWException( __METHOD__ . ': not supported' ); 01989 } 01990 01991 public function splitHeading() { 01992 throw new MWException( __METHOD__ . ': not supported' ); 01993 } 01994 } 01995 02000 class PPNode_Hash_Array implements PPNode { 02001 // @codingStandardsIgnoreEnd 02002 02003 public $value, $nextSibling; 02004 02005 public function __construct( $value ) { 02006 $this->value = $value; 02007 } 02008 02009 public function __toString() { 02010 return var_export( $this, true ); 02011 } 02012 02013 public function getLength() { 02014 return count( $this->value ); 02015 } 02016 02017 public function item( $i ) { 02018 return $this->value[$i]; 02019 } 02020 02021 public function getName() { 02022 return '#nodelist'; 02023 } 02024 02025 public function getNextSibling() { 02026 return $this->nextSibling; 02027 } 02028 02029 public function getChildren() { 02030 return false; 02031 } 02032 02033 public function getFirstChild() { 02034 return false; 02035 } 02036 02037 public function getChildrenOfType( $name ) { 02038 return false; 02039 } 02040 02041 public function splitArg() { 02042 throw new MWException( __METHOD__ . ': not supported' ); 02043 } 02044 02045 public function splitExt() { 02046 throw new MWException( __METHOD__ . ': not supported' ); 02047 } 02048 02049 public function splitHeading() { 02050 throw new MWException( __METHOD__ . ': not supported' ); 02051 } 02052 } 02053 02058 class PPNode_Hash_Attr implements PPNode { 02059 // @codingStandardsIgnoreEnd 02060 02061 public $name, $value, $nextSibling; 02062 02063 public function __construct( $name, $value ) { 02064 $this->name = $name; 02065 $this->value = $value; 02066 } 02067 02068 public function __toString() { 02069 return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>"; 02070 } 02071 02072 public function getName() { 02073 return $this->name; 02074 } 02075 02076 public function getNextSibling() { 02077 return $this->nextSibling; 02078 } 02079 02080 public function getChildren() { 02081 return false; 02082 } 02083 02084 public function getFirstChild() { 02085 return false; 02086 } 02087 02088 public function getChildrenOfType( $name ) { 02089 return false; 02090 } 02091 02092 public function getLength() { 02093 return false; 02094 } 02095 02096 public function item( $i ) { 02097 return false; 02098 } 02099 02100 public function splitArg() { 02101 throw new MWException( __METHOD__ . ': not supported' ); 02102 } 02103 02104 public function splitExt() { 02105 throw new MWException( __METHOD__ . ': not supported' ); 02106 } 02107 02108 public function splitHeading() { 02109 throw new MWException( __METHOD__ . ': not supported' ); 02110 } 02111 }