@vituum/vite-plugin-latte 1.0.0-beta.1 → 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 +13 -11
- package/types/index.d.ts +2 -0
- 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
|
@@ -9,7 +9,6 @@ declare(strict_types=1);
|
|
|
9
9
|
|
|
10
10
|
namespace Latte\Compiler;
|
|
11
11
|
|
|
12
|
-
use Latte;
|
|
13
12
|
use Latte\ContentType;
|
|
14
13
|
|
|
15
14
|
|
|
@@ -18,8 +17,6 @@ use Latte\ContentType;
|
|
|
18
17
|
*/
|
|
19
18
|
final class TemplateGenerator
|
|
20
19
|
{
|
|
21
|
-
use Latte\Strict;
|
|
22
|
-
|
|
23
20
|
/** @var array<string, ?array{body: string, arguments: string, returns: string, comment: ?string}> */
|
|
24
21
|
private array $methods = [];
|
|
25
22
|
|
|
@@ -36,7 +33,7 @@ final class TemplateGenerator
|
|
|
36
33
|
public function generate(
|
|
37
34
|
Nodes\TemplateNode $node,
|
|
38
35
|
string $className,
|
|
39
|
-
?string $
|
|
36
|
+
?string $sourceName = null,
|
|
40
37
|
bool $strictMode = false,
|
|
41
38
|
): string
|
|
42
39
|
{
|
|
@@ -57,6 +54,10 @@ final class TemplateGenerator
|
|
|
57
54
|
$this->addConstant('ContentType', $node->contentType);
|
|
58
55
|
}
|
|
59
56
|
|
|
57
|
+
if ($sourceName !== null) {
|
|
58
|
+
$this->addConstant('Source', $sourceName);
|
|
59
|
+
}
|
|
60
|
+
|
|
60
61
|
$this->generateBlocks($context->blocks, $context);
|
|
61
62
|
|
|
62
63
|
$members = [];
|
|
@@ -79,7 +80,7 @@ final class TemplateGenerator
|
|
|
79
80
|
$code = "<?php\n\n"
|
|
80
81
|
. ($strictMode ? "declare(strict_types=1);\n\n" : '')
|
|
81
82
|
. "use Latte\\Runtime as LR;\n\n"
|
|
82
|
-
. ($
|
|
83
|
+
. ($sourceName === null ? '' : '/** source: ' . str_replace('*/', '* /', $sourceName) . " */\n")
|
|
83
84
|
. "final class $className extends Latte\\Runtime\\Template\n{\n"
|
|
84
85
|
. implode("\n\n", $members)
|
|
85
86
|
. "\n}\n";
|
|
@@ -9,15 +9,22 @@ declare(strict_types=1);
|
|
|
9
9
|
|
|
10
10
|
namespace Latte\Compiler;
|
|
11
11
|
|
|
12
|
-
use Latte;
|
|
13
12
|
use Latte\CompileException;
|
|
14
|
-
use Latte\ContentType;
|
|
15
|
-
use Latte\RegexpException;
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
final class TemplateLexer
|
|
19
16
|
{
|
|
20
|
-
|
|
17
|
+
public const
|
|
18
|
+
StatePlain = 'Plain',
|
|
19
|
+
StateLatteTag = 'LatteTag',
|
|
20
|
+
StateLatteComment = 'LatteComment',
|
|
21
|
+
StateHtmlText = 'HtmlText',
|
|
22
|
+
StateHtmlTag = 'HtmlTag',
|
|
23
|
+
StateHtmlQuotedValue = 'HtmlQuotedValue',
|
|
24
|
+
StateHtmlQuotedNAttrValue = 'HtmlQuotedNAttrValue',
|
|
25
|
+
StateHtmlRawText = 'HtmlRawText',
|
|
26
|
+
StateHtmlComment = 'HtmlComment',
|
|
27
|
+
StateHtmlBogus = 'HtmlBogus';
|
|
21
28
|
|
|
22
29
|
/** HTML tag name for Latte needs (actually is [a-zA-Z][^\s/>]*) */
|
|
23
30
|
public const ReTagName = '[a-zA-Z][a-zA-Z0-9:_.-]*';
|
|
@@ -27,45 +34,50 @@ final class TemplateLexer
|
|
|
27
34
|
|
|
28
35
|
/** HTML attribute name/value (\p{C} means \x00-\x1F except space) */
|
|
29
36
|
private const ReAttrName = '[^\p{C} "\'<>=`/]';
|
|
30
|
-
private const StateEnd = 'end';
|
|
31
37
|
|
|
32
38
|
public string $openDelimiter;
|
|
33
39
|
public string $closeDelimiter;
|
|
34
40
|
private TagLexer $tagLexer;
|
|
35
41
|
|
|
36
42
|
/** @var array<array{name: string, args: mixed[]}> */
|
|
37
|
-
private array $states;
|
|
43
|
+
private array $states = [];
|
|
38
44
|
private string $input;
|
|
39
45
|
private Position $position;
|
|
40
|
-
private bool $xmlMode;
|
|
41
46
|
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
public function tokenize(string $template, string $contentType = ContentType::Html): \Generator
|
|
48
|
+
public function __construct()
|
|
45
49
|
{
|
|
46
|
-
$this->position = new Position
|
|
47
|
-
$this->
|
|
48
|
-
$this->states = [];
|
|
49
|
-
$this->setContentType($contentType);
|
|
50
|
+
$this->position = new Position;
|
|
51
|
+
$this->setState(self::StatePlain);
|
|
50
52
|
$this->setSyntax(null);
|
|
51
53
|
$this->tagLexer = new TagLexer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/** @return \Generator<Token> */
|
|
58
|
+
public function tokenize(string $template): \Generator
|
|
59
|
+
{
|
|
60
|
+
$this->input = $this->normalize($template);
|
|
52
61
|
|
|
53
62
|
do {
|
|
63
|
+
$offset = $this->position->offset;
|
|
54
64
|
$state = $this->states[0];
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
$tokens = $this->{"state$state[name]"}(...$state['args']);
|
|
66
|
+
yield from $tokens;
|
|
67
|
+
|
|
68
|
+
} while ($offset !== $this->position->offset);
|
|
57
69
|
|
|
58
|
-
if ($
|
|
59
|
-
throw new CompileException("Unexpected '" . substr($this->input, $
|
|
70
|
+
if ($offset < strlen($this->input)) {
|
|
71
|
+
throw new CompileException("Unexpected '" . substr($this->input, $offset, 10) . "'", $this->position);
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
yield new Token(Token::End, '', $this->position);
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
|
|
66
|
-
private function statePlain():
|
|
78
|
+
private function statePlain(): array
|
|
67
79
|
{
|
|
68
|
-
|
|
80
|
+
return $this->match('~
|
|
69
81
|
(?<Text>.+?)??
|
|
70
82
|
(?<Indentation>(?<=\n|^)[ \t]+)?
|
|
71
83
|
(
|
|
@@ -74,163 +86,102 @@ final class TemplateLexer
|
|
|
74
86
|
$
|
|
75
87
|
)
|
|
76
88
|
~xsiAuD');
|
|
77
|
-
|
|
78
|
-
if (isset($m['Latte_TagOpen'])) {
|
|
79
|
-
$this->pushState('stateLatteTag');
|
|
80
|
-
} elseif (isset($m['Latte_CommentOpen'])) {
|
|
81
|
-
$this->pushState('stateLatteComment');
|
|
82
|
-
} else {
|
|
83
|
-
$this->setState(self::StateEnd);
|
|
84
|
-
}
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
|
|
88
|
-
private function stateLatteTag():
|
|
92
|
+
private function stateLatteTag(): array
|
|
89
93
|
{
|
|
90
|
-
$
|
|
91
|
-
$this->popState();
|
|
92
|
-
|
|
93
|
-
yield from $this->match('~
|
|
94
|
+
$tokens[] = $this->match('~
|
|
94
95
|
(?<Slash>/)?
|
|
95
96
|
(?<Latte_Name> = | _(?!_) | [a-z]\w*+(?:[.:-]\w+)*+(?!::|\(|\\\\))? # name, /name, but not function( or class:: or namespace\
|
|
96
97
|
~xsiAu');
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
$tokens[] = $this->tagLexer->tokenizePartially($this->input, $this->position);
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
$tokens[] = $this->match('~
|
|
101
102
|
(?<Slash>/)?
|
|
102
103
|
(?<Latte_TagClose>' . $this->closeDelimiter . ')
|
|
103
104
|
(?<Newline>[ \t]*\R)?
|
|
104
|
-
~xsiAu')
|
|
105
|
-
|
|
105
|
+
~xsiAu');
|
|
106
|
+
|
|
107
|
+
return array_merge(...$tokens);
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
|
|
109
|
-
private function stateLatteComment():
|
|
111
|
+
private function stateLatteComment(): array
|
|
110
112
|
{
|
|
111
|
-
|
|
113
|
+
return $this->match('~
|
|
112
114
|
(?<Text>.+?)??
|
|
113
|
-
(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
(
|
|
116
|
+
(?<Latte_CommentClose>\*' . $this->closeDelimiter . ')(?<Newline>[ \t]*\R{1,2})?|
|
|
117
|
+
$
|
|
118
|
+
)
|
|
119
|
+
~xsiAu');
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
|
|
121
|
-
private function stateHtmlText():
|
|
123
|
+
private function stateHtmlText(): array
|
|
122
124
|
{
|
|
123
|
-
|
|
125
|
+
return $this->match('~(?J)
|
|
124
126
|
(?<Text>.+?)??
|
|
125
127
|
(
|
|
126
|
-
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(
|
|
128
|
+
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(?=[a-z]|' . $this->openDelimiter . ')| # < </ tag
|
|
127
129
|
(?<Html_CommentOpen><!--(?!>|->))| # <!-- comment
|
|
128
|
-
(?<Html_BogusOpen><[
|
|
130
|
+
(?<Html_BogusOpen><[?!])| # <!doctype <?xml or error
|
|
129
131
|
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
130
132
|
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
131
133
|
$
|
|
132
134
|
)
|
|
133
135
|
~xsiAuD');
|
|
134
|
-
|
|
135
|
-
if (isset($m['Html_TagOpen'])) {
|
|
136
|
-
$tagName = isset($m['Slash']) ? null : strtolower($m['Html_Name']);
|
|
137
|
-
$this->setState('stateHtmlTag', $tagName);
|
|
138
|
-
} elseif (isset($m['Html_CommentOpen'])) {
|
|
139
|
-
$this->setState('stateHtmlComment');
|
|
140
|
-
} elseif (isset($m['Html_BogusOpen'])) {
|
|
141
|
-
$this->setState('stateHtmlBogus');
|
|
142
|
-
} elseif (isset($m['Latte_TagOpen'])) {
|
|
143
|
-
$this->pushState('stateLatteTag');
|
|
144
|
-
} elseif (isset($m['Latte_CommentOpen'])) {
|
|
145
|
-
$this->pushState('stateLatteComment');
|
|
146
|
-
} else {
|
|
147
|
-
$this->setState(self::StateEnd);
|
|
148
|
-
}
|
|
149
136
|
}
|
|
150
137
|
|
|
151
138
|
|
|
152
|
-
private function stateHtmlTag(
|
|
139
|
+
private function stateHtmlTag(): array
|
|
153
140
|
{
|
|
154
|
-
|
|
141
|
+
return $this->match('~(?J)
|
|
142
|
+
(?<Equals>=)
|
|
143
|
+
(?<Whitespace>\s+)?
|
|
144
|
+
(?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . '|/)+)? # HTML attribute value can contain /
|
|
145
|
+
|
|
|
155
146
|
(?<Whitespace>\s+)| # whitespace
|
|
156
|
-
(?<Equals>=)|
|
|
157
147
|
(?<Quote>["\'])|
|
|
158
148
|
(?<Slash>/)?(?<Html_TagClose>>)(?<Newline>[ \t]*\R)?| # > />
|
|
159
149
|
(?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . ')+)| # HTML attribute name/value
|
|
160
150
|
(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
161
151
|
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
|
|
162
152
|
~xsiAu');
|
|
163
|
-
|
|
164
|
-
if (isset($m['Html_Name'])) {
|
|
165
|
-
$this->states[0]['args'][1] = $m['Html_Name']; // sets $attrName
|
|
166
|
-
} elseif (isset($m['Equals'])) {
|
|
167
|
-
yield from $this->match('~
|
|
168
|
-
(?<Whitespace>\s+)? # whitespace
|
|
169
|
-
(?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . '|/)+) # HTML attribute value can contain /
|
|
170
|
-
~xsiAu');
|
|
171
|
-
} elseif (isset($m['Whitespace'])) {
|
|
172
|
-
} elseif (isset($m['Quote'])) {
|
|
173
|
-
$this->pushState(str_starts_with($attrName ?? '', self::NPrefix)
|
|
174
|
-
? 'stateHtmlQuotedNAttrValue'
|
|
175
|
-
: 'stateHtmlQuotedValue', $m['Quote']);
|
|
176
|
-
} elseif (
|
|
177
|
-
isset($m['Html_TagClose'])
|
|
178
|
-
&& !$this->xmlMode
|
|
179
|
-
&& !isset($m['Slash'])
|
|
180
|
-
&& in_array($tagName, ['script', 'style'], true)
|
|
181
|
-
) {
|
|
182
|
-
$this->setState('stateHtmlRawText', $tagName);
|
|
183
|
-
} elseif (isset($m['Html_TagClose'])) {
|
|
184
|
-
$this->setState('stateHtmlText');
|
|
185
|
-
} elseif (isset($m['Latte_TagOpen'])) {
|
|
186
|
-
$this->pushState('stateLatteTag');
|
|
187
|
-
} elseif (isset($m['Latte_CommentOpen'])) {
|
|
188
|
-
$this->pushState('stateLatteComment');
|
|
189
|
-
} else {
|
|
190
|
-
$this->setState(self::StateEnd);
|
|
191
|
-
}
|
|
192
153
|
}
|
|
193
154
|
|
|
194
155
|
|
|
195
|
-
private function stateHtmlQuotedValue(string $quote):
|
|
156
|
+
private function stateHtmlQuotedValue(string $quote): array
|
|
196
157
|
{
|
|
197
|
-
|
|
198
|
-
(?<Text>.+?)??
|
|
158
|
+
return $this->match('~
|
|
159
|
+
(?<Text>.+?)??
|
|
160
|
+
(
|
|
199
161
|
(?<Quote>' . $quote . ')|
|
|
200
162
|
(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
201
|
-
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)
|
|
163
|
+
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
164
|
+
$
|
|
202
165
|
)
|
|
203
166
|
~xsiAu');
|
|
204
|
-
|
|
205
|
-
if (isset($m['Quote'])) {
|
|
206
|
-
$this->popState();
|
|
207
|
-
} elseif (isset($m['Latte_TagOpen'])) {
|
|
208
|
-
$this->pushState('stateLatteTag');
|
|
209
|
-
} elseif (isset($m['Latte_CommentOpen'])) {
|
|
210
|
-
$this->pushState('stateLatteComment');
|
|
211
|
-
} else {
|
|
212
|
-
throw new CompileException('Unterminated HTML attribute value', $this->states[0]['pos']);
|
|
213
|
-
}
|
|
214
167
|
}
|
|
215
168
|
|
|
216
169
|
|
|
217
|
-
private function stateHtmlQuotedNAttrValue(string $quote):
|
|
170
|
+
private function stateHtmlQuotedNAttrValue(string $quote): array
|
|
218
171
|
{
|
|
219
|
-
|
|
220
|
-
(?<Text>.+?)??
|
|
172
|
+
return $this->match('~
|
|
173
|
+
(?<Text>.+?)??
|
|
174
|
+
(
|
|
175
|
+
(?<Quote>' . $quote . ')|
|
|
176
|
+
$
|
|
177
|
+
)
|
|
221
178
|
~xsiAu');
|
|
222
|
-
|
|
223
|
-
if (isset($m['Quote'])) {
|
|
224
|
-
$this->popState();
|
|
225
|
-
} else {
|
|
226
|
-
throw new CompileException('Unterminated n:attribute value', $this->states[0]['pos']);
|
|
227
|
-
}
|
|
228
179
|
}
|
|
229
180
|
|
|
230
181
|
|
|
231
|
-
private function stateHtmlRawText(string $tagName):
|
|
182
|
+
private function stateHtmlRawText(string $tagName): array
|
|
232
183
|
{
|
|
233
|
-
|
|
184
|
+
return $this->match('~
|
|
234
185
|
(?<Text>.+?)??
|
|
235
186
|
(?<Indentation>(?<=\n|^)[ \t]+)?
|
|
236
187
|
(
|
|
@@ -240,114 +191,82 @@ final class TemplateLexer
|
|
|
240
191
|
$
|
|
241
192
|
)
|
|
242
193
|
~xsiAu');
|
|
243
|
-
|
|
244
|
-
if (isset($m['Html_TagOpen'])) {
|
|
245
|
-
$this->setState('stateHtmlTag');
|
|
246
|
-
} elseif (isset($m['Latte_TagOpen'])) {
|
|
247
|
-
$this->pushState('stateLatteTag');
|
|
248
|
-
} elseif (isset($m['Latte_CommentOpen'])) {
|
|
249
|
-
$this->pushState('stateLatteComment');
|
|
250
|
-
} else {
|
|
251
|
-
$this->setState(self::StateEnd);
|
|
252
|
-
}
|
|
253
194
|
}
|
|
254
195
|
|
|
255
196
|
|
|
256
|
-
private function stateHtmlComment():
|
|
197
|
+
private function stateHtmlComment(): array
|
|
257
198
|
{
|
|
258
|
-
|
|
199
|
+
return $this->match('~(?J)
|
|
259
200
|
(?<Text>.+?)??
|
|
260
201
|
(
|
|
261
202
|
(?<Html_CommentClose>-->)| # -->
|
|
262
203
|
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
263
|
-
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)
|
|
204
|
+
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
205
|
+
$
|
|
264
206
|
)
|
|
265
207
|
~xsiAu');
|
|
266
|
-
|
|
267
|
-
if (isset($m['Html_CommentClose'])) {
|
|
268
|
-
$this->setState('stateHtmlText');
|
|
269
|
-
} elseif (isset($m['Latte_TagOpen'])) {
|
|
270
|
-
$this->pushState('stateLatteTag');
|
|
271
|
-
} elseif (isset($m['Latte_CommentOpen'])) {
|
|
272
|
-
$this->pushState('stateLatteComment');
|
|
273
|
-
} else {
|
|
274
|
-
throw new CompileException('Unterminated HTML comment', $this->states[0]['pos']);
|
|
275
|
-
}
|
|
276
208
|
}
|
|
277
209
|
|
|
278
210
|
|
|
279
|
-
private function stateHtmlBogus():
|
|
211
|
+
private function stateHtmlBogus(): array
|
|
280
212
|
{
|
|
281
|
-
|
|
282
|
-
(?<Text>.+?)??
|
|
213
|
+
return $this->match('~
|
|
214
|
+
(?<Text>.+?)??
|
|
215
|
+
(
|
|
283
216
|
(?<Html_TagClose>>)| # >
|
|
284
217
|
(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
285
|
-
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)
|
|
218
|
+
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
219
|
+
$
|
|
286
220
|
)
|
|
287
221
|
~xsiAu');
|
|
288
|
-
|
|
289
|
-
if (isset($m['Html_TagClose'])) {
|
|
290
|
-
$this->setState('stateHtmlText');
|
|
291
|
-
} elseif (isset($m['Latte_TagOpen'])) {
|
|
292
|
-
$this->pushState('stateLatteTag');
|
|
293
|
-
} elseif (isset($m['Latte_CommentOpen'])) {
|
|
294
|
-
$this->pushState('stateLatteComment');
|
|
295
|
-
} else {
|
|
296
|
-
throw new CompileException('Unterminated HTML tag', $this->states[0]['pos']);
|
|
297
|
-
}
|
|
298
222
|
}
|
|
299
223
|
|
|
300
224
|
|
|
301
225
|
/**
|
|
302
226
|
* Matches next token.
|
|
227
|
+
* @return Token[]
|
|
303
228
|
*/
|
|
304
|
-
private function match(string $re):
|
|
229
|
+
private function match(string $re): array
|
|
305
230
|
{
|
|
306
231
|
preg_match($re, $this->input, $matches, PREG_UNMATCHED_AS_NULL, $this->position->offset);
|
|
307
232
|
if (preg_last_error()) {
|
|
308
|
-
throw new
|
|
233
|
+
throw new CompileException(preg_last_error_msg());
|
|
309
234
|
}
|
|
310
235
|
|
|
236
|
+
$tokens = [];
|
|
311
237
|
foreach ($matches as $k => $v) {
|
|
312
238
|
if ($v !== null && !\is_int($k)) {
|
|
313
|
-
|
|
239
|
+
$tokens[] = new Token(\constant(Token::class . '::' . $k), $v, $this->position);
|
|
314
240
|
$this->position = $this->position->advance($v);
|
|
315
241
|
}
|
|
316
242
|
}
|
|
317
243
|
|
|
318
|
-
return $
|
|
244
|
+
return $tokens;
|
|
319
245
|
}
|
|
320
246
|
|
|
321
247
|
|
|
322
|
-
public function
|
|
248
|
+
public function setState(string $state, ...$args): void
|
|
323
249
|
{
|
|
324
|
-
|
|
325
|
-
$this->setState('stateHtmlText');
|
|
326
|
-
$this->xmlMode = $type === ContentType::Xml;
|
|
327
|
-
} else {
|
|
328
|
-
$this->setState('statePlain');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return $this;
|
|
250
|
+
$this->states[0] = ['name' => $state, 'args' => $args];
|
|
332
251
|
}
|
|
333
252
|
|
|
334
253
|
|
|
335
|
-
|
|
254
|
+
public function pushState(string $state, ...$args): void
|
|
336
255
|
{
|
|
337
|
-
$this->states
|
|
256
|
+
array_unshift($this->states, null);
|
|
257
|
+
$this->setState($state, ...$args);
|
|
338
258
|
}
|
|
339
259
|
|
|
340
260
|
|
|
341
|
-
|
|
261
|
+
public function popState(): void
|
|
342
262
|
{
|
|
343
|
-
|
|
344
|
-
$this->setState($state, ...$args);
|
|
263
|
+
array_shift($this->states);
|
|
345
264
|
}
|
|
346
265
|
|
|
347
266
|
|
|
348
|
-
|
|
267
|
+
public function getState(): string
|
|
349
268
|
{
|
|
350
|
-
|
|
269
|
+
return $this->states[0]['name'];
|
|
351
270
|
}
|
|
352
271
|
|
|
353
272
|
|
|
@@ -21,17 +21,11 @@ use Latte\SecurityViolationException;
|
|
|
21
21
|
|
|
22
22
|
final class TemplateParser
|
|
23
23
|
{
|
|
24
|
-
use Latte\Strict;
|
|
25
|
-
|
|
26
|
-
public const
|
|
27
|
-
LocationHead = 1,
|
|
28
|
-
LocationText = 2,
|
|
29
|
-
LocationTag = 3;
|
|
30
|
-
|
|
31
24
|
/** @var Block[][] */
|
|
32
25
|
public array $blocks = [[]];
|
|
33
26
|
public int $blockLayer = Template::LayerTop;
|
|
34
|
-
public
|
|
27
|
+
public bool $inHead = true;
|
|
28
|
+
public bool $strict = false;
|
|
35
29
|
public ?Nodes\TextNode $lastIndentation = null;
|
|
36
30
|
|
|
37
31
|
/** @var array<string, callable(Tag, self): (Node|\Generator|void)> */
|
|
@@ -42,27 +36,35 @@ final class TemplateParser
|
|
|
42
36
|
|
|
43
37
|
private TemplateParserHtml $html;
|
|
44
38
|
private ?TokenStream $stream = null;
|
|
45
|
-
private
|
|
39
|
+
private TemplateLexer $lexer;
|
|
46
40
|
private ?Policy $policy = null;
|
|
47
|
-
private string $contentType
|
|
41
|
+
private string $contentType;
|
|
48
42
|
private int $counter = 0;
|
|
49
43
|
private ?Tag $tag = null;
|
|
50
44
|
private $lastResolver;
|
|
45
|
+
private \WeakMap $lookFor;
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
public function __construct()
|
|
49
|
+
{
|
|
50
|
+
$this->lexer = new TemplateLexer;
|
|
51
|
+
$this->setContentType(ContentType::Html);
|
|
52
|
+
}
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
/**
|
|
54
56
|
* Parses tokens to nodes.
|
|
55
57
|
* @throws CompileException
|
|
56
58
|
*/
|
|
57
|
-
public function parse(string $template
|
|
59
|
+
public function parse(string $template): Nodes\TemplateNode
|
|
58
60
|
{
|
|
59
|
-
$this->lexer = $lexer;
|
|
60
61
|
$this->html = new TemplateParserHtml($this, $this->completeAttrParsers());
|
|
61
|
-
$this->stream = new TokenStream($lexer->tokenize($template
|
|
62
|
+
$this->stream = new TokenStream($this->lexer->tokenize($template));
|
|
63
|
+
$this->lookFor = new \WeakMap;
|
|
62
64
|
|
|
63
65
|
$headLength = 0;
|
|
64
66
|
$findLength = function (FragmentNode $fragment) use (&$headLength) {
|
|
65
|
-
if ($this->
|
|
67
|
+
if ($this->inHead && !end($fragment->children) instanceof Nodes\TextNode) {
|
|
66
68
|
$headLength = count($fragment->children);
|
|
67
69
|
}
|
|
68
70
|
};
|
|
@@ -119,9 +121,7 @@ final class TemplateParser
|
|
|
119
121
|
public function parseText(): Nodes\TextNode
|
|
120
122
|
{
|
|
121
123
|
$token = $this->stream->consume(Token::Text, Token::Html_Name);
|
|
122
|
-
|
|
123
|
-
$this->location = self::LocationText;
|
|
124
|
-
}
|
|
124
|
+
$this->inHead = $this->inHead && trim($token->text) === '';
|
|
125
125
|
$this->lastIndentation = null;
|
|
126
126
|
return new Nodes\TextNode($token->text, $token->position);
|
|
127
127
|
}
|
|
@@ -152,20 +152,25 @@ final class TemplateParser
|
|
|
152
152
|
if (str_ends_with($this->stream->peek(-1)?->text ?? "\n", "\n")) {
|
|
153
153
|
$this->lastIndentation ??= new Nodes\TextNode('');
|
|
154
154
|
}
|
|
155
|
-
$this->stream->consume(Token::Latte_CommentOpen);
|
|
155
|
+
$openToken = $this->stream->consume(Token::Latte_CommentOpen);
|
|
156
|
+
$this->lexer->pushState(TemplateLexer::StateLatteComment);
|
|
156
157
|
$this->stream->consume(Token::Text);
|
|
157
|
-
$this->stream->
|
|
158
|
+
$this->stream->tryConsume(Token::Latte_CommentClose) || $this->stream->throwUnexpectedException([Token::Latte_CommentClose], addendum: " started $openToken->position");
|
|
159
|
+
$this->lexer->popState();
|
|
158
160
|
return new Nodes\NopNode;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
|
|
162
|
-
public function parseLatteStatement(): ?Node
|
|
164
|
+
public function parseLatteStatement(?callable $resolver = null): ?Node
|
|
163
165
|
{
|
|
166
|
+
$this->lexer->pushState(TemplateLexer::StateLatteTag);
|
|
164
167
|
if ($this->stream->peek(1)->is(Token::Slash)
|
|
165
|
-
|| isset($this->tag->
|
|
168
|
+
|| (isset($this->tag, $this->lookFor[$this->tag]) && in_array($this->stream->peek(1)->text, $this->lookFor[$this->tag], true))
|
|
166
169
|
) {
|
|
170
|
+
$this->lexer->popState();
|
|
167
171
|
return null; // go back to previous parseLatteStatement()
|
|
168
172
|
}
|
|
173
|
+
$this->lexer->popState();
|
|
169
174
|
|
|
170
175
|
$token = $this->stream->peek();
|
|
171
176
|
$startTag = $this->pushTag($this->parseLatteTag());
|
|
@@ -186,8 +191,8 @@ final class TemplateParser
|
|
|
186
191
|
$res->send([new FragmentNode, $startTag]);
|
|
187
192
|
} else {
|
|
188
193
|
while ($res->valid()) {
|
|
189
|
-
$startTag
|
|
190
|
-
$content = $this->parseFragment($this->lastResolver);
|
|
194
|
+
$this->lookFor[$startTag] = $res->current() ?: null;
|
|
195
|
+
$content = $this->parseFragment($resolver ?? $this->lastResolver);
|
|
191
196
|
|
|
192
197
|
if (!$this->stream->is(Token::Latte_TagOpen)) {
|
|
193
198
|
$this->checkEndTag($startTag, null);
|
|
@@ -206,7 +211,7 @@ final class TemplateParser
|
|
|
206
211
|
$res->send([$content, $tag]);
|
|
207
212
|
$this->ensureIsConsumed($tag);
|
|
208
213
|
break;
|
|
209
|
-
} elseif (in_array($tag->name, $startTag
|
|
214
|
+
} elseif (in_array($tag->name, $this->lookFor[$startTag] ?? [], true)) {
|
|
210
215
|
$this->pushTag($tag);
|
|
211
216
|
$res->send([$content, $tag]);
|
|
212
217
|
$this->ensureIsConsumed($tag);
|
|
@@ -238,9 +243,7 @@ final class TemplateParser
|
|
|
238
243
|
throw new \LogicException("Incorrect behavior of {{$startTag->name}} parser, unexpected returned value (on line {$startTag->position->line})");
|
|
239
244
|
}
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
$this->location = self::LocationText;
|
|
243
|
-
}
|
|
246
|
+
$this->inHead = $this->inHead && $startTag->outputMode === $startTag::OutputNone;
|
|
244
247
|
|
|
245
248
|
$this->popTag();
|
|
246
249
|
|
|
@@ -256,17 +259,21 @@ final class TemplateParser
|
|
|
256
259
|
$this->lastIndentation ??= new Nodes\TextNode('');
|
|
257
260
|
}
|
|
258
261
|
|
|
262
|
+
$inTag = in_array($this->lexer->getState(), [TemplateLexer::StateHtmlTag, TemplateLexer::StateHtmlQuotedValue, TemplateLexer::StateHtmlComment, TemplateLexer::StateHtmlBogus], true);
|
|
259
263
|
$openToken = $stream->consume(Token::Latte_TagOpen);
|
|
264
|
+
$this->lexer->pushState(TemplateLexer::StateLatteTag);
|
|
260
265
|
$tag = new Tag(
|
|
261
266
|
position: $openToken->position,
|
|
262
267
|
closing: $closing = (bool) $stream->tryConsume(Token::Slash),
|
|
263
268
|
name: $stream->tryConsume(Token::Latte_Name)?->text ?? ($closing ? '' : '='),
|
|
264
269
|
tokens: $this->consumeTag(),
|
|
265
270
|
void: (bool) $stream->tryConsume(Token::Slash),
|
|
266
|
-
|
|
271
|
+
inHead: $this->inHead,
|
|
272
|
+
inTag: $inTag,
|
|
267
273
|
htmlElement: $this->html->getElement(),
|
|
268
274
|
);
|
|
269
|
-
$stream->
|
|
275
|
+
$stream->tryConsume(Token::Latte_TagClose) || $stream->throwUnexpectedException([Token::Latte_TagClose], addendum: " started $openToken->position");
|
|
276
|
+
$this->lexer->popState();
|
|
270
277
|
return $tag;
|
|
271
278
|
}
|
|
272
279
|
|
|
@@ -309,9 +316,7 @@ final class TemplateParser
|
|
|
309
316
|
$hint = ($t = Helpers::getSuggestion(array_keys($this->tagParsers), $name))
|
|
310
317
|
? ", did you mean {{$t}}?"
|
|
311
318
|
: '';
|
|
312
|
-
if ($this->
|
|
313
|
-
&& in_array($this->html->getElement()?->name, ['script', 'style'], true)
|
|
314
|
-
) {
|
|
319
|
+
if ($this->html->getElement()?->isRawText()) {
|
|
315
320
|
$hint .= ' (in JavaScript or CSS, try to put a space after bracket or use n:syntax=off)';
|
|
316
321
|
}
|
|
317
322
|
throw new CompileException("Unexpected tag {{$name}}$hint", $pos);
|
|
@@ -391,7 +396,9 @@ final class TemplateParser
|
|
|
391
396
|
public function setContentType(string $type): static
|
|
392
397
|
{
|
|
393
398
|
$this->contentType = $type;
|
|
394
|
-
$this->lexer
|
|
399
|
+
$this->lexer->setState($type === ContentType::Html || $type === ContentType::Xml
|
|
400
|
+
? TemplateLexer::StateHtmlText
|
|
401
|
+
: TemplateLexer::StatePlain);
|
|
395
402
|
return $this;
|
|
396
403
|
}
|
|
397
404
|
|