@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
|
@@ -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 $templateName = 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 ($templateName !== null && !preg_match('#\n|\?#', $templateName)) {
|
|
58
|
+
$this->addConstant('Source', $templateName);
|
|
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
|
+
. ($templateName === null ? '' : '/** source: ' . str_replace('*/', '* /', $templateName) . " */\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,51 @@ 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
|
-
|
|
33
|
-
|
|
38
|
+
private string $openDelimiter = '';
|
|
39
|
+
private string $closeDelimiter = '';
|
|
40
|
+
private array $delimiters = [];
|
|
34
41
|
private TagLexer $tagLexer;
|
|
35
42
|
|
|
36
43
|
/** @var array<array{name: string, args: mixed[]}> */
|
|
37
|
-
private array $states;
|
|
44
|
+
private array $states = [];
|
|
38
45
|
private string $input;
|
|
39
46
|
private Position $position;
|
|
40
|
-
private bool $xmlMode;
|
|
41
47
|
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
public function tokenize(string $template, string $contentType = ContentType::Html): \Generator
|
|
49
|
+
public function __construct()
|
|
45
50
|
{
|
|
46
|
-
$this->position = new Position
|
|
47
|
-
$this->
|
|
48
|
-
$this->states = [];
|
|
49
|
-
$this->setContentType($contentType);
|
|
51
|
+
$this->position = new Position;
|
|
52
|
+
$this->setState(self::StatePlain);
|
|
50
53
|
$this->setSyntax(null);
|
|
51
54
|
$this->tagLexer = new TagLexer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/** @return \Generator<Token> */
|
|
59
|
+
public function tokenize(string $template): \Generator
|
|
60
|
+
{
|
|
61
|
+
$this->input = $this->normalize($template);
|
|
52
62
|
|
|
53
63
|
do {
|
|
64
|
+
$offset = $this->position->offset;
|
|
54
65
|
$state = $this->states[0];
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
$tokens = $this->{"state$state[name]"}(...$state['args']);
|
|
67
|
+
yield from $tokens;
|
|
57
68
|
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
} while ($offset !== $this->position->offset);
|
|
70
|
+
|
|
71
|
+
if ($offset < strlen($this->input)) {
|
|
72
|
+
throw new CompileException("Unexpected '" . substr($this->input, $offset, 10) . "'", $this->position);
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
yield new Token(Token::End, '', $this->position);
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
|
|
66
|
-
private function statePlain():
|
|
79
|
+
private function statePlain(): array
|
|
67
80
|
{
|
|
68
|
-
|
|
81
|
+
return $this->match('~
|
|
69
82
|
(?<Text>.+?)??
|
|
70
83
|
(?<Indentation>(?<=\n|^)[ \t]+)?
|
|
71
84
|
(
|
|
@@ -74,163 +87,102 @@ final class TemplateLexer
|
|
|
74
87
|
$
|
|
75
88
|
)
|
|
76
89
|
~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
90
|
}
|
|
86
91
|
|
|
87
92
|
|
|
88
|
-
private function stateLatteTag():
|
|
93
|
+
private function stateLatteTag(): array
|
|
89
94
|
{
|
|
90
|
-
$
|
|
91
|
-
$this->popState();
|
|
92
|
-
|
|
93
|
-
yield from $this->match('~
|
|
95
|
+
$tokens[] = $this->match('~
|
|
94
96
|
(?<Slash>/)?
|
|
95
97
|
(?<Latte_Name> = | _(?!_) | [a-z]\w*+(?:[.:-]\w+)*+(?!::|\(|\\\\))? # name, /name, but not function( or class:: or namespace\
|
|
96
98
|
~xsiAu');
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
$tokens[] = $this->tagLexer->tokenizePartially($this->input, $this->position);
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
$tokens[] = $this->match('~
|
|
101
103
|
(?<Slash>/)?
|
|
102
104
|
(?<Latte_TagClose>' . $this->closeDelimiter . ')
|
|
103
105
|
(?<Newline>[ \t]*\R)?
|
|
104
|
-
~xsiAu')
|
|
105
|
-
|
|
106
|
+
~xsiAu');
|
|
107
|
+
|
|
108
|
+
return array_merge(...$tokens);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
|
|
109
|
-
private function stateLatteComment():
|
|
112
|
+
private function stateLatteComment(): array
|
|
110
113
|
{
|
|
111
|
-
|
|
114
|
+
return $this->match('~
|
|
112
115
|
(?<Text>.+?)??
|
|
113
|
-
(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
(
|
|
117
|
+
(?<Latte_CommentClose>\*' . $this->closeDelimiter . ')(?<Newline>[ \t]*\R{1,2})?|
|
|
118
|
+
$
|
|
119
|
+
)
|
|
120
|
+
~xsiAu');
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
|
|
121
|
-
private function stateHtmlText():
|
|
124
|
+
private function stateHtmlText(): array
|
|
122
125
|
{
|
|
123
|
-
|
|
126
|
+
return $this->match('~(?J)
|
|
124
127
|
(?<Text>.+?)??
|
|
125
128
|
(
|
|
126
|
-
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(
|
|
129
|
+
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Html_TagOpen><)(?<Slash>/)?(?=[a-z]|' . $this->openDelimiter . ')| # < </ tag
|
|
127
130
|
(?<Html_CommentOpen><!--(?!>|->))| # <!-- comment
|
|
128
|
-
(?<Html_BogusOpen><[
|
|
131
|
+
(?<Html_BogusOpen><[?!])| # <!doctype <?xml or error
|
|
129
132
|
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
130
133
|
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
131
134
|
$
|
|
132
135
|
)
|
|
133
136
|
~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
137
|
}
|
|
150
138
|
|
|
151
139
|
|
|
152
|
-
private function stateHtmlTag(
|
|
140
|
+
private function stateHtmlTag(): array
|
|
153
141
|
{
|
|
154
|
-
|
|
142
|
+
return $this->match('~(?J)
|
|
143
|
+
(?<Equals>=)
|
|
144
|
+
(?<Whitespace>\s+)?
|
|
145
|
+
(?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . '|/)+)? # HTML attribute value can contain /
|
|
146
|
+
|
|
|
155
147
|
(?<Whitespace>\s+)| # whitespace
|
|
156
|
-
(?<Equals>=)|
|
|
157
148
|
(?<Quote>["\'])|
|
|
158
149
|
(?<Slash>/)?(?<Html_TagClose>>)(?<Newline>[ \t]*\R)?| # > />
|
|
159
150
|
(?<Html_Name>(?:(?!' . $this->openDelimiter . ')' . self::ReAttrName . ')+)| # HTML attribute name/value
|
|
160
151
|
(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
161
152
|
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*) # {* comment
|
|
162
153
|
~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
154
|
}
|
|
193
155
|
|
|
194
156
|
|
|
195
|
-
private function stateHtmlQuotedValue(string $quote):
|
|
157
|
+
private function stateHtmlQuotedValue(string $quote): array
|
|
196
158
|
{
|
|
197
|
-
|
|
198
|
-
(?<Text>.+?)??
|
|
159
|
+
return $this->match('~
|
|
160
|
+
(?<Text>.+?)??
|
|
161
|
+
(
|
|
199
162
|
(?<Quote>' . $quote . ')|
|
|
200
163
|
(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
201
|
-
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)
|
|
164
|
+
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
165
|
+
$
|
|
202
166
|
)
|
|
203
167
|
~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
168
|
}
|
|
215
169
|
|
|
216
170
|
|
|
217
|
-
private function stateHtmlQuotedNAttrValue(string $quote):
|
|
171
|
+
private function stateHtmlQuotedNAttrValue(string $quote): array
|
|
218
172
|
{
|
|
219
|
-
|
|
220
|
-
(?<Text>.+?)??
|
|
173
|
+
return $this->match('~
|
|
174
|
+
(?<Text>.+?)??
|
|
175
|
+
(
|
|
176
|
+
(?<Quote>' . $quote . ')|
|
|
177
|
+
$
|
|
178
|
+
)
|
|
221
179
|
~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
180
|
}
|
|
229
181
|
|
|
230
182
|
|
|
231
|
-
private function stateHtmlRawText(string $tagName):
|
|
183
|
+
private function stateHtmlRawText(string $tagName): array
|
|
232
184
|
{
|
|
233
|
-
|
|
185
|
+
return $this->match('~
|
|
234
186
|
(?<Text>.+?)??
|
|
235
187
|
(?<Indentation>(?<=\n|^)[ \t]+)?
|
|
236
188
|
(
|
|
@@ -240,114 +192,82 @@ final class TemplateLexer
|
|
|
240
192
|
$
|
|
241
193
|
)
|
|
242
194
|
~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
195
|
}
|
|
254
196
|
|
|
255
197
|
|
|
256
|
-
private function stateHtmlComment():
|
|
198
|
+
private function stateHtmlComment(): array
|
|
257
199
|
{
|
|
258
|
-
|
|
200
|
+
return $this->match('~(?J)
|
|
259
201
|
(?<Text>.+?)??
|
|
260
202
|
(
|
|
261
203
|
(?<Html_CommentClose>-->)| # -->
|
|
262
204
|
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
263
|
-
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)
|
|
205
|
+
(?<Indentation>(?<=\n|^)[ \t]+)?(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
206
|
+
$
|
|
264
207
|
)
|
|
265
208
|
~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
209
|
}
|
|
277
210
|
|
|
278
211
|
|
|
279
|
-
private function stateHtmlBogus():
|
|
212
|
+
private function stateHtmlBogus(): array
|
|
280
213
|
{
|
|
281
|
-
|
|
282
|
-
(?<Text>.+?)??
|
|
214
|
+
return $this->match('~
|
|
215
|
+
(?<Text>.+?)??
|
|
216
|
+
(
|
|
283
217
|
(?<Html_TagClose>>)| # >
|
|
284
218
|
(?<Latte_TagOpen>' . $this->openDelimiter . '(?!\*))| # {tag
|
|
285
|
-
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)
|
|
219
|
+
(?<Latte_CommentOpen>' . $this->openDelimiter . '\*)| # {* comment
|
|
220
|
+
$
|
|
286
221
|
)
|
|
287
222
|
~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
223
|
}
|
|
299
224
|
|
|
300
225
|
|
|
301
226
|
/**
|
|
302
227
|
* Matches next token.
|
|
228
|
+
* @return Token[]
|
|
303
229
|
*/
|
|
304
|
-
private function match(string $re):
|
|
230
|
+
private function match(string $re): array
|
|
305
231
|
{
|
|
306
232
|
preg_match($re, $this->input, $matches, PREG_UNMATCHED_AS_NULL, $this->position->offset);
|
|
307
233
|
if (preg_last_error()) {
|
|
308
|
-
throw new
|
|
234
|
+
throw new CompileException(preg_last_error_msg());
|
|
309
235
|
}
|
|
310
236
|
|
|
237
|
+
$tokens = [];
|
|
311
238
|
foreach ($matches as $k => $v) {
|
|
312
239
|
if ($v !== null && !\is_int($k)) {
|
|
313
|
-
|
|
240
|
+
$tokens[] = new Token(\constant(Token::class . '::' . $k), $v, $this->position);
|
|
314
241
|
$this->position = $this->position->advance($v);
|
|
315
242
|
}
|
|
316
243
|
}
|
|
317
244
|
|
|
318
|
-
return $
|
|
245
|
+
return $tokens;
|
|
319
246
|
}
|
|
320
247
|
|
|
321
248
|
|
|
322
|
-
public function
|
|
249
|
+
public function setState(string $state, ...$args): void
|
|
323
250
|
{
|
|
324
|
-
|
|
325
|
-
$this->setState('stateHtmlText');
|
|
326
|
-
$this->xmlMode = $type === ContentType::Xml;
|
|
327
|
-
} else {
|
|
328
|
-
$this->setState('statePlain');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return $this;
|
|
251
|
+
$this->states[0] = ['name' => $state, 'args' => $args];
|
|
332
252
|
}
|
|
333
253
|
|
|
334
254
|
|
|
335
|
-
|
|
255
|
+
public function pushState(string $state, ...$args): void
|
|
336
256
|
{
|
|
337
|
-
$this->states
|
|
257
|
+
array_unshift($this->states, null);
|
|
258
|
+
$this->setState($state, ...$args);
|
|
338
259
|
}
|
|
339
260
|
|
|
340
261
|
|
|
341
|
-
|
|
262
|
+
public function popState(): void
|
|
342
263
|
{
|
|
343
|
-
|
|
344
|
-
$this->setState($state, ...$args);
|
|
264
|
+
array_shift($this->states);
|
|
345
265
|
}
|
|
346
266
|
|
|
347
267
|
|
|
348
|
-
|
|
268
|
+
public function getState(): string
|
|
349
269
|
{
|
|
350
|
-
|
|
270
|
+
return $this->states[0]['name'];
|
|
351
271
|
}
|
|
352
272
|
|
|
353
273
|
|
|
@@ -359,6 +279,7 @@ final class TemplateLexer
|
|
|
359
279
|
$left = '\{(?![\s\'"{}])';
|
|
360
280
|
$end = $endTag ? '\{/' . preg_quote($endTag, '~') . '\}' : null;
|
|
361
281
|
|
|
282
|
+
$this->delimiters[] = [$this->openDelimiter, $this->closeDelimiter];
|
|
362
283
|
[$this->openDelimiter, $this->closeDelimiter] = match ($type) {
|
|
363
284
|
null => [$left, '\}'], // {...}
|
|
364
285
|
'off' => [$endTag ? '(?=' . $end . ')\{' : '(?!x)x', '\}'],
|
|
@@ -371,6 +292,12 @@ final class TemplateLexer
|
|
|
371
292
|
}
|
|
372
293
|
|
|
373
294
|
|
|
295
|
+
public function popSyntax(): void
|
|
296
|
+
{
|
|
297
|
+
[$this->openDelimiter, $this->closeDelimiter] = array_pop($this->delimiters);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
374
301
|
private function normalize(string $str): string
|
|
375
302
|
{
|
|
376
303
|
if (str_starts_with($str, "\u{FEFF}")) { // BOM
|
|
@@ -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
|
|