Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 69 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
TokenCollector | |
0.00% |
0 / 69 |
|
0.00% |
0 / 8 |
812 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
type | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
name | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
toEnd | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
ackEnd | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
transformation | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
onDelimiterToken | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
182 | |||
onAnyToken | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
buildMetaToken | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
buildStrippedMetaToken | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
onTag | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
onEnd | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
onAny | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace Wikimedia\Parsoid\Wt2Html\TT; |
5 | |
6 | use Wikimedia\Assert\Assert; |
7 | use Wikimedia\Parsoid\NodeData\DataParsoid; |
8 | use Wikimedia\Parsoid\Tokens\EndTagTk; |
9 | use Wikimedia\Parsoid\Tokens\EOFTk; |
10 | use Wikimedia\Parsoid\Tokens\KV; |
11 | use Wikimedia\Parsoid\Tokens\SelfclosingTagTk; |
12 | use Wikimedia\Parsoid\Tokens\SourceRange; |
13 | use Wikimedia\Parsoid\Tokens\TagTk; |
14 | use Wikimedia\Parsoid\Tokens\Token; |
15 | use Wikimedia\Parsoid\Utils\PHPUtils; |
16 | use Wikimedia\Parsoid\Wt2Html\TokenTransformManager; |
17 | |
18 | /** |
19 | * Small utility class that encapsulates the common 'collect all tokens |
20 | * starting from a token of type x until token of type y or (optionally) the |
21 | * end-of-input'. |
22 | */ |
23 | abstract class TokenCollector extends TokenHandler { |
24 | protected $scopeStack; |
25 | |
26 | /** |
27 | * @param TokenTransformManager $manager manager enviroment |
28 | * @param array $options various configuration options |
29 | */ |
30 | public function __construct( TokenTransformManager $manager, array $options ) { |
31 | parent::__construct( $manager, $options ); |
32 | $this->onAnyEnabled = false; |
33 | $this->scopeStack = []; |
34 | } |
35 | |
36 | /** |
37 | * Token type to register for ('tag', 'text' etc) |
38 | * @return string |
39 | */ |
40 | abstract protected function type(): string; |
41 | |
42 | /** |
43 | * (optional, only for token type 'tag'): tag name. |
44 | * @return string |
45 | */ |
46 | abstract protected function name(): string; |
47 | |
48 | /** |
49 | * Match the 'end' tokens as closing tag as well (accept unclosed sections). |
50 | * @return bool |
51 | */ |
52 | abstract protected function toEnd(): bool; |
53 | |
54 | /** |
55 | * Whether to transform unmatched end tags. If this returns true, |
56 | * unbalanced end tags will be passed to transform(). If it returns false, |
57 | * they will be left in the token stream unmodified. |
58 | * |
59 | * @return bool |
60 | */ |
61 | abstract protected function ackEnd(): bool; |
62 | |
63 | /** |
64 | * When an end delimiter is found, this function is called with the |
65 | * collected token array including the start and end delimiters. The |
66 | * subclass should transform it and return the result. |
67 | * |
68 | * @param array $array |
69 | * @return TokenHandlerResult |
70 | */ |
71 | abstract protected function transformation( array $array ): TokenHandlerResult; |
72 | |
73 | /** |
74 | * Handle the delimiter token. |
75 | * XXX: Adjust to sync phase callback when that is modified! |
76 | * @param Token $token |
77 | * @return TokenHandlerResult|null |
78 | */ |
79 | private function onDelimiterToken( Token $token ): ?TokenHandlerResult { |
80 | $haveOpenTag = count( $this->scopeStack ) > 0; |
81 | if ( $token instanceof TagTk ) { |
82 | if ( count( $this->scopeStack ) === 0 ) { |
83 | $this->onAnyEnabled = true; |
84 | // Set up transforms |
85 | $this->env->log( 'debug', 'starting collection on ', $token ); |
86 | } |
87 | |
88 | // Push a new scope |
89 | $newScope = []; |
90 | $this->scopeStack[] = &$newScope; |
91 | $newScope[] = $token; |
92 | |
93 | return new TokenHandlerResult( [] ); |
94 | } elseif ( $token instanceof SelfclosingTagTk ) { |
95 | // We need to handle <ref /> for example, so call the handler. |
96 | return $this->transformation( [ $token, $token ] ); |
97 | } elseif ( $haveOpenTag ) { |
98 | // EOFTk or EndTagTk |
99 | $this->env->log( 'debug', 'finishing collection on ', $token ); |
100 | |
101 | // Pop top scope and push token onto it |
102 | $activeTokens = array_pop( $this->scopeStack ); |
103 | $activeTokens[] = $token; |
104 | |
105 | if ( $token instanceof EndTagTk ) { |
106 | // Transformation receives all collected tokens instead of a single token. |
107 | $res = $this->transformation( $activeTokens ); |
108 | |
109 | if ( count( $this->scopeStack ) === 0 ) { |
110 | $this->onAnyEnabled = false; |
111 | return $res; |
112 | } else { |
113 | // Merge tokens onto parent scope and return []. |
114 | // Only when we hit the bottom of the stack, |
115 | // we will return the collapsed token stream. |
116 | $topScope = array_pop( $this->scopeStack ); |
117 | array_push( $this->scopeStack, array_merge( $topScope, $res->tokens ) ); |
118 | return new TokenHandlerResult( [] ); |
119 | } |
120 | } else { |
121 | // EOF -- collapse stack! |
122 | $allToks = []; |
123 | for ( $i = 0, $n = count( $this->scopeStack ); $i < $n; $i++ ) { |
124 | PHPUtils::pushArray( $allToks, $this->scopeStack[$i] ); |
125 | } |
126 | PHPUtils::pushArray( $allToks, $activeTokens ); |
127 | |
128 | $res = $this->toEnd() ? $this->transformation( $allToks ) : new TokenHandlerResult( $allToks ); |
129 | if ( $res->tokens !== null |
130 | && count( $res->tokens ) |
131 | && !( PHPUtils::lastItem( $res->tokens ) instanceof EOFTk ) |
132 | ) { |
133 | $this->env->log( 'error', $this::name(), 'handler dropped the EOFTk!' ); |
134 | |
135 | // preserve the EOFTk |
136 | $res->tokens[] = $token; |
137 | } |
138 | |
139 | $this->scopeStack = []; |
140 | $this->onAnyEnabled = false; |
141 | return $res; |
142 | } |
143 | } else { |
144 | // EndTagTk should be the only one that can reach here. |
145 | Assert::invariant( $token instanceof EndTagTk, 'Expected an end tag.' ); |
146 | if ( $this->ackEnd() ) { |
147 | return $this->transformation( [ $token ] ); |
148 | } else { |
149 | // An unbalanced end tag. Ignore it. |
150 | return null; |
151 | } |
152 | } |
153 | } |
154 | |
155 | /** |
156 | * Handle 'any' token in between delimiter tokens. Activated when |
157 | * encountering the delimiter token, and collects all tokens until the end |
158 | * token is reached. |
159 | * @param Token|string $token |
160 | * @return TokenHandlerResult |
161 | */ |
162 | private function onAnyToken( $token ): TokenHandlerResult { |
163 | // Simply collect anything ordinary in between |
164 | $this->scopeStack[count( $this->scopeStack ) - 1][] = $token; |
165 | return new TokenHandlerResult( [] ); |
166 | } |
167 | |
168 | /** |
169 | * This helper function will build a meta token in the right way for these tags. |
170 | * @param TokenTransformManager $manager |
171 | * @param string $tokenName |
172 | * @param bool $isEnd |
173 | * @param SourceRange $tsr |
174 | * @param ?string $src |
175 | * @return SelfclosingTagTk |
176 | */ |
177 | public static function buildMetaToken( |
178 | TokenTransformManager $manager, string $tokenName, bool $isEnd, |
179 | SourceRange $tsr, ?string $src |
180 | ): SelfclosingTagTk { |
181 | if ( $isEnd ) { |
182 | $tokenName .= '/End'; |
183 | } |
184 | |
185 | $srcText = $manager->getFrame()->getSrcText(); |
186 | $newSrc = $tsr->substr( $srcText ); |
187 | $dp = new DataParsoid; |
188 | $dp->tsr = $tsr; |
189 | $dp->src = $newSrc; |
190 | |
191 | return new SelfclosingTagTk( 'meta', |
192 | [ new KV( 'typeof', $tokenName ) ], |
193 | $dp |
194 | ); |
195 | } |
196 | |
197 | /** |
198 | * @param TokenTransformManager $manager |
199 | * @param string $tokenName |
200 | * @param Token $startDelim |
201 | * @param ?Token $endDelim |
202 | * @return SelfclosingTagTk |
203 | */ |
204 | protected static function buildStrippedMetaToken( |
205 | TokenTransformManager $manager, string $tokenName, Token $startDelim, |
206 | ?Token $endDelim |
207 | ): SelfclosingTagTk { |
208 | $da = $startDelim->dataParsoid; |
209 | $tsr0 = $da ? $da->tsr : null; |
210 | $t0 = $tsr0 ? $tsr0->start : null; |
211 | $t1 = null; |
212 | |
213 | if ( $endDelim !== null ) { |
214 | $da = $endDelim->dataParsoid ?? null; |
215 | $tsr1 = $da ? $da->tsr : null; |
216 | $t1 = $tsr1 ? $tsr1->end : null; |
217 | } else { |
218 | $t1 = strlen( $manager->getFrame()->getSrcText() ); |
219 | } |
220 | |
221 | return self::buildMetaToken( $manager, $tokenName, false, new SourceRange( $t0, $t1 ), '' ); |
222 | } |
223 | |
224 | /** |
225 | * @inheritDoc |
226 | */ |
227 | public function onTag( Token $token ): ?TokenHandlerResult { |
228 | return $token->getName() === $this->name() ? $this->onDelimiterToken( $token ) : null; |
229 | } |
230 | |
231 | /** |
232 | * @inheritDoc |
233 | */ |
234 | public function onEnd( EOFTk $token ): ?TokenHandlerResult { |
235 | return $this->onAnyEnabled ? $this->onDelimiterToken( $token ) : null; |
236 | } |
237 | |
238 | /** |
239 | * @inheritDoc |
240 | */ |
241 | public function onAny( $token ): ?TokenHandlerResult { |
242 | return $this->onAnyToken( $token ); |
243 | } |
244 | } |