Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 69 |
TokenCollector | |
0.00% |
0 / 1 |
|
0.00% |
0 / 8 |
1560 | |
0.00% |
0 / 69 |
__construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
type | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
|||||
name | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
|||||
toEnd | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
|||||
ackEnd | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
|||||
transformation | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
|||||
onDelimiterToken | |
0.00% |
0 / 1 |
210 | |
0.00% |
0 / 40 |
|||
onAnyToken | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
buildMetaToken | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 10 |
|||
buildStrippedMetaToken | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 10 |
|||
onTag | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 1 |
|||
onEnd | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 1 |
|||
onAny | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 1 |
<?php | |
declare( strict_types = 1 ); | |
namespace Wikimedia\Parsoid\Wt2Html\TT; | |
use Wikimedia\Assert\Assert; | |
use Wikimedia\Parsoid\NodeData\DataParsoid; | |
use Wikimedia\Parsoid\Tokens\EndTagTk; | |
use Wikimedia\Parsoid\Tokens\EOFTk; | |
use Wikimedia\Parsoid\Tokens\KV; | |
use Wikimedia\Parsoid\Tokens\SelfclosingTagTk; | |
use Wikimedia\Parsoid\Tokens\SourceRange; | |
use Wikimedia\Parsoid\Tokens\TagTk; | |
use Wikimedia\Parsoid\Tokens\Token; | |
use Wikimedia\Parsoid\Utils\PHPUtils; | |
use Wikimedia\Parsoid\Wt2Html\TokenTransformManager; | |
/** | |
* Small utility class that encapsulates the common 'collect all tokens | |
* starting from a token of type x until token of type y or (optionally) the | |
* end-of-input'. | |
*/ | |
abstract class TokenCollector extends TokenHandler { | |
protected $scopeStack; | |
/** | |
* @param TokenTransformManager $manager manager enviroment | |
* @param array $options various configuration options | |
*/ | |
public function __construct( TokenTransformManager $manager, array $options ) { | |
parent::__construct( $manager, $options ); | |
$this->onAnyEnabled = false; | |
$this->scopeStack = []; | |
} | |
/** | |
* Token type to register for ('tag', 'text' etc) | |
* @return string | |
*/ | |
abstract protected function type(): string; | |
/** | |
* (optional, only for token type 'tag'): tag name. | |
* @return string | |
*/ | |
abstract protected function name(): string; | |
/** | |
* Match the 'end' tokens as closing tag as well (accept unclosed sections). | |
* @return bool | |
*/ | |
abstract protected function toEnd(): bool; | |
/** | |
* Whether to transform unmatched end tags. If this returns true, | |
* unbalanced end tags will be passed to transform(). If it returns false, | |
* they will be left in the token stream unmodified. | |
* | |
* @return bool | |
*/ | |
abstract protected function ackEnd(): bool; | |
/** | |
* When an end delimiter is found, this function is called with the | |
* collected token array including the start and end delimiters. The | |
* subclass should transform it and return the result. | |
* | |
* @param array $array | |
* @return TokenHandlerResult | |
*/ | |
abstract protected function transformation( array $array ): TokenHandlerResult; | |
/** | |
* Handle the delimiter token. | |
* XXX: Adjust to sync phase callback when that is modified! | |
* @param Token $token | |
* @return TokenHandlerResult|null | |
*/ | |
private function onDelimiterToken( Token $token ): ?TokenHandlerResult { | |
$haveOpenTag = count( $this->scopeStack ) > 0; | |
if ( $token instanceof TagTk ) { | |
if ( count( $this->scopeStack ) === 0 ) { | |
$this->onAnyEnabled = true; | |
// Set up transforms | |
$this->env->log( 'debug', 'starting collection on ', $token ); | |
} | |
// Push a new scope | |
$newScope = []; | |
$this->scopeStack[] = &$newScope; | |
$newScope[] = $token; | |
return new TokenHandlerResult( [] ); | |
} elseif ( $token instanceof SelfclosingTagTk ) { | |
// We need to handle <ref /> for example, so call the handler. | |
return $this->transformation( [ $token, $token ] ); | |
} elseif ( $haveOpenTag ) { | |
// EOFTk or EndTagTk | |
$this->env->log( 'debug', 'finishing collection on ', $token ); | |
// Pop top scope and push token onto it | |
$activeTokens = array_pop( $this->scopeStack ); | |
$activeTokens[] = $token; | |
if ( $token instanceof EndTagTk ) { | |
// Transformation receives all collected tokens instead of a single token. | |
$res = $this->transformation( $activeTokens ); | |
if ( count( $this->scopeStack ) === 0 ) { | |
$this->onAnyEnabled = false; | |
return $res; | |
} else { | |
// Merge tokens onto parent scope and return []. | |
// Only when we hit the bottom of the stack, | |
// we will return the collapsed token stream. | |
$topScope = array_pop( $this->scopeStack ); | |
array_push( $this->scopeStack, array_merge( $topScope, $res->tokens ) ); | |
return new TokenHandlerResult( [] ); | |
} | |
} else { | |
// EOF -- collapse stack! | |
$allToks = []; | |
for ( $i = 0, $n = count( $this->scopeStack ); $i < $n; $i++ ) { | |
PHPUtils::pushArray( $allToks, $this->scopeStack[$i] ); | |
} | |
PHPUtils::pushArray( $allToks, $activeTokens ); | |
$res = $this->toEnd() ? $this->transformation( $allToks ) : new TokenHandlerResult( $allToks ); | |
if ( $res->tokens !== null | |
&& count( $res->tokens ) | |
&& !( PHPUtils::lastItem( $res->tokens ) instanceof EOFTk ) | |
) { | |
$this->env->log( 'error', $this::name(), 'handler dropped the EOFTk!' ); | |
// preserve the EOFTk | |
$res->tokens[] = $token; | |
} | |
$this->scopeStack = []; | |
$this->onAnyEnabled = false; | |
return $res; | |
} | |
} else { | |
// EndTagTk should be the only one that can reach here. | |
Assert::invariant( $token instanceof EndTagTk, 'Expected an end tag.' ); | |
if ( $this->ackEnd() ) { | |
return $this->transformation( [ $token ] ); | |
} else { | |
// An unbalanced end tag. Ignore it. | |
return null; | |
} | |
} | |
} | |
/** | |
* Handle 'any' token in between delimiter tokens. Activated when | |
* encountering the delimiter token, and collects all tokens until the end | |
* token is reached. | |
* @param Token|string $token | |
* @return TokenHandlerResult | |
*/ | |
private function onAnyToken( $token ): TokenHandlerResult { | |
// Simply collect anything ordinary in between | |
$this->scopeStack[count( $this->scopeStack ) - 1][] = $token; | |
return new TokenHandlerResult( [] ); | |
} | |
/** | |
* This helper function will build a meta token in the right way for these tags. | |
* @param TokenTransformManager $manager | |
* @param string $tokenName | |
* @param bool $isEnd | |
* @param SourceRange $tsr | |
* @param ?string $src | |
* @return SelfclosingTagTk | |
*/ | |
public static function buildMetaToken( | |
TokenTransformManager $manager, string $tokenName, bool $isEnd, | |
SourceRange $tsr, ?string $src | |
): SelfclosingTagTk { | |
if ( $isEnd ) { | |
$tokenName .= '/End'; | |
} | |
$srcText = $manager->getFrame()->getSrcText(); | |
$newSrc = $tsr->substr( $srcText ); | |
$dp = new DataParsoid; | |
$dp->tsr = $tsr; | |
$dp->src = $newSrc; | |
return new SelfclosingTagTk( 'meta', | |
[ new KV( 'typeof', $tokenName ) ], | |
$dp | |
); | |
} | |
/** | |
* @param TokenTransformManager $manager | |
* @param string $tokenName | |
* @param Token $startDelim | |
* @param ?Token $endDelim | |
* @return SelfclosingTagTk | |
*/ | |
protected static function buildStrippedMetaToken( | |
TokenTransformManager $manager, string $tokenName, Token $startDelim, | |
?Token $endDelim | |
): SelfclosingTagTk { | |
$da = $startDelim->dataAttribs; | |
$tsr0 = $da ? $da->tsr : null; | |
$t0 = $tsr0 ? $tsr0->start : null; | |
$t1 = null; | |
if ( $endDelim !== null ) { | |
$da = $endDelim->dataAttribs ?? null; | |
$tsr1 = $da ? $da->tsr : null; | |
$t1 = $tsr1 ? $tsr1->end : null; | |
} else { | |
$t1 = strlen( $manager->getFrame()->getSrcText() ); | |
} | |
return self::buildMetaToken( $manager, $tokenName, false, new SourceRange( $t0, $t1 ), '' ); | |
} | |
/** | |
* @inheritDoc | |
*/ | |
public function onTag( Token $token ): ?TokenHandlerResult { | |
return $token->getName() === $this->name() ? $this->onDelimiterToken( $token ) : null; | |
} | |
/** | |
* @inheritDoc | |
*/ | |
public function onEnd( EOFTk $token ): ?TokenHandlerResult { | |
return $this->onAnyEnabled ? $this->onDelimiterToken( $token ) : null; | |
} | |
/** | |
* @inheritDoc | |
*/ | |
public function onAny( $token ): ?TokenHandlerResult { | |
return $this->onAnyToken( $token ); | |
} | |
} |