00001 <?php
00002
00009 class Preprocessor_Hash implements Preprocessor {
00010 var $parser;
00011
00012 const CACHE_VERSION = 1;
00013
00014 function __construct( $parser ) {
00015 $this->parser = $parser;
00016 }
00017
00018 function newFrame() {
00019 return new PPFrame_Hash( $this );
00020 }
00021
00022 function newCustomFrame( $args ) {
00023 return new PPCustomFrame_Hash( $this, $args );
00024 }
00025
00048 function preprocessToObj( $text, $flags = 0 ) {
00049 wfProfileIn( __METHOD__ );
00050
00051
00052
00053 global $wgMemc, $wgPreprocessorCacheThreshold;
00054
00055 $cacheable = strlen( $text ) > $wgPreprocessorCacheThreshold;
00056 if ( $cacheable ) {
00057 wfProfileIn( __METHOD__.'-cacheable' );
00058
00059 $cacheKey = wfMemcKey( 'preprocess-hash', md5($text), $flags );
00060 $cacheValue = $wgMemc->get( $cacheKey );
00061 if ( $cacheValue ) {
00062 $version = substr( $cacheValue, 0, 8 );
00063 if ( intval( $version ) == self::CACHE_VERSION ) {
00064 $hash = unserialize( substr( $cacheValue, 8 ) );
00065
00066 wfDebugLog( "Preprocessor",
00067 "Loaded preprocessor hash from memcached (key $cacheKey)" );
00068 wfProfileOut( __METHOD__.'-cacheable' );
00069 wfProfileOut( __METHOD__ );
00070 return $hash;
00071 }
00072 }
00073 wfProfileIn( __METHOD__.'-cache-miss' );
00074 }
00075
00076 $rules = array(
00077 '{' => array(
00078 'end' => '}',
00079 'names' => array(
00080 2 => 'template',
00081 3 => 'tplarg',
00082 ),
00083 'min' => 2,
00084 'max' => 3,
00085 ),
00086 '[' => array(
00087 'end' => ']',
00088 'names' => array( 2 => null ),
00089 'min' => 2,
00090 'max' => 2,
00091 )
00092 );
00093
00094 $forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
00095
00096 $xmlishElements = $this->parser->getStripList();
00097 $enableOnlyinclude = false;
00098 if ( $forInclusion ) {
00099 $ignoredTags = array( 'includeonly', '/includeonly' );
00100 $ignoredElements = array( 'noinclude' );
00101 $xmlishElements[] = 'noinclude';
00102 if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
00103 $enableOnlyinclude = true;
00104 }
00105 } else {
00106 $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' );
00107 $ignoredElements = array( 'includeonly' );
00108 $xmlishElements[] = 'includeonly';
00109 }
00110 $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
00111
00112
00113 $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
00114
00115 $stack = new PPDStack_Hash;
00116
00117 $searchBase = "[{<\n";
00118 $revText = strrev( $text );
00119
00120 $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
00121 $accum =& $stack->getAccum(); # Current accumulator
00122 $findEquals = false; # True to find equals signs in arguments
00123 $findPipe = false; # True to take notice of pipe characters
00124 $headingIndex = 1;
00125 $inHeading = false; # True if $i is inside a possible heading
00126 $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
00127 $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
00128 $fakeLineStart = true; # Do a line-start run without outputting an LF character
00129
00130 while ( true ) {
00131
00132
00133 if ( $findOnlyinclude ) {
00134
00135 $startPos = strpos( $text, '<onlyinclude>', $i );
00136 if ( $startPos === false ) {
00137
00138 $accum->addNodeWithText( 'ignore', substr( $text, $i ) );
00139 break;
00140 }
00141 $tagEndPos = $startPos + strlen( '<onlyinclude>' );
00142 $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i ) );
00143 $i = $tagEndPos;
00144 $findOnlyinclude = false;
00145 }
00146
00147 if ( $fakeLineStart ) {
00148 $found = 'line-start';
00149 $curChar = '';
00150 } else {
00151 # Find next opening brace, closing brace or pipe
00152 $search = $searchBase;
00153 if ( $stack->top === false ) {
00154 $currentClosing = '';
00155 } else {
00156 $currentClosing = $stack->top->close;
00157 $search .= $currentClosing;
00158 }
00159 if ( $findPipe ) {
00160 $search .= '|';
00161 }
00162 if ( $findEquals ) {
00163
00164 $search .= '=';
00165 }
00166 $rule = null;
00167 # Output literal section, advance input counter
00168 $literalLength = strcspn( $text, $search, $i );
00169 if ( $literalLength > 0 ) {
00170 $accum->addLiteral( substr( $text, $i, $literalLength ) );
00171 $i += $literalLength;
00172 }
00173 if ( $i >= strlen( $text ) ) {
00174 if ( $currentClosing == "\n" ) {
00175
00176 $curChar = '';
00177 $found = 'line-end';
00178 } else {
00179 # All done
00180 break;
00181 }
00182 } else {
00183 $curChar = $text[$i];
00184 if ( $curChar == '|' ) {
00185 $found = 'pipe';
00186 } elseif ( $curChar == '=' ) {
00187 $found = 'equals';
00188 } elseif ( $curChar == '<' ) {
00189 $found = 'angle';
00190 } elseif ( $curChar == "\n" ) {
00191 if ( $inHeading ) {
00192 $found = 'line-end';
00193 } else {
00194 $found = 'line-start';
00195 }
00196 } elseif ( $curChar == $currentClosing ) {
00197 $found = 'close';
00198 } elseif ( isset( $rules[$curChar] ) ) {
00199 $found = 'open';
00200 $rule = $rules[$curChar];
00201 } else {
00202 # Some versions of PHP have a strcspn which stops on null characters
00203 # Ignore and continue
00204 ++$i;
00205 continue;
00206 }
00207 }
00208 }
00209
00210 if ( $found == 'angle' ) {
00211 $matches = false;
00212
00213 if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) {
00214 $findOnlyinclude = true;
00215 continue;
00216 }
00217
00218
00219 if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
00220
00221 $accum->addLiteral( '<' );
00222 ++$i;
00223 continue;
00224 }
00225
00226 if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
00227
00228
00229
00230
00231
00232 $endPos = strpos( $text, '-->', $i + 4 );
00233 if ( $endPos === false ) {
00234
00235 $inner = substr( $text, $i );
00236 $accum->addNodeWithText( 'comment', $inner );
00237 $i = strlen( $text );
00238 } else {
00239
00240 $wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0;
00241
00242
00243 $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 );
00244
00245
00246
00247
00248 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
00249 && substr( $text, $wsEnd + 1, 1 ) == "\n" )
00250 {
00251 $startPos = $wsStart;
00252 $endPos = $wsEnd + 1;
00253
00254
00255 $wsLength = $i - $wsStart;
00256 if ( $wsLength > 0
00257 && $accum->lastNode instanceof PPNode_Hash_Text
00258 && substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) )
00259 {
00260 $accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength );
00261 }
00262
00263 $fakeLineStart = true;
00264 } else {
00265
00266 $startPos = $i;
00267 $endPos += 2;
00268 }
00269
00270 if ( $stack->top ) {
00271 $part = $stack->top->getCurrentPart();
00272 if ( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) {
00273
00274 $part->commentEnd = $wsEnd;
00275 } else {
00276 $part->visualEnd = $wsStart;
00277 $part->commentEnd = $endPos;
00278 }
00279 }
00280 $i = $endPos + 1;
00281 $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
00282 $accum->addNodeWithText( 'comment', $inner );
00283 }
00284 continue;
00285 }
00286 $name = $matches[1];
00287 $lowerName = strtolower( $name );
00288 $attrStart = $i + strlen( $name ) + 1;
00289
00290
00291 $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
00292 if ( $tagEndPos === false ) {
00293
00294
00295 $noMoreGT = true;
00296 $accum->addLiteral( '<' );
00297 ++$i;
00298 continue;
00299 }
00300
00301
00302 if ( in_array( $lowerName, $ignoredTags ) ) {
00303 $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i + 1 ) );
00304 $i = $tagEndPos + 1;
00305 continue;
00306 }
00307
00308 $tagStartPos = $i;
00309 if ( $text[$tagEndPos-1] == '/' ) {
00310
00311 $attrEnd = $tagEndPos - 1;
00312 $inner = null;
00313 $i = $tagEndPos + 1;
00314 $close = null;
00315 } else {
00316 $attrEnd = $tagEndPos;
00317
00318 if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
00319 $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
00320 {
00321 $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
00322 $i = $matches[0][1] + strlen( $matches[0][0] );
00323 $close = $matches[0][0];
00324 } else {
00325
00326 $inner = substr( $text, $tagEndPos + 1 );
00327 $i = strlen( $text );
00328 $close = null;
00329 }
00330 }
00331
00332 if ( in_array( $lowerName, $ignoredElements ) ) {
00333 $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) );
00334 continue;
00335 }
00336
00337 if ( $attrEnd <= $attrStart ) {
00338 $attr = '';
00339 } else {
00340
00341
00342 $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
00343 }
00344
00345 $extNode = new PPNode_Hash_Tree( 'ext' );
00346 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'name', $name ) );
00347 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'attr', $attr ) );
00348 if ( $inner !== null ) {
00349 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'inner', $inner ) );
00350 }
00351 if ( $close !== null ) {
00352 $extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) );
00353 }
00354 $accum->addNode( $extNode );
00355 }
00356
00357 elseif ( $found == 'line-start' ) {
00358
00359
00360 if ( $fakeLineStart ) {
00361 $fakeLineStart = false;
00362 } else {
00363 $accum->addLiteral( $curChar );
00364 $i++;
00365 }
00366
00367 $count = strspn( $text, '=', $i, 6 );
00368 if ( $count == 1 && $findEquals ) {
00369
00370
00371
00372 } elseif ( $count > 0 ) {
00373 $piece = array(
00374 'open' => "\n",
00375 'close' => "\n",
00376 'parts' => array( new PPDPart_Hash( str_repeat( '=', $count ) ) ),
00377 'startPos' => $i,
00378 'count' => $count );
00379 $stack->push( $piece );
00380 $accum =& $stack->getAccum();
00381 extract( $stack->getFlags() );
00382 $i += $count;
00383 }
00384 }
00385
00386 elseif ( $found == 'line-end' ) {
00387 $piece = $stack->top;
00388
00389 assert( $piece->open == "\n" );
00390 $part = $piece->getCurrentPart();
00391
00392
00393 $wsLength = strspn( $revText, " \t", strlen( $text ) - $i );
00394 $searchStart = $i - $wsLength;
00395 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
00396
00397
00398 $searchStart = $part->visualEnd;
00399 $searchStart -= strspn( $revText, " \t", strlen( $text ) - $searchStart );
00400 }
00401 $count = $piece->count;
00402 $equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart );
00403 if ( $equalsLength > 0 ) {
00404 if ( $i - $equalsLength == $piece->startPos ) {
00405
00406
00407
00408 $count = $equalsLength;
00409 if ( $count < 3 ) {
00410 $count = 0;
00411 } else {
00412 $count = min( 6, intval( ( $count - 1 ) / 2 ) );
00413 }
00414 } else {
00415 $count = min( $equalsLength, $count );
00416 }
00417 if ( $count > 0 ) {
00418
00419 $element = new PPNode_Hash_Tree( 'possible-h' );
00420 $element->addChild( new PPNode_Hash_Attr( 'level', $count ) );
00421 $element->addChild( new PPNode_Hash_Attr( 'i', $headingIndex++ ) );
00422 $element->lastChild->nextSibling = $accum->firstNode;
00423 $element->lastChild = $accum->lastNode;
00424 } else {
00425
00426 $element = $accum;
00427 }
00428 } else {
00429
00430 $element = $accum;
00431 }
00432
00433 $stack->pop();
00434 $accum =& $stack->getAccum();
00435 extract( $stack->getFlags() );
00436
00437
00438 if ( $element instanceof PPNode ) {
00439 $accum->addNode( $element );
00440 } else {
00441 $accum->addAccum( $element );
00442 }
00443
00444
00445
00446
00447
00448 }
00449
00450 elseif ( $found == 'open' ) {
00451 # count opening brace characters
00452 $count = strspn( $text, $curChar, $i );
00453
00454 # we need to add to stack only if opening brace count is enough for one of the rules
00455 if ( $count >= $rule['min'] ) {
00456 # Add it to the stack
00457 $piece = array(
00458 'open' => $curChar,
00459 'close' => $rule['end'],
00460 'count' => $count,
00461 'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
00462 );
00463
00464 $stack->push( $piece );
00465 $accum =& $stack->getAccum();
00466 extract( $stack->getFlags() );
00467 } else {
00468 # Add literal brace(s)
00469 $accum->addLiteral( str_repeat( $curChar, $count ) );
00470 }
00471 $i += $count;
00472 }
00473
00474 elseif ( $found == 'close' ) {
00475 $piece = $stack->top;
00476 # lets check if there are enough characters for closing brace
00477 $maxCount = $piece->count;
00478 $count = strspn( $text, $curChar, $i, $maxCount );
00479
00480 # check for maximum matching characters (if there are 5 closing
00481 # characters, we will probably need only 3 - depending on the rules)
00482 $matchingCount = 0;
00483 $rule = $rules[$piece->open];
00484 if ( $count > $rule['max'] ) {
00485 # The specified maximum exists in the callback array, unless the caller
00486 # has made an error
00487 $matchingCount = $rule['max'];
00488 } else {
00489 # Count is less than the maximum
00490 # Skip any gaps in the callback array to find the true largest match
00491 # Need to use array_key_exists not isset because the callback can be null
00492 $matchingCount = $count;
00493 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
00494 --$matchingCount;
00495 }
00496 }
00497
00498 if ($matchingCount <= 0) {
00499 # No matching element found in callback array
00500 # Output a literal closing brace and continue
00501 $accum->addLiteral( str_repeat( $curChar, $count ) );
00502 $i += $count;
00503 continue;
00504 }
00505 $name = $rule['names'][$matchingCount];
00506 if ( $name === null ) {
00507
00508 $element = $piece->breakSyntax( $matchingCount );
00509 $element->addLiteral( str_repeat( $rule['end'], $matchingCount ) );
00510 } else {
00511 # Create XML element
00512 # Note: $parts is already XML, does not need to be encoded further
00513 $parts = $piece->parts;
00514 $titleAccum = $parts[0]->out;
00515 unset( $parts[0] );
00516
00517 $element = new PPNode_Hash_Tree( $name );
00518
00519 # The invocation is at the start of the line if lineStart is set in
00520 # the stack, and all opening brackets are used up.
00521 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
00522 $element->addChild( new PPNode_Hash_Attr( 'lineStart', 1 ) );
00523 }
00524 $titleNode = new PPNode_Hash_Tree( 'title' );
00525 $titleNode->firstChild = $titleAccum->firstNode;
00526 $titleNode->lastChild = $titleAccum->lastNode;
00527 $element->addChild( $titleNode );
00528 $argIndex = 1;
00529 foreach ( $parts as $partIndex => $part ) {
00530 if ( isset( $part->eqpos ) ) {
00531
00532 $lastNode = false;
00533 for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) {
00534 if ( $node === $part->eqpos ) {
00535 break;
00536 }
00537 $lastNode = $node;
00538 }
00539 if ( !$node ) {
00540 throw new MWException( __METHOD__. ': eqpos not found' );
00541 }
00542 if ( $node->name !== 'equals' ) {
00543 throw new MWException( __METHOD__ .': eqpos is not equals' );
00544 }
00545 $equalsNode = $node;
00546
00547
00548 $nameNode = new PPNode_Hash_Tree( 'name' );
00549 if ( $lastNode !== false ) {
00550 $lastNode->nextSibling = false;
00551 $nameNode->firstChild = $part->out->firstNode;
00552 $nameNode->lastChild = $lastNode;
00553 }
00554
00555
00556 $valueNode = new PPNode_Hash_Tree( 'value' );
00557 if ( $equalsNode->nextSibling !== false ) {
00558 $valueNode->firstChild = $equalsNode->nextSibling;
00559 $valueNode->lastChild = $part->out->lastNode;
00560 }
00561 $partNode = new PPNode_Hash_Tree( 'part' );
00562 $partNode->addChild( $nameNode );
00563 $partNode->addChild( $equalsNode->firstChild );
00564 $partNode->addChild( $valueNode );
00565 $element->addChild( $partNode );
00566 } else {
00567 $partNode = new PPNode_Hash_Tree( 'part' );
00568 $nameNode = new PPNode_Hash_Tree( 'name' );
00569 $nameNode->addChild( new PPNode_Hash_Attr( 'index', $argIndex++ ) );
00570 $valueNode = new PPNode_Hash_Tree( 'value' );
00571 $valueNode->firstChild = $part->out->firstNode;
00572 $valueNode->lastChild = $part->out->lastNode;
00573 $partNode->addChild( $nameNode );
00574 $partNode->addChild( $valueNode );
00575 $element->addChild( $partNode );
00576 }
00577 }
00578 }
00579
00580 # Advance input pointer
00581 $i += $matchingCount;
00582
00583 # Unwind the stack
00584 $stack->pop();
00585 $accum =& $stack->getAccum();
00586
00587 # Re-add the old stack element if it still has unmatched opening characters remaining
00588 if ($matchingCount < $piece->count) {
00589 $piece->parts = array( new PPDPart_Hash );
00590 $piece->count -= $matchingCount;
00591 # do we still qualify for any callback with remaining count?
00592 $names = $rules[$piece->open]['names'];
00593 $skippedBraces = 0;
00594 $enclosingAccum =& $accum;
00595 while ( $piece->count ) {
00596 if ( array_key_exists( $piece->count, $names ) ) {
00597 $stack->push( $piece );
00598 $accum =& $stack->getAccum();
00599 break;
00600 }
00601 --$piece->count;
00602 $skippedBraces ++;
00603 }
00604 $enclosingAccum->addLiteral( str_repeat( $piece->open, $skippedBraces ) );
00605 }
00606
00607 extract( $stack->getFlags() );
00608
00609 # Add XML element to the enclosing accumulator
00610 if ( $element instanceof PPNode ) {
00611 $accum->addNode( $element );
00612 } else {
00613 $accum->addAccum( $element );
00614 }
00615 }
00616
00617 elseif ( $found == 'pipe' ) {
00618 $findEquals = true;
00619 $stack->addPart();
00620 $accum =& $stack->getAccum();
00621 ++$i;
00622 }
00623
00624 elseif ( $found == 'equals' ) {
00625 $findEquals = false;
00626 $accum->addNodeWithText( 'equals', '=' );
00627 $stack->getCurrentPart()->eqpos = $accum->lastNode;
00628 ++$i;
00629 }
00630 }
00631
00632 # Output any remaining unclosed brackets
00633 foreach ( $stack->stack as $piece ) {
00634 $stack->rootAccum->addAccum( $piece->breakSyntax() );
00635 }
00636
00637 # Enable top-level headings
00638 for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) {
00639 if ( isset( $node->name ) && $node->name === 'possible-h' ) {
00640 $node->name = 'h';
00641 }
00642 }
00643
00644 $rootNode = new PPNode_Hash_Tree( 'root' );
00645 $rootNode->firstChild = $stack->rootAccum->firstNode;
00646 $rootNode->lastChild = $stack->rootAccum->lastNode;
00647
00648
00649 if ($cacheable) {
00650 $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );;
00651 $wgMemc->set( $cacheKey, $cacheValue, 86400 );
00652 wfProfileOut( __METHOD__.'-cache-miss' );
00653 wfProfileOut( __METHOD__.'-cacheable' );
00654 wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
00655 }
00656
00657 wfProfileOut( __METHOD__ );
00658 return $rootNode;
00659 }
00660 }
00661
00666 class PPDStack_Hash extends PPDStack {
00667 function __construct() {
00668 $this->elementClass = 'PPDStackElement_Hash';
00669 parent::__construct();
00670 $this->rootAccum = new PPDAccum_Hash;
00671 }
00672 }
00673
00677 class PPDStackElement_Hash extends PPDStackElement {
00678 function __construct( $data = array() ) {
00679 $this->partClass = 'PPDPart_Hash';
00680 parent::__construct( $data );
00681 }
00682
00686 function breakSyntax( $openingCount = false ) {
00687 if ( $this->open == "\n" ) {
00688 $accum = $this->parts[0]->out;
00689 } else {
00690 if ( $openingCount === false ) {
00691 $openingCount = $this->count;
00692 }
00693 $accum = new PPDAccum_Hash;
00694 $accum->addLiteral( str_repeat( $this->open, $openingCount ) );
00695 $first = true;
00696 foreach ( $this->parts as $part ) {
00697 if ( $first ) {
00698 $first = false;
00699 } else {
00700 $accum->addLiteral( '|' );
00701 }
00702 $accum->addAccum( $part->out );
00703 }
00704 }
00705 return $accum;
00706 }
00707 }
00708
00712 class PPDPart_Hash extends PPDPart {
00713 function __construct( $out = '' ) {
00714 $accum = new PPDAccum_Hash;
00715 if ( $out !== '' ) {
00716 $accum->addLiteral( $out );
00717 }
00718 parent::__construct( $accum );
00719 }
00720 }
00721
00725 class PPDAccum_Hash {
00726 var $firstNode, $lastNode;
00727
00728 function __construct() {
00729 $this->firstNode = $this->lastNode = false;
00730 }
00731
00735 function addLiteral( $s ) {
00736 if ( $this->lastNode === false ) {
00737 $this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s );
00738 } elseif ( $this->lastNode instanceof PPNode_Hash_Text ) {
00739 $this->lastNode->value .= $s;
00740 } else {
00741 $this->lastNode->nextSibling = new PPNode_Hash_Text( $s );
00742 $this->lastNode = $this->lastNode->nextSibling;
00743 }
00744 }
00745
00749 function addNode( PPNode $node ) {
00750 if ( $this->lastNode === false ) {
00751 $this->firstNode = $this->lastNode = $node;
00752 } else {
00753 $this->lastNode->nextSibling = $node;
00754 $this->lastNode = $node;
00755 }
00756 }
00757
00761 function addNodeWithText( $name, $value ) {
00762 $node = PPNode_Hash_Tree::newWithText( $name, $value );
00763 $this->addNode( $node );
00764 }
00765
00771 function addAccum( $accum ) {
00772 if ( $accum->lastNode === false ) {
00773
00774 } elseif ( $this->lastNode === false ) {
00775 $this->firstNode = $accum->firstNode;
00776 $this->lastNode = $accum->lastNode;
00777 } else {
00778 $this->lastNode->nextSibling = $accum->firstNode;
00779 $this->lastNode = $accum->lastNode;
00780 }
00781 }
00782 }
00783
00788 class PPFrame_Hash implements PPFrame {
00789 var $preprocessor, $parser, $title;
00790 var $titleCache;
00791
00796 var $loopCheckHash;
00797
00802 var $depth;
00803
00804
00809 function __construct( $preprocessor ) {
00810 $this->preprocessor = $preprocessor;
00811 $this->parser = $preprocessor->parser;
00812 $this->title = $this->parser->mTitle;
00813 $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
00814 $this->loopCheckHash = array();
00815 $this->depth = 0;
00816 }
00817
00822 function newChild( $args = false, $title = false ) {
00823 $namedArgs = array();
00824 $numberedArgs = array();
00825 if ( $title === false ) {
00826 $title = $this->title;
00827 }
00828 if ( $args !== false ) {
00829 $xpath = false;
00830 if ( $args instanceof PPNode_Hash_Array ) {
00831 $args = $args->value;
00832 } elseif ( !is_array( $args ) ) {
00833 throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
00834 }
00835 foreach ( $args as $arg ) {
00836 $bits = $arg->splitArg();
00837 if ( $bits['index'] !== '' ) {
00838
00839 $numberedArgs[$bits['index']] = $bits['value'];
00840 unset( $namedArgs[$bits['index']] );
00841 } else {
00842
00843 $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
00844 $namedArgs[$name] = $bits['value'];
00845 unset( $numberedArgs[$name] );
00846 }
00847 }
00848 }
00849 return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
00850 }
00851
00852 function expand( $root, $flags = 0 ) {
00853 static $expansionDepth = 0;
00854 if ( is_string( $root ) ) {
00855 return $root;
00856 }
00857
00858 if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount )
00859 {
00860 return '<span class="error">Node-count limit exceeded</span>';
00861 }
00862 if ( $expansionDepth > $this->parser->mOptions->mMaxPPExpandDepth ) {
00863 return '<span class="error">Expansion depth limit exceeded</span>';
00864 }
00865 ++$expansionDepth;
00866
00867 $outStack = array( '', '' );
00868 $iteratorStack = array( false, $root );
00869 $indexStack = array( 0, 0 );
00870
00871 while ( count( $iteratorStack ) > 1 ) {
00872 $level = count( $outStack ) - 1;
00873 $iteratorNode =& $iteratorStack[ $level ];
00874 $out =& $outStack[$level];
00875 $index =& $indexStack[$level];
00876
00877 if ( is_array( $iteratorNode ) ) {
00878 if ( $index >= count( $iteratorNode ) ) {
00879
00880 $iteratorStack[$level] = false;
00881 $contextNode = false;
00882 } else {
00883 $contextNode = $iteratorNode[$index];
00884 $index++;
00885 }
00886 } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
00887 if ( $index >= $iteratorNode->getLength() ) {
00888
00889 $iteratorStack[$level] = false;
00890 $contextNode = false;
00891 } else {
00892 $contextNode = $iteratorNode->item( $index );
00893 $index++;
00894 }
00895 } else {
00896
00897
00898 $contextNode = $iteratorStack[$level];
00899 $iteratorStack[$level] = false;
00900 }
00901
00902 $newIterator = false;
00903
00904 if ( $contextNode === false ) {
00905
00906 } elseif ( is_string( $contextNode ) ) {
00907 $out .= $contextNode;
00908 } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_Hash_Array ) {
00909 $newIterator = $contextNode;
00910 } elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
00911
00912 } elseif ( $contextNode instanceof PPNode_Hash_Text ) {
00913 $out .= $contextNode->value;
00914 } elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
00915 if ( $contextNode->name == 'template' ) {
00916 # Double-brace expansion
00917 $bits = $contextNode->splitTemplate();
00918 if ( $flags & self::NO_TEMPLATES ) {
00919 $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
00920 } else {
00921 $ret = $this->parser->braceSubstitution( $bits, $this );
00922 if ( isset( $ret['object'] ) ) {
00923 $newIterator = $ret['object'];
00924 } else {
00925 $out .= $ret['text'];
00926 }
00927 }
00928 } elseif ( $contextNode->name == 'tplarg' ) {
00929 # Triple-brace expansion
00930 $bits = $contextNode->splitTemplate();
00931 if ( $flags & self::NO_ARGS ) {
00932 $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] );
00933 } else {
00934 $ret = $this->parser->argSubstitution( $bits, $this );
00935 if ( isset( $ret['object'] ) ) {
00936 $newIterator = $ret['object'];
00937 } else {
00938 $out .= $ret['text'];
00939 }
00940 }
00941 } elseif ( $contextNode->name == 'comment' ) {
00942 # HTML-style comment
00943 # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
00944 if ( $this->parser->ot['html']
00945 || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
00946 || ( $flags & self::STRIP_COMMENTS ) )
00947 {
00948 $out .= '';
00949 }
00950 # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
00951 # Not in RECOVER_COMMENTS mode (extractSections) though
00952 elseif ( $this->parser->ot['wiki'] && ! ( $flags & self::RECOVER_COMMENTS ) ) {
00953 $out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
00954 }
00955 # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
00956 else {
00957 $out .= $contextNode->firstChild->value;
00958 }
00959 } elseif ( $contextNode->name == 'ignore' ) {
00960 # Output suppression used by <includeonly> etc.
00961 # OT_WIKI will only respect <ignore> in substed templates.
00962 # The other output types respect it unless NO_IGNORE is set.
00963 # extractSections() sets NO_IGNORE and so never respects it.
00964 if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) {
00965 $out .= $contextNode->firstChild->value;
00966 } else {
00967
00968 }
00969 } elseif ( $contextNode->name == 'ext' ) {
00970 # Extension tag
00971 $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null );
00972 $out .= $this->parser->extensionSubstitution( $bits, $this );
00973 } elseif ( $contextNode->name == 'h' ) {
00974 # Heading
00975 if ( $this->parser->ot['html'] ) {
00976 # Expand immediately and insert heading index marker
00977 $s = '';
00978 for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) {
00979 $s .= $this->expand( $node, $flags );
00980 }
00981
00982 $bits = $contextNode->splitHeading();
00983 $titleText = $this->title->getPrefixedDBkey();
00984 $this->parser->mHeadings[] = array( $titleText, $bits['i'] );
00985 $serial = count( $this->parser->mHeadings ) - 1;
00986 $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX;
00987 $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
00988 $this->parser->mStripState->general->setPair( $marker, '' );
00989 $out .= $s;
00990 } else {
00991 # Expand in virtual stack
00992 $newIterator = $contextNode->getChildren();
00993 }
00994 } else {
00995 # Generic recursive expansion
00996 $newIterator = $contextNode->getChildren();
00997 }
00998 } else {
00999 throw new MWException( __METHOD__.': Invalid parameter type' );
01000 }
01001
01002 if ( $newIterator !== false ) {
01003 $outStack[] = '';
01004 $iteratorStack[] = $newIterator;
01005 $indexStack[] = 0;
01006 } elseif ( $iteratorStack[$level] === false ) {
01007
01008
01009 while ( $iteratorStack[$level] === false && $level > 0 ) {
01010 $outStack[$level - 1] .= $out;
01011 array_pop( $outStack );
01012 array_pop( $iteratorStack );
01013 array_pop( $indexStack );
01014 $level--;
01015 }
01016 }
01017 }
01018 --$expansionDepth;
01019 return $outStack[0];
01020 }
01021
01022 function implodeWithFlags( $sep, $flags ) {
01023 $args = array_slice( func_get_args(), 2 );
01024
01025 $first = true;
01026 $s = '';
01027 foreach ( $args as $root ) {
01028 if ( $root instanceof PPNode_Hash_Array ) {
01029 $root = $root->value;
01030 }
01031 if ( !is_array( $root ) ) {
01032 $root = array( $root );
01033 }
01034 foreach ( $root as $node ) {
01035 if ( $first ) {
01036 $first = false;
01037 } else {
01038 $s .= $sep;
01039 }
01040 $s .= $this->expand( $node, $flags );
01041 }
01042 }
01043 return $s;
01044 }
01045
01050 function implode( $sep ) {
01051 $args = array_slice( func_get_args(), 1 );
01052
01053 $first = true;
01054 $s = '';
01055 foreach ( $args as $root ) {
01056 if ( $root instanceof PPNode_Hash_Array ) {
01057 $root = $root->value;
01058 }
01059 if ( !is_array( $root ) ) {
01060 $root = array( $root );
01061 }
01062 foreach ( $root as $node ) {
01063 if ( $first ) {
01064 $first = false;
01065 } else {
01066 $s .= $sep;
01067 }
01068 $s .= $this->expand( $node );
01069 }
01070 }
01071 return $s;
01072 }
01073
01078 function virtualImplode( $sep ) {
01079 $args = array_slice( func_get_args(), 1 );
01080 $out = array();
01081 $first = true;
01082
01083 foreach ( $args as $root ) {
01084 if ( $root instanceof PPNode_Hash_Array ) {
01085 $root = $root->value;
01086 }
01087 if ( !is_array( $root ) ) {
01088 $root = array( $root );
01089 }
01090 foreach ( $root as $node ) {
01091 if ( $first ) {
01092 $first = false;
01093 } else {
01094 $out[] = $sep;
01095 }
01096 $out[] = $node;
01097 }
01098 }
01099 return new PPNode_Hash_Array( $out );
01100 }
01101
01105 function virtualBracketedImplode( $start, $sep, $end ) {
01106 $args = array_slice( func_get_args(), 3 );
01107 $out = array( $start );
01108 $first = true;
01109
01110 foreach ( $args as $root ) {
01111 if ( $root instanceof PPNode_Hash_Array ) {
01112 $root = $root->value;
01113 }
01114 if ( !is_array( $root ) ) {
01115 $root = array( $root );
01116 }
01117 foreach ( $root as $node ) {
01118 if ( $first ) {
01119 $first = false;
01120 } else {
01121 $out[] = $sep;
01122 }
01123 $out[] = $node;
01124 }
01125 }
01126 $out[] = $end;
01127 return new PPNode_Hash_Array( $out );
01128 }
01129
01130 function __toString() {
01131 return 'frame{}';
01132 }
01133
01134 function getPDBK( $level = false ) {
01135 if ( $level === false ) {
01136 return $this->title->getPrefixedDBkey();
01137 } else {
01138 return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
01139 }
01140 }
01141
01145 function isEmpty() {
01146 return true;
01147 }
01148
01149 function getArgument( $name ) {
01150 return false;
01151 }
01152
01156 function loopCheck( $title ) {
01157 return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
01158 }
01159
01163 function isTemplate() {
01164 return false;
01165 }
01166 }
01167
01172 class PPTemplateFrame_Hash extends PPFrame_Hash {
01173 var $numberedArgs, $namedArgs, $parent;
01174 var $numberedExpansionCache, $namedExpansionCache;
01175
01176 function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
01177 $this->preprocessor = $preprocessor;
01178 $this->parser = $preprocessor->parser;
01179 $this->parent = $parent;
01180 $this->numberedArgs = $numberedArgs;
01181 $this->namedArgs = $namedArgs;
01182 $this->title = $title;
01183 $pdbk = $title ? $title->getPrefixedDBkey() : false;
01184 $this->titleCache = $parent->titleCache;
01185 $this->titleCache[] = $pdbk;
01186 $this->loopCheckHash = $parent->loopCheckHash;
01187 if ( $pdbk !== false ) {
01188 $this->loopCheckHash[$pdbk] = true;
01189 }
01190 $this->depth = $parent->depth + 1;
01191 $this->numberedExpansionCache = $this->namedExpansionCache = array();
01192 }
01193
01194 function __toString() {
01195 $s = 'tplframe{';
01196 $first = true;
01197 $args = $this->numberedArgs + $this->namedArgs;
01198 foreach ( $args as $name => $value ) {
01199 if ( $first ) {
01200 $first = false;
01201 } else {
01202 $s .= ', ';
01203 }
01204 $s .= "\"$name\":\"" .
01205 str_replace( '"', '\\"', $value->__toString() ) . '"';
01206 }
01207 $s .= '}';
01208 return $s;
01209 }
01213 function isEmpty() {
01214 return !count( $this->numberedArgs ) && !count( $this->namedArgs );
01215 }
01216
01217 function getArguments() {
01218 $arguments = array();
01219 foreach ( array_merge(
01220 array_keys($this->numberedArgs),
01221 array_keys($this->namedArgs)) as $key ) {
01222 $arguments[$key] = $this->getArgument($key);
01223 }
01224 return $arguments;
01225 }
01226
01227 function getNumberedArguments() {
01228 $arguments = array();
01229 foreach ( array_keys($this->numberedArgs) as $key ) {
01230 $arguments[$key] = $this->getArgument($key);
01231 }
01232 return $arguments;
01233 }
01234
01235 function getNamedArguments() {
01236 $arguments = array();
01237 foreach ( array_keys($this->namedArgs) as $key ) {
01238 $arguments[$key] = $this->getArgument($key);
01239 }
01240 return $arguments;
01241 }
01242
01243 function getNumberedArgument( $index ) {
01244 if ( !isset( $this->numberedArgs[$index] ) ) {
01245 return false;
01246 }
01247 if ( !isset( $this->numberedExpansionCache[$index] ) ) {
01248 # No trimming for unnamed arguments
01249 $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], self::STRIP_COMMENTS );
01250 }
01251 return $this->numberedExpansionCache[$index];
01252 }
01253
01254 function getNamedArgument( $name ) {
01255 if ( !isset( $this->namedArgs[$name] ) ) {
01256 return false;
01257 }
01258 if ( !isset( $this->namedExpansionCache[$name] ) ) {
01259 # Trim named arguments post-expand, for backwards compatibility
01260 $this->namedExpansionCache[$name] = trim(
01261 $this->parent->expand( $this->namedArgs[$name], self::STRIP_COMMENTS ) );
01262 }
01263 return $this->namedExpansionCache[$name];
01264 }
01265
01266 function getArgument( $name ) {
01267 $text = $this->getNumberedArgument( $name );
01268 if ( $text === false ) {
01269 $text = $this->getNamedArgument( $name );
01270 }
01271 return $text;
01272 }
01273
01277 function isTemplate() {
01278 return true;
01279 }
01280 }
01281
01286 class PPCustomFrame_Hash extends PPFrame_Hash {
01287 var $args;
01288
01289 function __construct( $preprocessor, $args ) {
01290 $this->preprocessor = $preprocessor;
01291 $this->parser = $preprocessor->parser;
01292 $this->args = $args;
01293 }
01294
01295 function __toString() {
01296 $s = 'cstmframe{';
01297 $first = true;
01298 foreach ( $this->args as $name => $value ) {
01299 if ( $first ) {
01300 $first = false;
01301 } else {
01302 $s .= ', ';
01303 }
01304 $s .= "\"$name\":\"" .
01305 str_replace( '"', '\\"', $value->__toString() ) . '"';
01306 }
01307 $s .= '}';
01308 return $s;
01309 }
01310
01311 function isEmpty() {
01312 return !count( $this->args );
01313 }
01314
01315 function getArgument( $index ) {
01316 if ( !isset( $this->args[$index] ) ) {
01317 return false;
01318 }
01319 return $this->args[$index];
01320 }
01321 }
01322
01326 class PPNode_Hash_Tree implements PPNode {
01327 var $name, $firstChild, $lastChild, $nextSibling;
01328
01329 function __construct( $name ) {
01330 $this->name = $name;
01331 $this->firstChild = $this->lastChild = $this->nextSibling = false;
01332 }
01333
01334 function __toString() {
01335 $inner = '';
01336 $attribs = '';
01337 for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) {
01338 if ( $node instanceof PPNode_Hash_Attr ) {
01339 $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
01340 } else {
01341 $inner .= $node->__toString();
01342 }
01343 }
01344 if ( $inner === '' ) {
01345 return "<{$this->name}$attribs/>";
01346 } else {
01347 return "<{$this->name}$attribs>$inner</{$this->name}>";
01348 }
01349 }
01350
01351 static function newWithText( $name, $text ) {
01352 $obj = new self( $name );
01353 $obj->addChild( new PPNode_Hash_Text( $text ) );
01354 return $obj;
01355 }
01356
01357 function addChild( $node ) {
01358 if ( $this->lastChild === false ) {
01359 $this->firstChild = $this->lastChild = $node;
01360 } else {
01361 $this->lastChild->nextSibling = $node;
01362 $this->lastChild = $node;
01363 }
01364 }
01365
01366 function getChildren() {
01367 $children = array();
01368 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
01369 $children[] = $child;
01370 }
01371 return new PPNode_Hash_Array( $children );
01372 }
01373
01374 function getFirstChild() {
01375 return $this->firstChild;
01376 }
01377
01378 function getNextSibling() {
01379 return $this->nextSibling;
01380 }
01381
01382 function getChildrenOfType( $name ) {
01383 $children = array();
01384 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
01385 if ( isset( $child->name ) && $child->name === $name ) {
01386 $children[] = $name;
01387 }
01388 }
01389 return $children;
01390 }
01391
01392 function getLength() { return false; }
01393 function item( $i ) { return false; }
01394
01395 function getName() {
01396 return $this->name;
01397 }
01398
01405 function splitArg() {
01406 $bits = array();
01407 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
01408 if ( !isset( $child->name ) ) {
01409 continue;
01410 }
01411 if ( $child->name === 'name' ) {
01412 $bits['name'] = $child;
01413 if ( $child->firstChild instanceof PPNode_Hash_Attr
01414 && $child->firstChild->name === 'index' )
01415 {
01416 $bits['index'] = $child->firstChild->value;
01417 }
01418 } elseif ( $child->name === 'value' ) {
01419 $bits['value'] = $child;
01420 }
01421 }
01422
01423 if ( !isset( $bits['name'] ) ) {
01424 throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
01425 }
01426 if ( !isset( $bits['index'] ) ) {
01427 $bits['index'] = '';
01428 }
01429 return $bits;
01430 }
01431
01436 function splitExt() {
01437 $bits = array();
01438 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
01439 if ( !isset( $child->name ) ) {
01440 continue;
01441 }
01442 if ( $child->name == 'name' ) {
01443 $bits['name'] = $child;
01444 } elseif ( $child->name == 'attr' ) {
01445 $bits['attr'] = $child;
01446 } elseif ( $child->name == 'inner' ) {
01447 $bits['inner'] = $child;
01448 } elseif ( $child->name == 'close' ) {
01449 $bits['close'] = $child;
01450 }
01451 }
01452 if ( !isset( $bits['name'] ) ) {
01453 throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
01454 }
01455 return $bits;
01456 }
01457
01461 function splitHeading() {
01462 if ( $this->name !== 'h' ) {
01463 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
01464 }
01465 $bits = array();
01466 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
01467 if ( !isset( $child->name ) ) {
01468 continue;
01469 }
01470 if ( $child->name == 'i' ) {
01471 $bits['i'] = $child->value;
01472 } elseif ( $child->name == 'level' ) {
01473 $bits['level'] = $child->value;
01474 }
01475 }
01476 if ( !isset( $bits['i'] ) ) {
01477 throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
01478 }
01479 return $bits;
01480 }
01481
01485 function splitTemplate() {
01486 $parts = array();
01487 $bits = array( 'lineStart' => '' );
01488 for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
01489 if ( !isset( $child->name ) ) {
01490 continue;
01491 }
01492 if ( $child->name == 'title' ) {
01493 $bits['title'] = $child;
01494 }
01495 if ( $child->name == 'part' ) {
01496 $parts[] = $child;
01497 }
01498 if ( $child->name == 'lineStart' ) {
01499 $bits['lineStart'] = '1';
01500 }
01501 }
01502 if ( !isset( $bits['title'] ) ) {
01503 throw new MWException( 'Invalid node passed to ' . __METHOD__ );
01504 }
01505 $bits['parts'] = new PPNode_Hash_Array( $parts );
01506 return $bits;
01507 }
01508 }
01509
01513 class PPNode_Hash_Text implements PPNode {
01514 var $value, $nextSibling;
01515
01516 function __construct( $value ) {
01517 if ( is_object( $value ) ) {
01518 throw new MWException( __CLASS__ . ' given object instead of string' );
01519 }
01520 $this->value = $value;
01521 }
01522
01523 function __toString() {
01524 return htmlspecialchars( $this->value );
01525 }
01526
01527 function getNextSibling() {
01528 return $this->nextSibling;
01529 }
01530
01531 function getChildren() { return false; }
01532 function getFirstChild() { return false; }
01533 function getChildrenOfType( $name ) { return false; }
01534 function getLength() { return false; }
01535 function item( $i ) { return false; }
01536 function getName() { return '#text'; }
01537 function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
01538 function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
01539 function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
01540 }
01541
01545 class PPNode_Hash_Array implements PPNode {
01546 var $value, $nextSibling;
01547
01548 function __construct( $value ) {
01549 $this->value = $value;
01550 }
01551
01552 function __toString() {
01553 return var_export( $this, true );
01554 }
01555
01556 function getLength() {
01557 return count( $this->value );
01558 }
01559
01560 function item( $i ) {
01561 return $this->value[$i];
01562 }
01563
01564 function getName() { return '#nodelist'; }
01565
01566 function getNextSibling() {
01567 return $this->nextSibling;
01568 }
01569
01570 function getChildren() { return false; }
01571 function getFirstChild() { return false; }
01572 function getChildrenOfType( $name ) { return false; }
01573 function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
01574 function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
01575 function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
01576 }
01577
01581 class PPNode_Hash_Attr implements PPNode {
01582 var $name, $value, $nextSibling;
01583
01584 function __construct( $name, $value ) {
01585 $this->name = $name;
01586 $this->value = $value;
01587 }
01588
01589 function __toString() {
01590 return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
01591 }
01592
01593 function getName() {
01594 return $this->name;
01595 }
01596
01597 function getNextSibling() {
01598 return $this->nextSibling;
01599 }
01600
01601 function getChildren() { return false; }
01602 function getFirstChild() { return false; }
01603 function getChildrenOfType( $name ) { return false; }
01604 function getLength() { return false; }
01605 function item( $i ) { return false; }
01606 function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
01607 function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
01608 function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
01609 }