@vituum/vite-plugin-latte 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +2 -2
- package/package.json +10 -11
- package/vendor/autoload.php +19 -1
- package/vendor/bin/latte-lint +16 -4
- package/vendor/composer/ClassLoader.php +72 -65
- package/vendor/composer/InstalledVersions.php +21 -12
- package/vendor/composer/autoload_classmap.php +12 -8
- package/vendor/composer/autoload_namespaces.php +1 -1
- package/vendor/composer/autoload_psr4.php +1 -1
- package/vendor/composer/autoload_real.php +7 -26
- package/vendor/composer/autoload_static.php +13 -9
- package/vendor/composer/installed.json +8 -8
- package/vendor/composer/installed.php +10 -10
- package/vendor/latte/latte/bin/latte-lint +8 -2
- package/vendor/latte/latte/composer.json +1 -1
- package/vendor/latte/latte/readme.md +6 -6
- package/vendor/latte/latte/src/Bridges/Tracy/BlueScreenPanel.php +1 -0
- package/vendor/latte/latte/src/Bridges/Tracy/LattePanel.php +3 -2
- package/vendor/latte/latte/src/Latte/Compiler/Block.php +0 -3
- package/vendor/latte/latte/src/Latte/Compiler/Escaper.php +103 -88
- package/vendor/latte/latte/src/Latte/Compiler/ExpressionBuilder.php +2 -1
- package/vendor/latte/latte/src/Latte/Compiler/Node.php +0 -4
- package/vendor/latte/latte/src/Latte/Compiler/NodeHelpers.php +0 -4
- package/vendor/latte/latte/src/Latte/Compiler/NodeTraverser.php +0 -4
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/AuxiliaryNode.php +11 -3
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/FragmentNode.php +10 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/AttributeNode.php +22 -4
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/ElementNode.php +35 -12
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArgumentNode.php +6 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArrayItemNode.php +12 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayNode.php +4 -31
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignNode.php +15 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignOpNode.php +11 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AuxiliaryNode.php +42 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClassConstantFetchNode.php +2 -2
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClosureNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ConstantFetchNode.php +8 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/IssetNode.php +15 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PostOpNode.php +11 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PreOpNode.php +11 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallNode.php → StaticMethodCallNode.php} +11 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallableNode.php → StaticMethodCallableNode.php} +11 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/TemporaryNode.php +38 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ExpressionNode.php +24 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListItemNode.php +48 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListNode.php +56 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +3 -3
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/InterpolatedStringNode.php +1 -8
- package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +30 -0
- package/vendor/latte/latte/src/Latte/Compiler/Position.php +4 -8
- package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +14 -7
- package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
- package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +3 -7
- package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
- package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +354 -322
- package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
- package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +95 -176
- package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
- package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +182 -126
- package/vendor/latte/latte/src/Latte/Compiler/Token.php +5 -9
- package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +6 -22
- package/vendor/latte/latte/src/Latte/Engine.php +37 -8
- package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +0 -2
- package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
- package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +14 -3
- package/vendor/latte/latte/src/Latte/Essential/Filters.php +8 -6
- package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +1 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +2 -13
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +1 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +0 -3
- package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +4 -13
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +4 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +5 -3
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +5 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +6 -4
- package/vendor/latte/latte/src/Latte/Essential/Nodes/JumpNode.php +26 -23
- package/vendor/latte/latte/src/Latte/Essential/Nodes/NElseNode.php +88 -0
- package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +20 -28
- package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +7 -12
- package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +3 -4
- package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Passes.php +9 -11
- package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +0 -2
- package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +6 -1
- package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +0 -2
- package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +0 -2
- package/vendor/latte/latte/src/Latte/PositionAwareException.php +1 -1
- package/vendor/latte/latte/src/Latte/Runtime/Block.php +0 -4
- package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +43 -51
- package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +0 -2
- package/vendor/latte/latte/src/Latte/Runtime/Filters.php +64 -30
- package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
- package/vendor/latte/latte/src/Latte/Runtime/Template.php +2 -2
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +2 -1
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +3 -3
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallNode.php → StaticMethodCallNode.php} +3 -3
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallableNode.php → StaticMethodCallableNode.php} +2 -2
- package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +0 -2
- package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +11 -9
- package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +0 -2
- package/vendor/latte/latte/src/Latte/exceptions.php +2 -11
- package/vendor/latte/latte/src/Tools/Linter.php +13 -37
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +0 -53
- package/vendor/latte/latte/src/Latte/Strict.php +0 -101
|
@@ -24,13 +24,14 @@ use Latte\SecurityViolationException;
|
|
|
24
24
|
*/
|
|
25
25
|
final class TemplateParserHtml
|
|
26
26
|
{
|
|
27
|
-
use Latte\Strict;
|
|
28
|
-
|
|
29
27
|
/** @var array<string, callable(Tag, TemplateParser): (Node|\Generator|void)> */
|
|
30
28
|
private array /*readonly*/ $attrParsers;
|
|
31
29
|
private ?Html\ElementNode $element = null;
|
|
32
30
|
private TemplateParser /*readonly*/ $parser;
|
|
33
31
|
|
|
32
|
+
/** @var array{string, ?Nodes\Php\ExpressionNode} */
|
|
33
|
+
private ?array $endName = null;
|
|
34
|
+
|
|
34
35
|
|
|
35
36
|
public function __construct(TemplateParser $parser, array $attrParsers)
|
|
36
37
|
{
|
|
@@ -50,9 +51,7 @@ final class TemplateParserHtml
|
|
|
50
51
|
$stream = $this->parser->getStream();
|
|
51
52
|
$token = $stream->peek();
|
|
52
53
|
return match ($token->type) {
|
|
53
|
-
Token::Html_TagOpen => $
|
|
54
|
-
? $this->parseEndTag()
|
|
55
|
-
: $this->parseElement(),
|
|
54
|
+
Token::Html_TagOpen => $this->parseTag(),
|
|
56
55
|
Token::Html_CommentOpen => $this->parseComment(),
|
|
57
56
|
Token::Html_BogusOpen => $this->parseBogusTag(),
|
|
58
57
|
default => $this->parser->inTextResolve(),
|
|
@@ -76,11 +75,40 @@ final class TemplateParserHtml
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
|
|
78
|
+
private function parseTag(): ?Node
|
|
79
|
+
{
|
|
80
|
+
$stream = $this->parser->getStream();
|
|
81
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
82
|
+
if (!$stream->peek(1)?->is(Token::Slash)) {
|
|
83
|
+
return $this->parseElement();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ($this->element
|
|
87
|
+
&& $this->parser->peekTag() === $this->element->data->tag // is directly in the element
|
|
88
|
+
) {
|
|
89
|
+
$save = $stream->getIndex();
|
|
90
|
+
$this->endName = [$endText] = $this->parseEndTag();
|
|
91
|
+
if ($this->element->is($endText) || $this->element->data->textualName === $endText) {
|
|
92
|
+
return null; // go to parseElement() one level up to close the element
|
|
93
|
+
}
|
|
94
|
+
$stream->seek($save);
|
|
95
|
+
if (!in_array($endText, $this->element->data->unclosedTags ?? [], true)) {
|
|
96
|
+
return null; // go to parseElement() one level up to collapse
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if ($this->parser->strict) {
|
|
101
|
+
$stream->throwUnexpectedException(excerpt: '/');
|
|
102
|
+
}
|
|
103
|
+
return $this->parseBogusEndTag();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
79
107
|
private function parseElement(): Node
|
|
80
108
|
{
|
|
81
109
|
$res = new FragmentNode;
|
|
82
110
|
$res->append($this->extractIndentation());
|
|
83
|
-
$res->append($this->
|
|
111
|
+
$res->append($this->parseStartTag($this->element));
|
|
84
112
|
$elem = $this->element;
|
|
85
113
|
|
|
86
114
|
$stream = $this->parser->getStream();
|
|
@@ -102,16 +130,21 @@ final class TemplateParserHtml
|
|
|
102
130
|
$frag = $this->parser->parseFragment([$this, 'inTextResolve']);
|
|
103
131
|
$content->append($this->finishNAttrNodes($frag, $innerNodes));
|
|
104
132
|
|
|
105
|
-
|
|
133
|
+
[$endText, $endVariable] = $this->endName;
|
|
134
|
+
$this->endName = null;
|
|
135
|
+
if ($endText && ($this->element->is($endText) || $this->element->data->textualName === $endText)) {
|
|
106
136
|
$elem->content = $content;
|
|
107
137
|
$elem->content->append($this->extractIndentation());
|
|
108
|
-
$this->parseTag();
|
|
109
138
|
|
|
110
139
|
} elseif ($outerNodes || $innerNodes || $tagNodes
|
|
111
|
-
||
|
|
140
|
+
|| $this->parser->strict
|
|
141
|
+
|| $elem->variableName
|
|
142
|
+
|| $endVariable
|
|
143
|
+
|| $elem->isRawText()
|
|
112
144
|
) {
|
|
113
145
|
$stream->throwUnexpectedException(
|
|
114
|
-
addendum: ", expecting </{$elem->
|
|
146
|
+
addendum: ", expecting </{$elem->data->textualName}> for element started $elem->position",
|
|
147
|
+
excerpt: $endText ? "/{$endText}>" : $stream->peek(1)?->text . $stream->peek(2)?->text,
|
|
115
148
|
);
|
|
116
149
|
} else { // element collapsed to tags
|
|
117
150
|
$res->append($content);
|
|
@@ -145,50 +178,112 @@ final class TemplateParserHtml
|
|
|
145
178
|
}
|
|
146
179
|
|
|
147
180
|
|
|
148
|
-
private function
|
|
181
|
+
private function parseStartTag(&$elem = null): Html\ElementNode
|
|
149
182
|
{
|
|
150
183
|
$stream = $this->parser->getStream();
|
|
151
184
|
$openToken = $stream->consume(Token::Html_TagOpen);
|
|
152
|
-
$
|
|
185
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
186
|
+
|
|
187
|
+
[$textual, $variable] = $this->parseTagName($this->parser->strict);
|
|
188
|
+
if (($this->parser->strict || $variable)
|
|
189
|
+
&& !$stream->is(Token::Whitespace, Token::Slash, Token::Html_TagClose)
|
|
190
|
+
) {
|
|
191
|
+
throw $stream->throwUnexpectedException();
|
|
192
|
+
}
|
|
193
|
+
|
|
153
194
|
$this->parser->lastIndentation = null;
|
|
154
|
-
$this->parser->
|
|
195
|
+
$this->parser->inHead = false;
|
|
155
196
|
$elem = new Html\ElementNode(
|
|
156
|
-
name: $
|
|
197
|
+
name: $variable ? '' : $textual,
|
|
157
198
|
position: $openToken->position,
|
|
158
199
|
parent: $this->element,
|
|
159
200
|
data: (object) ['tag' => $this->parser->peekTag()],
|
|
201
|
+
contentType: $this->parser->getContentType(),
|
|
160
202
|
);
|
|
161
203
|
$elem->attributes = $this->parser->parseFragment([$this, 'inTagResolve']);
|
|
162
204
|
$elem->selfClosing = (bool) $stream->tryConsume(Token::Slash);
|
|
205
|
+
$elem->variableName = $variable;
|
|
206
|
+
$elem->data->textualName = $textual;
|
|
163
207
|
$stream->consume(Token::Html_TagClose);
|
|
164
|
-
$
|
|
208
|
+
$state = !$elem->selfClosing && $elem->isRawText()
|
|
209
|
+
? TemplateLexer::StateHtmlRawText
|
|
210
|
+
: TemplateLexer::StateHtmlText;
|
|
211
|
+
$this->parser->getLexer()->setState($state, $elem->name);
|
|
165
212
|
return $elem;
|
|
166
213
|
}
|
|
167
214
|
|
|
168
215
|
|
|
169
|
-
|
|
216
|
+
/** @return array{string, ?Nodes\Php\ExpressionNode} */
|
|
217
|
+
private function parseEndTag(): array
|
|
170
218
|
{
|
|
171
219
|
$stream = $this->parser->getStream();
|
|
172
|
-
$
|
|
220
|
+
$stream->consume(Token::Html_TagOpen);
|
|
221
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
222
|
+
$stream->consume(Token::Slash);
|
|
223
|
+
$name = $this->parseTagName();
|
|
224
|
+
$stream->tryConsume(Token::Whitespace);
|
|
225
|
+
$stream->consume(Token::Html_TagClose);
|
|
226
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
|
|
227
|
+
return $name;
|
|
228
|
+
}
|
|
173
229
|
|
|
174
|
-
if ($this->element
|
|
175
|
-
&& $this->parser->peekTag() === $this->element->data->tag
|
|
176
|
-
&& (strcasecmp($name, $this->element->name) === 0
|
|
177
|
-
|| !in_array($name, $this->element->data->unclosedTags ?? [], true))
|
|
178
|
-
) {
|
|
179
|
-
return null; // go back to parseElement()
|
|
180
|
-
}
|
|
181
230
|
|
|
231
|
+
/** @return array{string, ?Nodes\Php\ExpressionNode} */
|
|
232
|
+
private function parseTagName(bool $strict = true): array
|
|
233
|
+
{
|
|
234
|
+
$variable = $text = null;
|
|
235
|
+
$parts = [];
|
|
236
|
+
$stream = $this->parser->getStream();
|
|
237
|
+
do {
|
|
238
|
+
if ($stream->is(Token::Latte_TagOpen)) {
|
|
239
|
+
$save = $stream->getIndex();
|
|
240
|
+
$statement = $this->parser->parseLatteStatement([$this, 'inTagResolve']);
|
|
241
|
+
if (!$statement instanceof Latte\Essential\Nodes\PrintNode) {
|
|
242
|
+
if (!$parts || $strict) {
|
|
243
|
+
throw new CompileException('Only expression can be used as a HTML tag name.', $statement->position);
|
|
244
|
+
}
|
|
245
|
+
$stream->seek($save);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
$parts[] = $statement->expression;
|
|
249
|
+
$save -= $stream->getIndex();
|
|
250
|
+
while ($save < 0) {
|
|
251
|
+
$text .= $stream->peek($save++)->text;
|
|
252
|
+
}
|
|
253
|
+
$variable = true;
|
|
254
|
+
|
|
255
|
+
} elseif ($token = $stream->tryConsume(Token::Html_Name)) {
|
|
256
|
+
$parts[] = new Latte\Compiler\Nodes\Php\Scalar\StringNode($token->text, $token->position);
|
|
257
|
+
$text .= $token->text;
|
|
258
|
+
|
|
259
|
+
} elseif (!$parts) {
|
|
260
|
+
throw $stream->throwUnexpectedException([Token::Html_Name, Token::Latte_TagOpen]);
|
|
261
|
+
} else {
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
} while (true);
|
|
265
|
+
|
|
266
|
+
$variable = $variable
|
|
267
|
+
? Latte\Compiler\Nodes\Php\Expression\BinaryOpNode::nest('.', ...$parts)
|
|
268
|
+
: null;
|
|
269
|
+
return [$text, $variable];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
private function parseBogusEndTag(): ?Html\BogusTagNode
|
|
274
|
+
{
|
|
275
|
+
$stream = $this->parser->getStream();
|
|
182
276
|
$openToken = $stream->consume(Token::Html_TagOpen);
|
|
277
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
183
278
|
$this->parser->lastIndentation = null;
|
|
184
|
-
$this->parser->
|
|
279
|
+
$this->parser->inHead = false;
|
|
185
280
|
$node = new Html\BogusTagNode(
|
|
186
|
-
openDelimiter: $openToken->text . $stream->consume(Token::Slash)->text . $stream->
|
|
187
|
-
content: $
|
|
281
|
+
openDelimiter: $openToken->text . $stream->consume(Token::Slash)->text . $stream->consume(Token::Html_Name)->text,
|
|
282
|
+
content: new Nodes\TextNode($stream->tryConsume(Token::Whitespace)->text ?? ''),
|
|
188
283
|
endDelimiter: $stream->consume(Token::Html_TagClose)->text,
|
|
189
284
|
position: $openToken->position,
|
|
190
285
|
);
|
|
191
|
-
$this->parser->
|
|
286
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
|
|
192
287
|
return $node;
|
|
193
288
|
}
|
|
194
289
|
|
|
@@ -197,13 +292,11 @@ final class TemplateParserHtml
|
|
|
197
292
|
{
|
|
198
293
|
$stream = $this->parser->getStream();
|
|
199
294
|
$openToken = $stream->consume(Token::Html_BogusOpen);
|
|
295
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlBogus);
|
|
200
296
|
$this->parser->lastIndentation = null;
|
|
201
|
-
$this->parser->
|
|
202
|
-
$content = $this->parser->parseFragment(
|
|
203
|
-
|
|
204
|
-
default => $this->parser->inTextResolve(),
|
|
205
|
-
});
|
|
206
|
-
$this->parser->location = $this->parser::LocationText;
|
|
297
|
+
$this->parser->inHead = false;
|
|
298
|
+
$content = $this->parser->parseFragment([$this->parser, 'inTextResolve']);
|
|
299
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
|
|
207
300
|
return new Html\BogusTagNode(
|
|
208
301
|
openDelimiter: $openToken->text,
|
|
209
302
|
content: $content,
|
|
@@ -215,7 +308,7 @@ final class TemplateParserHtml
|
|
|
215
308
|
|
|
216
309
|
private function resolveVoidness(Html\ElementNode $elem): bool
|
|
217
310
|
{
|
|
218
|
-
if ($
|
|
311
|
+
if ($elem->contentType !== ContentType::Html) {
|
|
219
312
|
return $elem->selfClosing;
|
|
220
313
|
} elseif (isset(Helpers::$emptyElements[strtolower($elem->name)])) {
|
|
221
314
|
return true;
|
|
@@ -246,69 +339,56 @@ final class TemplateParserHtml
|
|
|
246
339
|
private function parseAttribute(): ?Node
|
|
247
340
|
{
|
|
248
341
|
$stream = $this->parser->getStream();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
Token::Html_Name => $this->parser->parseText(),
|
|
254
|
-
Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
|
|
255
|
-
Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
|
|
256
|
-
default => null,
|
|
257
|
-
});
|
|
258
|
-
} catch (CompileException $e) {
|
|
259
|
-
if ($followsLatte // attribute name together with the value inside the tag
|
|
260
|
-
&& $stream->peek() // it is not lexer exception
|
|
261
|
-
) {
|
|
262
|
-
$stream->seek($save);
|
|
263
|
-
return $this->parser->parseLatteStatement();
|
|
264
|
-
}
|
|
265
|
-
throw $e;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (!$name->children) {
|
|
269
|
-
return null;
|
|
270
|
-
} elseif (count($name->children) === 1 && $name->children[0] instanceof Nodes\TextNode) {
|
|
271
|
-
$name = $name->children[0];
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
$save = $stream->getIndex();
|
|
275
|
-
$this->consumeIgnored();
|
|
276
|
-
if ($stream->tryConsume(Token::Equals)) {
|
|
277
|
-
$this->consumeIgnored();
|
|
278
|
-
$value = match ($stream->peek()->type) {
|
|
279
|
-
Token::Quote => $this->parseAttributeQuote(),
|
|
280
|
-
Token::Html_Name => $this->parser->parseText(),
|
|
281
|
-
Token::Latte_TagOpen => $this->parser->parseFragment(
|
|
282
|
-
function (FragmentNode $fragment) use ($stream) {
|
|
283
|
-
if ($fragment->children) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
return match ($stream->peek()->type) {
|
|
287
|
-
Token::Quote => $this->parseAttributeQuote(),
|
|
288
|
-
Token::Html_Name => $this->parser->parseText(),
|
|
289
|
-
Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
|
|
290
|
-
Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
|
|
291
|
-
default => null,
|
|
292
|
-
};
|
|
293
|
-
},
|
|
294
|
-
),
|
|
295
|
-
default => null,
|
|
342
|
+
if ($stream->is(Token::Latte_TagOpen)) {
|
|
343
|
+
$name = $this->parser->parseLatteStatement();
|
|
344
|
+
if (!$name instanceof Latte\Essential\Nodes\PrintNode) {
|
|
345
|
+
return $name; // value like '<span {if true}attr1=val{/if}>'
|
|
296
346
|
}
|
|
297
|
-
?? $stream->throwUnexpectedException();
|
|
298
|
-
|
|
299
347
|
} else {
|
|
300
|
-
$
|
|
301
|
-
$value = null;
|
|
348
|
+
$name = $this->parser->parseText();
|
|
302
349
|
}
|
|
303
350
|
|
|
351
|
+
[$value, $quote] = $this->parseAttributeValue();
|
|
304
352
|
return new Html\AttributeNode(
|
|
305
353
|
name: $name,
|
|
306
354
|
value: $value,
|
|
355
|
+
quote: $quote,
|
|
307
356
|
position: $name->position,
|
|
308
357
|
);
|
|
309
358
|
}
|
|
310
359
|
|
|
311
360
|
|
|
361
|
+
private function parseAttributeValue(): ?array
|
|
362
|
+
{
|
|
363
|
+
$stream = $this->parser->getStream();
|
|
364
|
+
$save = $stream->getIndex();
|
|
365
|
+
$this->consumeIgnored();
|
|
366
|
+
if (!$stream->tryConsume(Token::Equals)) {
|
|
367
|
+
$stream->seek($save);
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
$this->consumeIgnored();
|
|
372
|
+
if ($quoteToken = $stream->tryConsume(Token::Quote)) {
|
|
373
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlQuotedValue, $quoteToken->text);
|
|
374
|
+
$value = $this->parser->parseFragment([$this->parser, 'inTextResolve']);
|
|
375
|
+
$stream->tryConsume(Token::Quote) || $stream->throwUnexpectedException([$quoteToken->text], addendum: ", end of HTML attribute started $quoteToken->position");
|
|
376
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
377
|
+
return [$value, $quoteToken->text];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
$value = $this->parser->parseFragment(
|
|
381
|
+
fn() => match ($stream->peek()->type) {
|
|
382
|
+
Token::Html_Name => $this->parser->parseText(),
|
|
383
|
+
Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
|
|
384
|
+
Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
|
|
385
|
+
default => null,
|
|
386
|
+
},
|
|
387
|
+
)->simplify() ?? $stream->throwUnexpectedException();
|
|
388
|
+
return [$value, null];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
312
392
|
private function parseNAttribute(): Nodes\TextNode
|
|
313
393
|
{
|
|
314
394
|
$stream = $this->parser->getStream();
|
|
@@ -326,10 +406,12 @@ final class TemplateParserHtml
|
|
|
326
406
|
$this->consumeIgnored();
|
|
327
407
|
if ($stream->tryConsume(Token::Equals)) {
|
|
328
408
|
$this->consumeIgnored();
|
|
329
|
-
if ($stream->tryConsume(Token::Quote)) {
|
|
409
|
+
if ($quoteToken = $stream->tryConsume(Token::Quote)) {
|
|
410
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlQuotedNAttrValue, $quoteToken->text);
|
|
330
411
|
$valueToken = $stream->tryConsume(Token::Text);
|
|
331
412
|
$pos = $stream->peek()->position;
|
|
332
|
-
$stream->
|
|
413
|
+
$stream->tryConsume(Token::Quote) || $stream->throwUnexpectedException([$quoteToken->text], addendum: ", end of n:attribute started $quoteToken->position");
|
|
414
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
333
415
|
} else {
|
|
334
416
|
$valueToken = $stream->consume(Token::Html_Name);
|
|
335
417
|
}
|
|
@@ -346,28 +428,10 @@ final class TemplateParserHtml
|
|
|
346
428
|
tokens: $tokens,
|
|
347
429
|
position: $nameToken->position,
|
|
348
430
|
prefix: $this->getPrefix($name),
|
|
349
|
-
|
|
431
|
+
inTag: true,
|
|
350
432
|
htmlElement: $this->element,
|
|
351
|
-
|
|
352
|
-
);
|
|
353
|
-
return $node;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
private function parseAttributeQuote(): Html\QuotedValue
|
|
358
|
-
{
|
|
359
|
-
$stream = $this->parser->getStream();
|
|
360
|
-
$quoteToken = $stream->consume(Token::Quote);
|
|
361
|
-
$value = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
|
|
362
|
-
Token::Quote => null,
|
|
363
|
-
default => $this->parser->inTextResolve(),
|
|
364
|
-
});
|
|
365
|
-
$node = new Html\QuotedValue(
|
|
366
|
-
value: $value,
|
|
367
|
-
quote: $quoteToken->text,
|
|
368
|
-
position: $quoteToken->position,
|
|
433
|
+
nAttributeNode: $node = new Nodes\TextNode(''),
|
|
369
434
|
);
|
|
370
|
-
$stream->consume(Token::Quote);
|
|
371
435
|
return $node;
|
|
372
436
|
}
|
|
373
437
|
|
|
@@ -375,17 +439,16 @@ final class TemplateParserHtml
|
|
|
375
439
|
private function parseComment(): Html\CommentNode
|
|
376
440
|
{
|
|
377
441
|
$this->parser->lastIndentation = null;
|
|
378
|
-
$this->parser->
|
|
442
|
+
$this->parser->inHead = false;
|
|
443
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlComment);
|
|
379
444
|
$stream = $this->parser->getStream();
|
|
445
|
+
$openToken = $stream->consume(Token::Html_CommentOpen);
|
|
380
446
|
$node = new Html\CommentNode(
|
|
381
|
-
position: $
|
|
382
|
-
content: $this->parser->parseFragment(
|
|
383
|
-
Token::Html_CommentClose => null,
|
|
384
|
-
default => $this->parser->inTextResolve(),
|
|
385
|
-
}),
|
|
447
|
+
position: $openToken->position,
|
|
448
|
+
content: $this->parser->parseFragment([$this->parser, 'inTextResolve']),
|
|
386
449
|
);
|
|
387
|
-
$stream->
|
|
388
|
-
$this->parser->
|
|
450
|
+
$stream->tryConsume(Token::Html_CommentClose) || $stream->throwUnexpectedException([Token::Html_CommentClose], addendum: " started $openToken->position");
|
|
451
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
|
|
389
452
|
return $node;
|
|
390
453
|
}
|
|
391
454
|
|
|
@@ -398,8 +461,10 @@ final class TemplateParserHtml
|
|
|
398
461
|
continue;
|
|
399
462
|
}
|
|
400
463
|
if ($stream->tryConsume(Token::Latte_CommentOpen)) {
|
|
464
|
+
$this->parser->getLexer()->pushState(TemplateLexer::StateLatteComment);
|
|
401
465
|
$stream->consume(Token::Text);
|
|
402
466
|
$stream->consume(Token::Latte_CommentClose);
|
|
467
|
+
$this->parser->getLexer()->popState();
|
|
403
468
|
$stream->tryConsume(Token::Newline);
|
|
404
469
|
continue;
|
|
405
470
|
}
|
|
@@ -408,15 +473,6 @@ final class TemplateParserHtml
|
|
|
408
473
|
}
|
|
409
474
|
|
|
410
475
|
|
|
411
|
-
private function isClosingTag(string $name): bool
|
|
412
|
-
{
|
|
413
|
-
$stream = $this->parser->getStream();
|
|
414
|
-
return $stream->is(Token::Html_TagOpen)
|
|
415
|
-
&& $stream->peek(1)->is(Token::Slash)
|
|
416
|
-
&& strcasecmp($name, $stream->peek(2)->text ?? '') === 0;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
476
|
private function prepareNAttrs(array $attrs, bool $void): array
|
|
421
477
|
{
|
|
422
478
|
$res = [];
|
|
@@ -454,7 +510,7 @@ final class TemplateParserHtml
|
|
|
454
510
|
if ($res instanceof \Generator && $res->valid()) {
|
|
455
511
|
$toClose[] = [$res, $tag];
|
|
456
512
|
|
|
457
|
-
} elseif ($res instanceof
|
|
513
|
+
} elseif ($res instanceof AreaNode) {
|
|
458
514
|
$this->parser->ensureIsConsumed($tag);
|
|
459
515
|
$res->position = $tag->position;
|
|
460
516
|
$tag->replaceNAttribute($res);
|
|
@@ -9,13 +9,9 @@ declare(strict_types=1);
|
|
|
9
9
|
|
|
10
10
|
namespace Latte\Compiler;
|
|
11
11
|
|
|
12
|
-
use Latte;
|
|
13
|
-
|
|
14
12
|
|
|
15
13
|
final class Token
|
|
16
14
|
{
|
|
17
|
-
use Latte\Strict;
|
|
18
|
-
|
|
19
15
|
public const
|
|
20
16
|
End = 0,
|
|
21
17
|
Text = 10000,
|
|
@@ -124,7 +120,7 @@ final class Token
|
|
|
124
120
|
Php_True = 336,
|
|
125
121
|
Php_False = 337;
|
|
126
122
|
|
|
127
|
-
public const
|
|
123
|
+
public const Names = [
|
|
128
124
|
self::End => '[EOF]',
|
|
129
125
|
self::Text => 'text',
|
|
130
126
|
self::Whitespace => 'whitespace',
|
|
@@ -134,11 +130,11 @@ final class Token
|
|
|
134
130
|
self::Equals => "'='",
|
|
135
131
|
self::Quote => 'quote',
|
|
136
132
|
|
|
137
|
-
self::Latte_TagOpen =>
|
|
138
|
-
self::Latte_TagClose =>
|
|
133
|
+
self::Latte_TagOpen => 'Latte tag',
|
|
134
|
+
self::Latte_TagClose => 'end of Latte tag',
|
|
139
135
|
self::Latte_Name => 'tag name',
|
|
140
|
-
self::Latte_CommentOpen =>
|
|
141
|
-
self::Latte_CommentClose =>
|
|
136
|
+
self::Latte_CommentOpen => 'Latte comment',
|
|
137
|
+
self::Latte_CommentClose => 'end of Latte comment',
|
|
142
138
|
|
|
143
139
|
self::Html_TagOpen => 'HTML tag',
|
|
144
140
|
self::Html_TagClose => 'end of HTML tag',
|
|
@@ -9,7 +9,6 @@ declare(strict_types=1);
|
|
|
9
9
|
|
|
10
10
|
namespace Latte\Compiler;
|
|
11
11
|
|
|
12
|
-
use Latte;
|
|
13
12
|
use Latte\CompileException;
|
|
14
13
|
|
|
15
14
|
|
|
@@ -19,8 +18,6 @@ use Latte\CompileException;
|
|
|
19
18
|
*/
|
|
20
19
|
final class TokenStream
|
|
21
20
|
{
|
|
22
|
-
use Latte\Strict;
|
|
23
|
-
|
|
24
21
|
/** @var Token[] */
|
|
25
22
|
private array $tokens = [];
|
|
26
23
|
private \Iterator /*readonly*/ $source;
|
|
@@ -70,7 +67,7 @@ final class TokenStream
|
|
|
70
67
|
{
|
|
71
68
|
$token = $this->peek();
|
|
72
69
|
if ($kind && !$token->is(...$kind)) {
|
|
73
|
-
$kind = array_map(fn($item) => is_string($item) ? "'$item'" : Token::
|
|
70
|
+
$kind = array_map(fn($item) => is_string($item) ? "'$item'" : Token::Names[$item], $kind);
|
|
74
71
|
$this->throwUnexpectedException($kind);
|
|
75
72
|
} elseif (!$token->isEnd()) {
|
|
76
73
|
$this->index++;
|
|
@@ -119,28 +116,15 @@ final class TokenStream
|
|
|
119
116
|
* @throws CompileException
|
|
120
117
|
* @return never
|
|
121
118
|
*/
|
|
122
|
-
public function throwUnexpectedException(array $expected = [], string $addendum = ''): void
|
|
119
|
+
public function throwUnexpectedException(array $expected = [], string $addendum = '', string $excerpt = ''): void
|
|
123
120
|
{
|
|
124
|
-
$
|
|
125
|
-
$
|
|
126
|
-
do {
|
|
127
|
-
$token = $this->peek($i++);
|
|
128
|
-
if ($token->isEnd()) {
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
$s .= $token->text;
|
|
132
|
-
if (strlen($s) > 5) {
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
} while (true);
|
|
136
|
-
|
|
137
|
-
$expected = array_map(fn($item) => is_int($item) ? Token::NAMES[$item] : $item, $expected);
|
|
138
|
-
|
|
121
|
+
$token = $this->peek()->text . $excerpt;
|
|
122
|
+
$expected = array_map(fn($item) => is_int($item) ? Token::Names[$item] : $item, $expected);
|
|
139
123
|
throw new CompileException(
|
|
140
124
|
'Unexpected '
|
|
141
|
-
. ($
|
|
125
|
+
. ($token === ''
|
|
142
126
|
? 'end'
|
|
143
|
-
: "'" . trim($
|
|
127
|
+
: "'" . trim($token, "\n") . "'")
|
|
144
128
|
. ($expected && count($expected) < 5
|
|
145
129
|
? ', expecting ' . implode(', ', $expected)
|
|
146
130
|
: '')
|
|
@@ -17,10 +17,13 @@ use Latte\Compiler\Nodes\TemplateNode;
|
|
|
17
17
|
*/
|
|
18
18
|
class Engine
|
|
19
19
|
{
|
|
20
|
-
|
|
20
|
+
public const Version = '3.0.12';
|
|
21
|
+
public const VersionId = 30012;
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
public const
|
|
23
|
+
/** @deprecated use Engine::Version */
|
|
24
|
+
public const
|
|
25
|
+
VERSION = self::Version,
|
|
26
|
+
VERSION_ID = self::VersionId;
|
|
24
27
|
|
|
25
28
|
/** @deprecated use ContentType::* */
|
|
26
29
|
public const
|
|
@@ -42,8 +45,10 @@ class Engine
|
|
|
42
45
|
private ?string $tempDirectory = null;
|
|
43
46
|
private bool $autoRefresh = true;
|
|
44
47
|
private bool $strictTypes = false;
|
|
48
|
+
private bool $strictParsing = false;
|
|
45
49
|
private ?Policy $policy = null;
|
|
46
50
|
private bool $sandboxed = false;
|
|
51
|
+
private ?string $phpBinary = null;
|
|
47
52
|
|
|
48
53
|
|
|
49
54
|
public function __construct()
|
|
@@ -126,6 +131,10 @@ class Engine
|
|
|
126
131
|
throw $e->setSource($source, $name);
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
if ($this->phpBinary) {
|
|
135
|
+
Compiler\PhpHelpers::checkCode($this->phpBinary, $code, "(compiled $name)");
|
|
136
|
+
}
|
|
137
|
+
|
|
129
138
|
return $code;
|
|
130
139
|
}
|
|
131
140
|
|
|
@@ -135,8 +144,8 @@ class Engine
|
|
|
135
144
|
*/
|
|
136
145
|
public function parse(string $source): TemplateNode
|
|
137
146
|
{
|
|
138
|
-
$lexer = new Compiler\TemplateLexer;
|
|
139
147
|
$parser = new Compiler\TemplateParser;
|
|
148
|
+
$parser->strict = $this->strictParsing;
|
|
140
149
|
|
|
141
150
|
foreach ($this->extensions as $extension) {
|
|
142
151
|
$extension->beforeCompile($this);
|
|
@@ -146,7 +155,7 @@ class Engine
|
|
|
146
155
|
return $parser
|
|
147
156
|
->setContentType($this->contentType)
|
|
148
157
|
->setPolicy($this->getPolicy(effective: true))
|
|
149
|
-
->parse($source
|
|
158
|
+
->parse($source);
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
|
|
@@ -173,12 +182,12 @@ class Engine
|
|
|
173
182
|
*/
|
|
174
183
|
public function generate(TemplateNode $node, string $name): string
|
|
175
184
|
{
|
|
176
|
-
$
|
|
185
|
+
$sourceName = preg_match('#\n|\?#', $name) ? null : $name;
|
|
177
186
|
$generator = new Compiler\TemplateGenerator;
|
|
178
187
|
return $generator->generate(
|
|
179
188
|
$node,
|
|
180
189
|
$this->getTemplateClass($name),
|
|
181
|
-
$
|
|
190
|
+
$sourceName,
|
|
182
191
|
$this->strictTypes,
|
|
183
192
|
);
|
|
184
193
|
}
|
|
@@ -310,7 +319,7 @@ class Engine
|
|
|
310
319
|
{
|
|
311
320
|
$key = [
|
|
312
321
|
$this->getLoader()->getUniqueId($name),
|
|
313
|
-
self::
|
|
322
|
+
self::Version,
|
|
314
323
|
array_keys((array) $this->functions),
|
|
315
324
|
$this->contentType,
|
|
316
325
|
];
|
|
@@ -527,6 +536,19 @@ class Engine
|
|
|
527
536
|
}
|
|
528
537
|
|
|
529
538
|
|
|
539
|
+
public function setStrictParsing(bool $on = true): static
|
|
540
|
+
{
|
|
541
|
+
$this->strictParsing = $on;
|
|
542
|
+
return $this;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
public function isStrictParsing(): bool
|
|
547
|
+
{
|
|
548
|
+
return $this->strictParsing;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
530
552
|
public function setLoader(Loader $loader): static
|
|
531
553
|
{
|
|
532
554
|
$this->loader = $loader;
|
|
@@ -544,6 +566,13 @@ class Engine
|
|
|
544
566
|
}
|
|
545
567
|
|
|
546
568
|
|
|
569
|
+
public function enablePhpLinter(?string $phpBinary): static
|
|
570
|
+
{
|
|
571
|
+
$this->phpBinary = $phpBinary;
|
|
572
|
+
return $this;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
|
|
547
576
|
/**
|
|
548
577
|
* @param object|mixed[] $params
|
|
549
578
|
* @return mixed[]
|