Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 69 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
Frame | |
0.00% |
0 / 69 |
|
0.00% |
0 / 10 |
462 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
getEnv | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getArgs | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSrcText | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newChild | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expand | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
loopAndDepthCheck | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
expandArg | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
expandTemplateArg | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace Wikimedia\Parsoid\Wt2Html; |
5 | |
6 | use Wikimedia\Parsoid\Config\Env; |
7 | use Wikimedia\Parsoid\Tokens\EOFTk; |
8 | use Wikimedia\Parsoid\Tokens\KV; |
9 | use Wikimedia\Parsoid\Tokens\SourceRange; |
10 | use Wikimedia\Parsoid\Tokens\Token; |
11 | use Wikimedia\Parsoid\Utils\PHPUtils; |
12 | use Wikimedia\Parsoid\Utils\PipelineUtils; |
13 | use Wikimedia\Parsoid\Utils\Title; |
14 | use Wikimedia\Parsoid\Utils\TokenUtils; |
15 | |
16 | /** |
17 | * A frame represents a template expansion scope including parameters passed |
18 | * to the template (args). It provides a generic 'expand' method which |
19 | * expands / converts individual parameter values in its scope. It also |
20 | * provides methods to check if another expansion would lead to loops or |
21 | * exceed the maximum expansion depth. |
22 | */ |
23 | class Frame { |
24 | /** @var ?Frame */ |
25 | private $parentFrame; |
26 | |
27 | /** @var Env */ |
28 | private $env; |
29 | |
30 | /** @var Title */ |
31 | private $title; |
32 | |
33 | /** @var Params */ |
34 | private $args; |
35 | |
36 | /** @var string */ |
37 | private $srcText; |
38 | |
39 | /** @var int */ |
40 | private $depth; |
41 | |
42 | /** |
43 | * @param Title $title |
44 | * @param Env $env |
45 | * @param KV[] $args |
46 | * @param string $srcText |
47 | * @param ?Frame $parentFrame |
48 | */ |
49 | public function __construct( |
50 | Title $title, Env $env, array $args, string $srcText, |
51 | ?Frame $parentFrame = null |
52 | ) { |
53 | $this->title = $title; |
54 | $this->env = $env; |
55 | $this->args = new Params( $args ); |
56 | $this->srcText = $srcText; |
57 | |
58 | if ( $parentFrame ) { |
59 | $this->parentFrame = $parentFrame; |
60 | $this->depth = $parentFrame->depth + 1; |
61 | } else { |
62 | $this->parentFrame = null; |
63 | $this->depth = 0; |
64 | } |
65 | } |
66 | |
67 | public function getEnv(): Env { |
68 | return $this->env; |
69 | } |
70 | |
71 | public function getTitle(): Title { |
72 | return $this->title; |
73 | } |
74 | |
75 | public function getArgs(): Params { |
76 | return $this->args; |
77 | } |
78 | |
79 | public function getSrcText(): string { |
80 | return $this->srcText; |
81 | } |
82 | |
83 | /** |
84 | * Create a new child frame. |
85 | * @param Title $title |
86 | * @param KV[] $args |
87 | * @param string $srcText |
88 | * @return Frame |
89 | */ |
90 | public function newChild( Title $title, array $args, string $srcText ): Frame { |
91 | return new Frame( $title, $this->env, $args, $srcText, $this ); |
92 | } |
93 | |
94 | /** |
95 | * Expand / convert a thunk (a chunk of tokens not yet fully expanded). |
96 | * @param array<Token|string> $chunk |
97 | * @param array $options |
98 | * @return array<Token|string> |
99 | */ |
100 | public function expand( array $chunk, array $options ): array { |
101 | $this->env->log( 'debug', 'Frame.expand', $chunk ); |
102 | |
103 | if ( !$chunk ) { |
104 | return $chunk; |
105 | } |
106 | |
107 | // Add an EOFTk if it isn't present |
108 | $content = $chunk; |
109 | if ( !( PHPUtils::lastItem( $chunk ) instanceof EOFTk ) ) { |
110 | $content[] = new EOFTk(); |
111 | } |
112 | |
113 | // Downstream template uses should be tracked and wrapped only if: |
114 | // - not in a nested template Ex: {{Templ:Foo}} and we are processing Foo |
115 | // - not in a template use context Ex: {{ .. | {{ here }} | .. }} |
116 | // - the attribute use is wrappable Ex: [[ ... | {{ .. link text }} ]] |
117 | |
118 | $opts = [ |
119 | 'pipelineType' => 'peg-tokens-to-expanded-tokens', |
120 | 'pipelineOpts' => [ |
121 | 'expandTemplates' => $options['expandTemplates'], |
122 | 'inTemplate' => $options['inTemplate'], |
123 | 'attrExpansion' => $options['attrExpansion'] ?? false |
124 | ], |
125 | 'sol' => true, |
126 | 'srcOffsets' => $options['srcOffsets'] ?? null, |
127 | 'tplArgs' => [ 'name' => null, 'title' => null, 'attribs' => [] ] |
128 | ]; |
129 | |
130 | $tokens = PipelineUtils::processContentInPipeline( $this->env, $this, $content, $opts ); |
131 | TokenUtils::stripEOFTkfromTokens( $tokens ); |
132 | return $tokens; |
133 | } |
134 | |
135 | /** |
136 | * Check if expanding a template would lead to a loop, or would exceed the |
137 | * maximum expansion depth. |
138 | * |
139 | * @param Title $title |
140 | * @param int $maxDepth |
141 | * @param bool $ignoreLoop |
142 | * @return ?string null => no error; non-null => error message |
143 | */ |
144 | public function loopAndDepthCheck( Title $title, int $maxDepth, bool $ignoreLoop ): ?string { |
145 | if ( $this->depth > $maxDepth ) { |
146 | // Too deep |
147 | return "Template recursion depth limit exceeded ($maxDepth): "; |
148 | } |
149 | |
150 | if ( $ignoreLoop ) { |
151 | return null; |
152 | } |
153 | |
154 | $frame = $this; |
155 | do { |
156 | if ( $title->equals( $frame->title ) ) { |
157 | // Loop detected |
158 | return 'Template loop detected: '; |
159 | } |
160 | $frame = $frame->parentFrame; |
161 | } while ( $frame ); |
162 | |
163 | // No loop detected. |
164 | return null; |
165 | } |
166 | |
167 | /** |
168 | * @param mixed $arg |
169 | * @param SourceRange $srcOffsets |
170 | * @return array |
171 | */ |
172 | private function expandArg( $arg, SourceRange $srcOffsets ): array { |
173 | if ( is_string( $arg ) ) { |
174 | return [ $arg ]; |
175 | } else { |
176 | return $this->expand( $arg, [ |
177 | 'expandTemplates' => true, |
178 | 'inTemplate' => true, |
179 | 'srcOffsets' => $srcOffsets, |
180 | ] ); |
181 | } |
182 | } |
183 | |
184 | /** |
185 | * @param Token $tplArgToken |
186 | * @return array tokens representing the arg value |
187 | */ |
188 | public function expandTemplateArg( Token $tplArgToken ): array { |
189 | $args = $this->args->named(); |
190 | $attribs = $tplArgToken->attribs; |
191 | |
192 | $expandedKeyToks = $this->expandArg( |
193 | $attribs[0]->k, |
194 | $attribs[0]->srcOffsets->key |
195 | ); |
196 | |
197 | $argName = trim( TokenUtils::tokensToString( $expandedKeyToks ) ); |
198 | $res = $args['dict'][$argName] ?? null; |
199 | |
200 | if ( $res !== null ) { |
201 | $res = isset( $args['namedArgs'][$argName] ) ? |
202 | TokenUtils::tokenTrim( $res ) : $res; |
203 | return is_string( $res ) ? [ $res ] : $res; |
204 | } elseif ( count( $attribs ) > 1 ) { |
205 | return $this->expandArg( |
206 | $attribs[1]->v, |
207 | $attribs[1]->srcOffsets->value |
208 | ); |
209 | } else { |
210 | return array_merge( [ '{{{' ], $expandedKeyToks, [ '}}}' ] ); |
211 | } |
212 | } |
213 | } |