@vituum/vite-plugin-latte 0.1.8

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.
Files changed (201) hide show
  1. package/README.md +39 -0
  2. package/handler.js +9 -0
  3. package/index.js +138 -0
  4. package/index.php +173 -0
  5. package/latte/AssetFilter.php +11 -0
  6. package/latte/FetchFunction.php +28 -0
  7. package/latte/IconFilter.php +14 -0
  8. package/latte/JsonTag.php +64 -0
  9. package/latte/PlaceholderFunction.php +11 -0
  10. package/latte/RandomColorFunction.php +14 -0
  11. package/latte/TelFilter.php +22 -0
  12. package/package.json +35 -0
  13. package/vendor/autoload.php +7 -0
  14. package/vendor/bin/latte-lint +107 -0
  15. package/vendor/composer/ClassLoader.php +572 -0
  16. package/vendor/composer/InstalledVersions.php +350 -0
  17. package/vendor/composer/LICENSE +21 -0
  18. package/vendor/composer/autoload_classmap.php +183 -0
  19. package/vendor/composer/autoload_namespaces.php +9 -0
  20. package/vendor/composer/autoload_psr4.php +9 -0
  21. package/vendor/composer/autoload_real.php +57 -0
  22. package/vendor/composer/autoload_static.php +193 -0
  23. package/vendor/composer/installed.json +93 -0
  24. package/vendor/composer/installed.php +32 -0
  25. package/vendor/composer/platform_check.php +26 -0
  26. package/vendor/latte/latte/bin/latte-lint +29 -0
  27. package/vendor/latte/latte/composer.json +53 -0
  28. package/vendor/latte/latte/license.md +60 -0
  29. package/vendor/latte/latte/ncs.php +14 -0
  30. package/vendor/latte/latte/ncs.xml +9 -0
  31. package/vendor/latte/latte/readme.md +21 -0
  32. package/vendor/latte/latte/src/Bridges/Tracy/BlueScreenPanel.php +114 -0
  33. package/vendor/latte/latte/src/Bridges/Tracy/LattePanel.php +99 -0
  34. package/vendor/latte/latte/src/Bridges/Tracy/templates/LattePanel.panel.phtml +98 -0
  35. package/vendor/latte/latte/src/Bridges/Tracy/templates/LattePanel.tab.phtml +15 -0
  36. package/vendor/latte/latte/src/Latte/Compiler/Block.php +44 -0
  37. package/vendor/latte/latte/src/Latte/Compiler/Escaper.php +244 -0
  38. package/vendor/latte/latte/src/Latte/Compiler/ExpressionBuilder.php +128 -0
  39. package/vendor/latte/latte/src/Latte/Compiler/Node.php +28 -0
  40. package/vendor/latte/latte/src/Latte/Compiler/NodeHelpers.php +132 -0
  41. package/vendor/latte/latte/src/Latte/Compiler/NodeTraverser.php +77 -0
  42. package/vendor/latte/latte/src/Latte/Compiler/Nodes/AreaNode.php +17 -0
  43. package/vendor/latte/latte/src/Latte/Compiler/Nodes/AuxiliaryNode.php +27 -0
  44. package/vendor/latte/latte/src/Latte/Compiler/Nodes/FragmentNode.php +59 -0
  45. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/AttributeNode.php +48 -0
  46. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/BogusTagNode.php +46 -0
  47. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/CommentNode.php +39 -0
  48. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/ElementNode.php +129 -0
  49. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +53 -0
  50. package/vendor/latte/latte/src/Latte/Compiler/Nodes/NopNode.php +21 -0
  51. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArgumentNode.php +45 -0
  52. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ComplexTypeNode.php +17 -0
  53. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayAccessNode.php +41 -0
  54. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayItemNode.php +52 -0
  55. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayNode.php +94 -0
  56. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignNode.php +39 -0
  57. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignOpNode.php +45 -0
  58. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/BinaryOpNode.php +65 -0
  59. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/CastNode.php +43 -0
  60. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClassConstantFetchNode.php +42 -0
  61. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/CloneNode.php +36 -0
  62. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClosureNode.php +73 -0
  63. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClosureUseNode.php +37 -0
  64. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ConstantFetchNode.php +37 -0
  65. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/EmptyNode.php +36 -0
  66. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ErrorSuppressNode.php +36 -0
  67. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/FilterCallNode.php +39 -0
  68. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/FunctionCallNode.php +45 -0
  69. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/FunctionCallableNode.php +39 -0
  70. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/InRangeNode.php +42 -0
  71. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/InstanceofNode.php +40 -0
  72. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/IssetNode.php +40 -0
  73. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/MatchNode.php +49 -0
  74. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/MethodCallNode.php +50 -0
  75. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/MethodCallableNode.php +42 -0
  76. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/NewNode.php +45 -0
  77. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/NotNode.php +36 -0
  78. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PostOpNode.php +43 -0
  79. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PreOpNode.php +43 -0
  80. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PropertyFetchNode.php +42 -0
  81. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/StaticCallNode.php +55 -0
  82. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/StaticCallableNode.php +47 -0
  83. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/StaticPropertyFetchNode.php +42 -0
  84. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/TernaryNode.php +50 -0
  85. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/UnaryOpNode.php +45 -0
  86. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/VariableNode.php +40 -0
  87. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ExpressionNode.php +17 -0
  88. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/FilterNode.php +67 -0
  89. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/IdentifierNode.php +36 -0
  90. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/IntersectionTypeNode.php +39 -0
  91. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/MatchArmNode.php +45 -0
  92. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +107 -0
  93. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/NameNode.php +89 -0
  94. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/NullableTypeNode.php +35 -0
  95. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ParameterNode.php +54 -0
  96. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/BooleanNode.php +30 -0
  97. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/EncapsedStringNode.php +80 -0
  98. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/EncapsedStringPartNode.php +30 -0
  99. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/FloatNode.php +63 -0
  100. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/IntegerNode.php +70 -0
  101. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/NullNode.php +29 -0
  102. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/StringNode.php +40 -0
  103. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ScalarNode.php +15 -0
  104. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/SuperiorTypeNode.php +29 -0
  105. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/UnionTypeNode.php +39 -0
  106. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/VarLikeIdentifierNode.php +21 -0
  107. package/vendor/latte/latte/src/Latte/Compiler/Nodes/StatementNode.php +15 -0
  108. package/vendor/latte/latte/src/Latte/Compiler/Nodes/TemplateNode.php +34 -0
  109. package/vendor/latte/latte/src/Latte/Compiler/Nodes/TextNode.php +37 -0
  110. package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +227 -0
  111. package/vendor/latte/latte/src/Latte/Compiler/Position.php +43 -0
  112. package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +334 -0
  113. package/vendor/latte/latte/src/Latte/Compiler/Tag.php +117 -0
  114. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +399 -0
  115. package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +388 -0
  116. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +580 -0
  117. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +206 -0
  118. package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +391 -0
  119. package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +448 -0
  120. package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +506 -0
  121. package/vendor/latte/latte/src/Latte/Compiler/Token.php +259 -0
  122. package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +151 -0
  123. package/vendor/latte/latte/src/Latte/ContentType.php +22 -0
  124. package/vendor/latte/latte/src/Latte/Engine.php +591 -0
  125. package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +160 -0
  126. package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +232 -0
  127. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +232 -0
  128. package/vendor/latte/latte/src/Latte/Essential/Filters.php +591 -0
  129. package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +161 -0
  130. package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +98 -0
  131. package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +74 -0
  132. package/vendor/latte/latte/src/Latte/Essential/Nodes/DebugbreakNode.php +53 -0
  133. package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +134 -0
  134. package/vendor/latte/latte/src/Latte/Essential/Nodes/DoNode.php +49 -0
  135. package/vendor/latte/latte/src/Latte/Essential/Nodes/DumpNode.php +58 -0
  136. package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +125 -0
  137. package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +60 -0
  138. package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +81 -0
  139. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +91 -0
  140. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +155 -0
  141. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +141 -0
  142. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +82 -0
  143. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +180 -0
  144. package/vendor/latte/latte/src/Latte/Essential/Nodes/ImportNode.php +49 -0
  145. package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +140 -0
  146. package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeFileNode.php +81 -0
  147. package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +94 -0
  148. package/vendor/latte/latte/src/Latte/Essential/Nodes/NAttrNode.php +105 -0
  149. package/vendor/latte/latte/src/Latte/Essential/Nodes/NClassNode.php +54 -0
  150. package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +79 -0
  151. package/vendor/latte/latte/src/Latte/Essential/Nodes/ParametersNode.php +84 -0
  152. package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +70 -0
  153. package/vendor/latte/latte/src/Latte/Essential/Nodes/RawPhpNode.php +46 -0
  154. package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +37 -0
  155. package/vendor/latte/latte/src/Latte/Essential/Nodes/SkipNode.php +71 -0
  156. package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +62 -0
  157. package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +118 -0
  158. package/vendor/latte/latte/src/Latte/Essential/Nodes/TemplatePrintNode.php +39 -0
  159. package/vendor/latte/latte/src/Latte/Essential/Nodes/TemplateTypeNode.php +38 -0
  160. package/vendor/latte/latte/src/Latte/Essential/Nodes/TraceNode.php +35 -0
  161. package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +104 -0
  162. package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +75 -0
  163. package/vendor/latte/latte/src/Latte/Essential/Nodes/VarNode.php +106 -0
  164. package/vendor/latte/latte/src/Latte/Essential/Nodes/VarPrintNode.php +40 -0
  165. package/vendor/latte/latte/src/Latte/Essential/Nodes/VarTypeNode.php +36 -0
  166. package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +81 -0
  167. package/vendor/latte/latte/src/Latte/Essential/Passes.php +115 -0
  168. package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +28 -0
  169. package/vendor/latte/latte/src/Latte/Essential/RollbackException.php +16 -0
  170. package/vendor/latte/latte/src/Latte/Essential/Tracer.php +117 -0
  171. package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +101 -0
  172. package/vendor/latte/latte/src/Latte/Extension.php +97 -0
  173. package/vendor/latte/latte/src/Latte/Helpers.php +96 -0
  174. package/vendor/latte/latte/src/Latte/Loader.php +37 -0
  175. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +95 -0
  176. package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +76 -0
  177. package/vendor/latte/latte/src/Latte/Policy.php +24 -0
  178. package/vendor/latte/latte/src/Latte/PositionAwareException.php +50 -0
  179. package/vendor/latte/latte/src/Latte/Runtime/Block.php +24 -0
  180. package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +151 -0
  181. package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +39 -0
  182. package/vendor/latte/latte/src/Latte/Runtime/Filters.php +234 -0
  183. package/vendor/latte/latte/src/Latte/Runtime/Html.php +35 -0
  184. package/vendor/latte/latte/src/Latte/Runtime/HtmlStringable.php +17 -0
  185. package/vendor/latte/latte/src/Latte/Runtime/Template.php +381 -0
  186. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +30 -0
  187. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallableNode.php +29 -0
  188. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +32 -0
  189. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallableNode.php +30 -0
  190. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/PropertyFetchNode.php +32 -0
  191. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +73 -0
  192. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/StaticCallNode.php +31 -0
  193. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/StaticCallableNode.php +30 -0
  194. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/StaticPropertyFetchNode.php +32 -0
  195. package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +116 -0
  196. package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +132 -0
  197. package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +185 -0
  198. package/vendor/latte/latte/src/Latte/Strict.php +101 -0
  199. package/vendor/latte/latte/src/Latte/attributes.php +24 -0
  200. package/vendor/latte/latte/src/Latte/exceptions.php +69 -0
  201. package/vendor/latte/latte/src/Tools/Linter.php +175 -0
@@ -0,0 +1,506 @@
1
+ <?php
2
+
3
+ /**
4
+ * This file is part of the Latte (https://latte.nette.org)
5
+ * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
6
+ */
7
+
8
+ declare(strict_types=1);
9
+
10
+ namespace Latte\Compiler;
11
+
12
+ use Latte;
13
+ use Latte\CompileException;
14
+ use Latte\Compiler\Nodes\AreaNode;
15
+ use Latte\Compiler\Nodes\FragmentNode;
16
+ use Latte\Compiler\Nodes\Html;
17
+ use Latte\ContentType;
18
+ use Latte\Helpers;
19
+ use Latte\SecurityViolationException;
20
+
21
+
22
+ /**
23
+ * Template parser extension for HTML.
24
+ */
25
+ final class TemplateParserHtml
26
+ {
27
+ use Latte\Strict;
28
+
29
+ /** @var array<string, callable(Tag, TemplateParser): (Node|\Generator|void)> */
30
+ private array /*readonly*/ $attrParsers;
31
+ private ?Html\ElementNode $element = null;
32
+ private TemplateParser /*readonly*/ $parser;
33
+
34
+
35
+ public function __construct(TemplateParser $parser, array $attrParsers)
36
+ {
37
+ $this->parser = $parser;
38
+ $this->attrParsers = $attrParsers;
39
+ }
40
+
41
+
42
+ public function getElement(): ?Html\ElementNode
43
+ {
44
+ return $this->element;
45
+ }
46
+
47
+
48
+ public function inTextResolve(): ?Node
49
+ {
50
+ $stream = $this->parser->getStream();
51
+ $token = $stream->peek();
52
+ return match ($token->type) {
53
+ Token::Html_TagOpen => $stream->peek(1)?->is(Token::Slash)
54
+ ? $this->parseEndTag()
55
+ : $this->parseElement(),
56
+ Token::Html_CommentOpen => $this->parseComment(),
57
+ Token::Html_BogusOpen => $this->parseBogusTag(),
58
+ default => $this->parser->inTextResolve(),
59
+ };
60
+ }
61
+
62
+
63
+ public function inTagResolve(): ?Node
64
+ {
65
+ $stream = $this->parser->getStream();
66
+ $token = $stream->peek();
67
+ return match ($token->type) {
68
+ Token::Html_Name => str_starts_with($token->text, TemplateLexer::NPrefix)
69
+ ? $this->parseNAttribute()
70
+ : $this->parseAttribute(),
71
+ Token::Latte_TagOpen => $this->parseAttribute(),
72
+ Token::Whitespace => $this->parseAttributeWhitespace(),
73
+ Token::Html_TagClose => null,
74
+ default => $this->parser->inTextResolve(),
75
+ };
76
+ }
77
+
78
+
79
+ private function parseElement(): Node
80
+ {
81
+ $res = new FragmentNode;
82
+ $res->append($this->extractIndentation());
83
+ $res->append($this->parseTag($this->element));
84
+ $elem = $this->element;
85
+
86
+ $stream = $this->parser->getStream();
87
+ $void = $this->resolveVoidness($elem);
88
+ $attrs = $this->prepareNAttrs($elem->nAttributes, $void);
89
+ $outerNodes = $this->openNAttrNodes($attrs[Tag::PrefixNone] ?? []);
90
+ $tagNodes = $this->openNAttrNodes($attrs[Tag::PrefixTag] ?? []);
91
+ $elem->tagNode = $this->finishNAttrNodes($elem->tagNode, $tagNodes);
92
+ $elem->captureTagName = (bool) $tagNodes;
93
+
94
+ if (!$void) {
95
+ $content = new FragmentNode;
96
+ if ($token = $stream->tryConsume(Token::Newline)) {
97
+ $content->append(new Nodes\TextNode($token->text, $token->position));
98
+ }
99
+
100
+ $innerNodes = $this->openNAttrNodes($attrs[Tag::PrefixInner] ?? []);
101
+ $elem->data->tag = $this->parser->peekTag();
102
+ $frag = $this->parser->parseFragment([$this, 'inTextResolve']);
103
+ $content->append($this->finishNAttrNodes($frag, $innerNodes));
104
+
105
+ if ($this->isClosingTag($elem->name)) {
106
+ $elem->content = $content;
107
+ $elem->content->append($this->extractIndentation());
108
+ $this->parseTag();
109
+
110
+ } elseif ($outerNodes || $innerNodes || $tagNodes
111
+ || ($this->parser->getContentType() === ContentType::Html && in_array(strtolower($elem->name), ['script', 'style'], true))
112
+ ) {
113
+ $stream->throwUnexpectedException(
114
+ addendum: ", expecting </{$elem->name}> for element started on line {$elem->position->line}",
115
+ );
116
+ } else { // element collapsed to tags
117
+ $res->append($content);
118
+ $this->element = $elem->parent;
119
+ if ($this->element && !$stream->is(Token::Html_TagOpen)) {
120
+ $this->element->data->unclosedTags[] = $elem->name;
121
+ }
122
+ return $res;
123
+ }
124
+ }
125
+
126
+ if ($token = $stream->tryConsume(Token::Newline)) {
127
+ $res->append(new Nodes\TextNode($token->text, $token->position));
128
+ }
129
+
130
+ $res = $this->finishNAttrNodes($res, $outerNodes);
131
+ $this->element = $elem->parent;
132
+ return $res;
133
+ }
134
+
135
+
136
+ private function extractIndentation(): AreaNode
137
+ {
138
+ if ($this->parser->lastIndentation) {
139
+ $dolly = clone $this->parser->lastIndentation;
140
+ $this->parser->lastIndentation->content = '';
141
+ return $dolly;
142
+ } else {
143
+ return new Nodes\NopNode;
144
+ }
145
+ }
146
+
147
+
148
+ private function parseTag(&$elem = null): Html\ElementNode
149
+ {
150
+ $stream = $this->parser->getStream();
151
+ $openToken = $stream->consume(Token::Html_TagOpen);
152
+ $stream->tryConsume(Token::Slash);
153
+ $this->parser->lastIndentation = null;
154
+ $this->parser->location = $this->parser::LocationTag;
155
+ $elem = new Html\ElementNode(
156
+ name: $stream->consume(Token::Html_Name)->text,
157
+ position: $openToken->position,
158
+ parent: $this->element,
159
+ data: (object) ['tag' => $this->parser->peekTag()],
160
+ );
161
+ $elem->attributes = $this->parser->parseFragment([$this, 'inTagResolve']);
162
+ $elem->selfClosing = (bool) $stream->tryConsume(Token::Slash);
163
+ $stream->consume(Token::Html_TagClose);
164
+ $this->parser->location = $this->parser::LocationText;
165
+ return $elem;
166
+ }
167
+
168
+
169
+ private function parseEndTag(): ?Html\BogusTagNode
170
+ {
171
+ $stream = $this->parser->getStream();
172
+ $name = $stream->peek(2)->text ?? '';
173
+
174
+ if ($this->element
175
+ && $this->parser->peekTag() === $this->element->data->tag
176
+ && (strcasecmp($name, $this->element->name) === 0
177
+ || !in_array($name, $this->element->data->unclosedTags ?? [], true))
178
+ ) {
179
+ return null; // go back to parseElement()
180
+ }
181
+
182
+ $openToken = $stream->consume(Token::Html_TagOpen);
183
+ $this->parser->lastIndentation = null;
184
+ $this->parser->location = $this->parser::LocationTag;
185
+ $node = new Html\BogusTagNode(
186
+ openDelimiter: $openToken->text . $stream->consume(Token::Slash)->text . $stream->tryConsume(Token::Text)?->text,
187
+ content: $this->parser->parseFragment([$this, 'inTagResolve']),
188
+ endDelimiter: $stream->consume(Token::Html_TagClose)->text,
189
+ position: $openToken->position,
190
+ );
191
+ $this->parser->location = $this->parser::LocationText;
192
+ return $node;
193
+ }
194
+
195
+
196
+ private function parseBogusTag(): Html\BogusTagNode
197
+ {
198
+ $stream = $this->parser->getStream();
199
+ $openToken = $stream->consume(Token::Html_BogusOpen);
200
+ $this->parser->lastIndentation = null;
201
+ $this->parser->location = $this->parser::LocationTag;
202
+ $content = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
203
+ Token::Html_TagClose => null,
204
+ default => $this->parser->inTextResolve(),
205
+ });
206
+ $this->parser->location = $this->parser::LocationText;
207
+ return new Html\BogusTagNode(
208
+ openDelimiter: $openToken->text,
209
+ content: $content,
210
+ endDelimiter: $stream->consume(Token::Html_TagClose)->text,
211
+ position: $openToken->position,
212
+ );
213
+ }
214
+
215
+
216
+ private function resolveVoidness(Html\ElementNode $elem): bool
217
+ {
218
+ if ($this->parser->getContentType() !== ContentType::Html) {
219
+ return $elem->selfClosing;
220
+ } elseif (isset(Helpers::$emptyElements[strtolower($elem->name)])) {
221
+ return true;
222
+ } elseif ($elem->selfClosing) { // auto-correct
223
+ $elem->content = new Nodes\NopNode;
224
+ $elem->selfClosing = false;
225
+ $last = end($elem->attributes->children);
226
+ if ($last instanceof Nodes\TextNode && $last->isWhitespace()) {
227
+ array_pop($elem->attributes->children);
228
+ }
229
+ return true;
230
+ }
231
+
232
+ return $elem->selfClosing;
233
+ }
234
+
235
+
236
+ private function parseAttributeWhitespace(): Node
237
+ {
238
+ $stream = $this->parser->getStream();
239
+ $token = $stream->consume(Token::Whitespace);
240
+ return $stream->is(Token::Html_Name) && str_starts_with($stream->peek()->text, TemplateLexer::NPrefix)
241
+ ? new Nodes\NopNode
242
+ : new Nodes\TextNode($token->text, $token->position);
243
+ }
244
+
245
+
246
+ private function parseAttribute(): ?Node
247
+ {
248
+ $stream = $this->parser->getStream();
249
+ $followsLatte = $stream->is(Token::Latte_TagOpen);
250
+ $save = $stream->getIndex();
251
+ try {
252
+ $name = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
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) {
260
+ $stream->seek($save);
261
+ return $this->parser->parseLatteStatement();
262
+ }
263
+ throw $e;
264
+ }
265
+
266
+ if (!$name->children) {
267
+ return null;
268
+ } elseif (count($name->children) === 1 && $name->children[0] instanceof Nodes\TextNode) {
269
+ $name = $name->children[0];
270
+ }
271
+
272
+ $save = $stream->getIndex();
273
+ $this->consumeIgnored();
274
+ if ($stream->tryConsume(Token::Equals)) {
275
+ $this->consumeIgnored();
276
+ $value = match ($stream->peek()->type) {
277
+ Token::Quote => $this->parseAttributeQuote(),
278
+ Token::Html_Name => $this->parser->parseText(),
279
+ Token::Latte_TagOpen => $this->parser->parseFragment(
280
+ function (FragmentNode $fragment) use ($stream) {
281
+ if ($fragment->children) {
282
+ return null;
283
+ }
284
+ return match ($stream->peek()->type) {
285
+ Token::Quote => $this->parseAttributeQuote(),
286
+ Token::Html_Name => $this->parser->parseText(),
287
+ Token::Latte_TagOpen => $this->parser->parseLatteStatement(),
288
+ Token::Latte_CommentOpen => $this->parser->parseLatteComment(),
289
+ default => null,
290
+ };
291
+ },
292
+ ),
293
+ default => null,
294
+ }
295
+ ?? $stream->throwUnexpectedException();
296
+
297
+ } else {
298
+ $stream->seek($save);
299
+ $value = null;
300
+ }
301
+
302
+ return new Html\AttributeNode(
303
+ name: $name,
304
+ value: $value,
305
+ position: $name->position,
306
+ );
307
+ }
308
+
309
+
310
+ private function parseNAttribute(): Nodes\TextNode
311
+ {
312
+ $stream = $this->parser->getStream();
313
+ $nameToken = $stream->consume(Token::Html_Name);
314
+ $save = $stream->getIndex();
315
+ $pos = $stream->peek()->position;
316
+ $name = substr($nameToken->text, strlen(TemplateLexer::NPrefix));
317
+ if ($this->parser->peekTag() !== $this->element->data->tag) {
318
+ throw new CompileException("Attribute n:$name must not appear inside {tags}", $nameToken->position);
319
+
320
+ } elseif (isset($this->element->nAttributes[$name])) {
321
+ throw new CompileException("Found multiple attributes n:$name.", $nameToken->position);
322
+ }
323
+
324
+ $this->consumeIgnored();
325
+ if ($stream->tryConsume(Token::Equals)) {
326
+ $this->consumeIgnored();
327
+ if ($stream->tryConsume(Token::Quote)) {
328
+ $valueToken = $stream->tryConsume(Token::Text);
329
+ $pos = $stream->peek()->position;
330
+ $stream->consume(Token::Quote);
331
+ } else {
332
+ $valueToken = $stream->consume(Token::Html_Name);
333
+ }
334
+ if ($valueToken) {
335
+ $tokens = (new TagLexer)->tokenize($valueToken->text, $valueToken->position);
336
+ }
337
+ } else {
338
+ $stream->seek($save);
339
+ }
340
+ $tokens ??= [new Token(Token::End, '', $pos)];
341
+
342
+ $this->element->nAttributes[$name] = new Tag(
343
+ name: preg_replace('~(inner-|tag-|)~', '', $name),
344
+ tokens: $tokens,
345
+ position: $nameToken->position,
346
+ prefix: match (true) {
347
+ str_starts_with($name, 'inner-') => Tag::PrefixInner,
348
+ str_starts_with($name, 'tag-') => Tag::PrefixTag,
349
+ default => Tag::PrefixNone,
350
+ },
351
+ location: $this->parser->location,
352
+ htmlElement: $this->element,
353
+ data: (object) ['node' => $node = new Nodes\TextNode('')], // TODO: better
354
+ );
355
+ return $node;
356
+ }
357
+
358
+
359
+ private function parseAttributeQuote(): Html\QuotedValue
360
+ {
361
+ $stream = $this->parser->getStream();
362
+ $quoteToken = $stream->consume(Token::Quote);
363
+ $value = $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
364
+ Token::Quote => null,
365
+ default => $this->parser->inTextResolve(),
366
+ });
367
+ $node = new Html\QuotedValue(
368
+ value: $value,
369
+ quote: $quoteToken->text,
370
+ position: $quoteToken->position,
371
+ );
372
+ $stream->consume(Token::Quote);
373
+ return $node;
374
+ }
375
+
376
+
377
+ private function parseComment(): Html\CommentNode
378
+ {
379
+ $this->parser->lastIndentation = null;
380
+ $this->parser->location = $this->parser::LocationTag;
381
+ $stream = $this->parser->getStream();
382
+ $node = new Html\CommentNode(
383
+ position: $stream->consume(Token::Html_CommentOpen)->position,
384
+ content: $this->parser->parseFragment(fn() => match ($stream->peek()->type) {
385
+ Token::Html_CommentClose => null,
386
+ default => $this->parser->inTextResolve(),
387
+ }),
388
+ );
389
+ $stream->consume(Token::Html_CommentClose);
390
+ $this->parser->location = $this->parser::LocationText;
391
+ return $node;
392
+ }
393
+
394
+
395
+ private function consumeIgnored(): void
396
+ {
397
+ $stream = $this->parser->getStream();
398
+ do {
399
+ if ($stream->tryConsume(Token::Whitespace)) {
400
+ continue;
401
+ }
402
+ if ($stream->tryConsume(Token::Latte_CommentOpen)) {
403
+ $stream->consume(Token::Text);
404
+ $stream->consume(Token::Latte_CommentClose);
405
+ $stream->tryConsume(Token::Newline);
406
+ continue;
407
+ }
408
+ return;
409
+ } while (true);
410
+ }
411
+
412
+
413
+ private function isClosingTag(string $name): bool
414
+ {
415
+ $stream = $this->parser->getStream();
416
+ return $stream->is(Token::Html_TagOpen)
417
+ && $stream->peek(1)->is(Token::Slash)
418
+ && strcasecmp($name, $stream->peek(2)->text ?? '') === 0;
419
+ }
420
+
421
+
422
+ private function prepareNAttrs(array $attrs, bool $void): array
423
+ {
424
+ $res = [];
425
+ foreach ($this->attrParsers as $name => $foo) {
426
+ if ($tag = $attrs[$name] ?? null) {
427
+ $prefix = substr($name, 0, (int) strpos($name, '-'));
428
+ if (!$prefix || !$void) {
429
+ $res[$prefix][] = $tag;
430
+ unset($attrs[$name]);
431
+ }
432
+ }
433
+ }
434
+
435
+ if ($attrs) {
436
+ $hint = Helpers::getSuggestion(array_keys($this->attrParsers), $k = key($attrs));
437
+ throw new CompileException('Unexpected attribute n:'
438
+ . ($hint ? "$k, did you mean n:$hint?" : implode(' and n:', array_keys($attrs))), $attrs[$k]->position);
439
+ }
440
+
441
+ return $res;
442
+ }
443
+
444
+
445
+ /**
446
+ * @param array<Tag> $toOpen
447
+ * @return array<array{\Generator, Tag}>
448
+ */
449
+ private function openNAttrNodes(array $toOpen): array
450
+ {
451
+ $toClose = [];
452
+ foreach ($toOpen as $tag) {
453
+ $parser = $this->getAttrParser($tag->name, $tag->position);
454
+ $this->parser->pushTag($tag);
455
+ $res = $parser($tag, $this->parser);
456
+ if ($res instanceof \Generator && $res->valid()) {
457
+ $toClose[] = [$res, $tag];
458
+
459
+ } elseif ($res instanceof Node) {
460
+ $this->parser->ensureIsConsumed($tag);
461
+ $res->position = $tag->position;
462
+ $tag->replaceNAttribute($res);
463
+ $this->parser->popTag();
464
+
465
+ } elseif (!$res) {
466
+ $this->parser->ensureIsConsumed($tag);
467
+ $this->parser->popTag();
468
+
469
+ } else {
470
+ throw new CompileException("Unexpected value returned by {$tag->getNotation()} parser.", $tag->position);
471
+ }
472
+ }
473
+
474
+ return $toClose;
475
+ }
476
+
477
+
478
+ /** @param array<array{\Generator, Tag}> $toClose */
479
+ private function finishNAttrNodes(AreaNode $node, array $toClose): AreaNode
480
+ {
481
+ while ([$gen, $tag] = array_pop($toClose)) {
482
+ $gen->send([$node, null]);
483
+ $node = $gen->getReturn();
484
+ $node->position = $tag->position;
485
+ $this->parser->popTag();
486
+ $this->parser->ensureIsConsumed($tag);
487
+ }
488
+
489
+ return $node;
490
+ }
491
+
492
+
493
+ /** @return callable(Tag, TemplateParser): (Node|\Generator|void) */
494
+ private function getAttrParser(string $name, Position $pos): callable
495
+ {
496
+ if (!isset($this->attrParsers[$name])) {
497
+ $hint = ($t = Helpers::getSuggestion(array_keys($this->attrParsers), $name))
498
+ ? ", did you mean n:$t?"
499
+ : '';
500
+ throw new CompileException("Unknown n:{$name}{$hint}", $pos);
501
+ } elseif (!$this->parser->isTagAllowed($name)) {
502
+ throw new SecurityViolationException("Attribute n:$name is not allowed", $pos);
503
+ }
504
+ return $this->attrParsers[$name];
505
+ }
506
+ }