@vituum/vite-plugin-latte 1.0.0 → 1.2.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/latte/PlaceholderFunction.php +2 -2
- package/package.json +10 -11
- package/vendor/autoload.php +18 -0
- 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 +14 -8
- package/vendor/composer/autoload_namespaces.php +1 -1
- package/vendor/composer/autoload_psr4.php +1 -1
- package/vendor/composer/autoload_real.php +3 -22
- package/vendor/composer/autoload_static.php +13 -7
- 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 +113 -89
- 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/FilterNode.php +1 -1
- 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 +5 -5
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/NameNode.php +11 -21
- 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 +15 -8
- package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
- package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +5 -9
- package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
- package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +353 -326
- package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
- package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +105 -178
- package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
- package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +186 -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 +136 -95
- package/vendor/latte/latte/src/Latte/Essential/AuxiliaryIterator.php +46 -0
- package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +42 -27
- package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
- package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +81 -66
- package/vendor/latte/latte/src/Latte/Essential/Filters.php +103 -43
- 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 +8 -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 +40 -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/TemplatePrintNode.php +25 -3
- 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/VarPrintNode.php +9 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Passes.php +16 -58
- 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/Helpers.php +3 -1
- package/vendor/latte/latte/src/Latte/Loader.php +1 -0
- package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +1 -4
- 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 -33
- package/vendor/latte/latte/src/Latte/Runtime/FunctionExecutor.php +68 -0
- package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
- package/vendor/latte/latte/src/Latte/Runtime/Template.php +3 -5
- 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,116 @@ 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
|
-
$
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
) {
|
|
179
|
-
return null; // go back to parseElement()
|
|
220
|
+
$lexer = $this->parser->getLexer();
|
|
221
|
+
$stream->consume(Token::Html_TagOpen);
|
|
222
|
+
$lexer->setState(TemplateLexer::StateHtmlTag);
|
|
223
|
+
$stream->consume(Token::Slash);
|
|
224
|
+
if (isset($this->element->nAttributes['syntax'])) { // hardcoded
|
|
225
|
+
$lexer->popSyntax();
|
|
180
226
|
}
|
|
227
|
+
$name = $this->parseTagName();
|
|
228
|
+
$stream->tryConsume(Token::Whitespace);
|
|
229
|
+
$stream->consume(Token::Html_TagClose);
|
|
230
|
+
$lexer->setState(TemplateLexer::StateHtmlText);
|
|
231
|
+
return $name;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
/** @return array{string, ?Nodes\Php\ExpressionNode} */
|
|
236
|
+
private function parseTagName(bool $strict = true): array
|
|
237
|
+
{
|
|
238
|
+
$variable = $text = null;
|
|
239
|
+
$parts = [];
|
|
240
|
+
$stream = $this->parser->getStream();
|
|
241
|
+
do {
|
|
242
|
+
if ($stream->is(Token::Latte_TagOpen)) {
|
|
243
|
+
$save = $stream->getIndex();
|
|
244
|
+
$statement = $this->parser->parseLatteStatement([$this, 'inTagResolve']);
|
|
245
|
+
if (!$statement instanceof Latte\Essential\Nodes\PrintNode) {
|
|
246
|
+
if (!$parts || $strict) {
|
|
247
|
+
throw new CompileException('Only expression can be used as a HTML tag name.', $statement->position);
|
|
248
|
+
}
|
|
249
|
+
$stream->seek($save);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
$parts[] = $statement->expression;
|
|
253
|
+
$save -= $stream->getIndex();
|
|
254
|
+
while ($save < 0) {
|
|
255
|
+
$text .= $stream->peek($save++)->text;
|
|
256
|
+
}
|
|
257
|
+
$variable = true;
|
|
258
|
+
|
|
259
|
+
} elseif ($token = $stream->tryConsume(Token::Html_Name)) {
|
|
260
|
+
$parts[] = new Latte\Compiler\Nodes\Php\Scalar\StringNode($token->text, $token->position);
|
|
261
|
+
$text .= $token->text;
|
|
262
|
+
|
|
263
|
+
} elseif (!$parts) {
|
|
264
|
+
throw $stream->throwUnexpectedException([Token::Html_Name, Token::Latte_TagOpen]);
|
|
265
|
+
} else {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
} while (true);
|
|
269
|
+
|
|
270
|
+
$variable = $variable
|
|
271
|
+
? Latte\Compiler\Nodes\Php\Expression\BinaryOpNode::nest('.', ...$parts)
|
|
272
|
+
: null;
|
|
273
|
+
return [$text, $variable];
|
|
274
|
+
}
|
|
181
275
|
|
|
276
|
+
|
|
277
|
+
private function parseBogusEndTag(): ?Html\BogusTagNode
|
|
278
|
+
{
|
|
279
|
+
$stream = $this->parser->getStream();
|
|
182
280
|
$openToken = $stream->consume(Token::Html_TagOpen);
|
|
281
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
183
282
|
$this->parser->lastIndentation = null;
|
|
184
|
-
$this->parser->
|
|
283
|
+
$this->parser->inHead = false;
|
|
185
284
|
$node = new Html\BogusTagNode(
|
|
186
|
-
openDelimiter: $openToken->text . $stream->consume(Token::Slash)->text . $stream->
|
|
187
|
-
content: $
|
|
285
|
+
openDelimiter: $openToken->text . $stream->consume(Token::Slash)->text . $stream->consume(Token::Html_Name)->text,
|
|
286
|
+
content: new Nodes\TextNode($stream->tryConsume(Token::Whitespace)->text ?? ''),
|
|
188
287
|
endDelimiter: $stream->consume(Token::Html_TagClose)->text,
|
|
189
288
|
position: $openToken->position,
|
|
190
289
|
);
|
|
191
|
-
$this->parser->
|
|
290
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
|
|
192
291
|
return $node;
|
|
193
292
|
}
|
|
194
293
|
|
|
@@ -197,13 +296,11 @@ final class TemplateParserHtml
|
|
|
197
296
|
{
|
|
198
297
|
$stream = $this->parser->getStream();
|
|
199
298
|
$openToken = $stream->consume(Token::Html_BogusOpen);
|
|
299
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlBogus);
|
|
200
300
|
$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;
|
|
301
|
+
$this->parser->inHead = false;
|
|
302
|
+
$content = $this->parser->parseFragment([$this->parser, 'inTextResolve']);
|
|
303
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
|
|
207
304
|
return new Html\BogusTagNode(
|
|
208
305
|
openDelimiter: $openToken->text,
|
|
209
306
|
content: $content,
|
|
@@ -215,7 +312,7 @@ final class TemplateParserHtml
|
|
|
215
312
|
|
|
216
313
|
private function resolveVoidness(Html\ElementNode $elem): bool
|
|
217
314
|
{
|
|
218
|
-
if ($
|
|
315
|
+
if ($elem->contentType !== ContentType::Html) {
|
|
219
316
|
return $elem->selfClosing;
|
|
220
317
|
} elseif (isset(Helpers::$emptyElements[strtolower($elem->name)])) {
|
|
221
318
|
return true;
|
|
@@ -246,69 +343,56 @@ final class TemplateParserHtml
|
|
|
246
343
|
private function parseAttribute(): ?Node
|
|
247
344
|
{
|
|
248
345
|
$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();
|
|
346
|
+
if ($stream->is(Token::Latte_TagOpen)) {
|
|
347
|
+
$name = $this->parser->parseLatteStatement();
|
|
348
|
+
if (!$name instanceof Latte\Essential\Nodes\PrintNode) {
|
|
349
|
+
return $name; // value like '<span {if true}attr1=val{/if}>'
|
|
264
350
|
}
|
|
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,
|
|
296
|
-
}
|
|
297
|
-
?? $stream->throwUnexpectedException();
|
|
298
|
-
|
|
299
351
|
} else {
|
|
300
|
-
$
|
|
301
|
-
$value = null;
|
|
352
|
+
$name = $this->parser->parseText();
|
|
302
353
|
}
|
|
303
354
|
|
|
355
|
+
[$value, $quote] = $this->parseAttributeValue();
|
|
304
356
|
return new Html\AttributeNode(
|
|
305
357
|
name: $name,
|
|
306
358
|
value: $value,
|
|
359
|
+
quote: $quote,
|
|
307
360
|
position: $name->position,
|
|
308
361
|
);
|
|
309
362
|
}
|
|
310
363
|
|
|
311
364
|
|
|
365
|
+
private function parseAttributeValue(): ?array
|
|
366
|
+
{
|
|
367
|
+
$stream = $this->parser->getStream();
|
|
368
|
+
$save = $stream->getIndex();
|
|
369
|
+
$this->consumeIgnored();
|
|
370
|
+
if (!$stream->tryConsume(Token::Equals)) {
|
|
371
|
+
$stream->seek($save);
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
$this->consumeIgnored();
|
|
376
|
+
if ($quoteToken = $stream->tryConsume(Token::Quote)) {
|
|
377
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlQuotedValue, $quoteToken->text);
|
|
378
|
+
$value = $this->parser->parseFragment([$this->parser, 'inTextResolve']);
|
|
379
|
+
$stream->tryConsume(Token::Quote) || $stream->throwUnexpectedException([$quoteToken->text], addendum: ", end of HTML attribute started $quoteToken->position");
|
|
380
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
381
|
+
return [$value, $quoteToken->text];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
$value = $this->parser->parseFragment(
|
|
385
|
+
fn() => match ($stream->peek()->type) {
|
|
386
|
+
Token::Html_Name => $this->parser->parseText(),
|
|
387
|
+
Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
|
|
388
|
+
Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
|
|
389
|
+
default => null,
|
|
390
|
+
},
|
|
391
|
+
)->simplify() ?? $stream->throwUnexpectedException();
|
|
392
|
+
return [$value, null];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
312
396
|
private function parseNAttribute(): Nodes\TextNode
|
|
313
397
|
{
|
|
314
398
|
$stream = $this->parser->getStream();
|
|
@@ -326,10 +410,12 @@ final class TemplateParserHtml
|
|
|
326
410
|
$this->consumeIgnored();
|
|
327
411
|
if ($stream->tryConsume(Token::Equals)) {
|
|
328
412
|
$this->consumeIgnored();
|
|
329
|
-
if ($stream->tryConsume(Token::Quote)) {
|
|
413
|
+
if ($quoteToken = $stream->tryConsume(Token::Quote)) {
|
|
414
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlQuotedNAttrValue, $quoteToken->text);
|
|
330
415
|
$valueToken = $stream->tryConsume(Token::Text);
|
|
331
416
|
$pos = $stream->peek()->position;
|
|
332
|
-
$stream->
|
|
417
|
+
$stream->tryConsume(Token::Quote) || $stream->throwUnexpectedException([$quoteToken->text], addendum: ", end of n:attribute started $quoteToken->position");
|
|
418
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlTag);
|
|
333
419
|
} else {
|
|
334
420
|
$valueToken = $stream->consume(Token::Html_Name);
|
|
335
421
|
}
|
|
@@ -346,28 +432,10 @@ final class TemplateParserHtml
|
|
|
346
432
|
tokens: $tokens,
|
|
347
433
|
position: $nameToken->position,
|
|
348
434
|
prefix: $this->getPrefix($name),
|
|
349
|
-
|
|
435
|
+
inTag: true,
|
|
350
436
|
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,
|
|
437
|
+
nAttributeNode: $node = new Nodes\TextNode(''),
|
|
369
438
|
);
|
|
370
|
-
$stream->consume(Token::Quote);
|
|
371
439
|
return $node;
|
|
372
440
|
}
|
|
373
441
|
|
|
@@ -375,17 +443,16 @@ final class TemplateParserHtml
|
|
|
375
443
|
private function parseComment(): Html\CommentNode
|
|
376
444
|
{
|
|
377
445
|
$this->parser->lastIndentation = null;
|
|
378
|
-
$this->parser->
|
|
446
|
+
$this->parser->inHead = false;
|
|
447
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlComment);
|
|
379
448
|
$stream = $this->parser->getStream();
|
|
449
|
+
$openToken = $stream->consume(Token::Html_CommentOpen);
|
|
380
450
|
$node = new Html\CommentNode(
|
|
381
|
-
position: $
|
|
382
|
-
content: $this->parser->parseFragment(
|
|
383
|
-
Token::Html_CommentClose => null,
|
|
384
|
-
default => $this->parser->inTextResolve(),
|
|
385
|
-
}),
|
|
451
|
+
position: $openToken->position,
|
|
452
|
+
content: $this->parser->parseFragment([$this->parser, 'inTextResolve']),
|
|
386
453
|
);
|
|
387
|
-
$stream->
|
|
388
|
-
$this->parser->
|
|
454
|
+
$stream->tryConsume(Token::Html_CommentClose) || $stream->throwUnexpectedException([Token::Html_CommentClose], addendum: " started $openToken->position");
|
|
455
|
+
$this->parser->getLexer()->setState(TemplateLexer::StateHtmlText);
|
|
389
456
|
return $node;
|
|
390
457
|
}
|
|
391
458
|
|
|
@@ -398,8 +465,10 @@ final class TemplateParserHtml
|
|
|
398
465
|
continue;
|
|
399
466
|
}
|
|
400
467
|
if ($stream->tryConsume(Token::Latte_CommentOpen)) {
|
|
468
|
+
$this->parser->getLexer()->pushState(TemplateLexer::StateLatteComment);
|
|
401
469
|
$stream->consume(Token::Text);
|
|
402
470
|
$stream->consume(Token::Latte_CommentClose);
|
|
471
|
+
$this->parser->getLexer()->popState();
|
|
403
472
|
$stream->tryConsume(Token::Newline);
|
|
404
473
|
continue;
|
|
405
474
|
}
|
|
@@ -408,15 +477,6 @@ final class TemplateParserHtml
|
|
|
408
477
|
}
|
|
409
478
|
|
|
410
479
|
|
|
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
480
|
private function prepareNAttrs(array $attrs, bool $void): array
|
|
421
481
|
{
|
|
422
482
|
$res = [];
|
|
@@ -454,7 +514,7 @@ final class TemplateParserHtml
|
|
|
454
514
|
if ($res instanceof \Generator && $res->valid()) {
|
|
455
515
|
$toClose[] = [$res, $tag];
|
|
456
516
|
|
|
457
|
-
} elseif ($res instanceof
|
|
517
|
+
} elseif ($res instanceof AreaNode) {
|
|
458
518
|
$this->parser->ensureIsConsumed($tag);
|
|
459
519
|
$res->position = $tag->position;
|
|
460
520
|
$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
|
: '')
|