|
8 | 8 | namespace Latte\Essential\Nodes; |
9 | 9 |
|
10 | 10 | use Latte\CompileException; |
| 11 | +use Latte\Compiler\Block; |
| 12 | +use Latte\Compiler\Nodes\AreaNode; |
11 | 13 | use Latte\Compiler\Nodes\FragmentNode; |
12 | 14 | use Latte\Compiler\Nodes\Php\Expression\ArrayNode; |
13 | 15 | use Latte\Compiler\Nodes\Php\ExpressionNode; |
| 16 | +use Latte\Compiler\Nodes\Php\ModifierNode; |
14 | 17 | use Latte\Compiler\Nodes\Php\Scalar\StringNode; |
15 | 18 | use Latte\Compiler\Nodes\StatementNode; |
16 | 19 | use Latte\Compiler\Nodes\TextNode; |
17 | 20 | use Latte\Compiler\PrintContext; |
18 | 21 | use Latte\Compiler\Tag; |
19 | 22 | use Latte\Compiler\TemplateParser; |
20 | | -use function count, preg_match; |
| 23 | +use Latte\Compiler\Token; |
| 24 | +use function array_pop, array_shift, count, end, preg_match; |
21 | 25 |
|
22 | 26 |
|
23 | 27 | /** |
@@ -50,21 +54,64 @@ public static function create(Tag $tag, TemplateParser $parser): \Generator |
50 | 54 | $node->args = $tag->parser->parseArguments(); |
51 | 55 |
|
52 | 56 | $prevIndex = $parser->blockLayer; |
53 | | - $parser->blockLayer = $node->layer = count($parser->blocks); |
| 57 | + $layer = $parser->blockLayer = $node->layer = count($parser->blocks); |
54 | 58 | $parser->blocks[$parser->blockLayer] = []; |
55 | 59 | [$node->blocks] = yield; |
56 | 60 |
|
| 61 | + // Content not wrapped in a {block} becomes the implicit {block default} block. |
| 62 | + $kept = $loose = []; |
57 | 63 | foreach ($node->blocks->children as $child) { |
58 | | - if (!$child instanceof ImportNode && !$child instanceof BlockNode && !$child instanceof TextNode) { |
59 | | - throw new CompileException('Unexpected content inside {embed} tags.', $child->position); |
| 64 | + $child instanceof ImportNode || $child instanceof BlockNode |
| 65 | + ? $kept[] = $child |
| 66 | + : $loose[] = $child; |
| 67 | + } |
| 68 | + |
| 69 | + // ignore whitespace-only text surrounding the blocks, but keep it in between |
| 70 | + while ($loose && $loose[0] instanceof TextNode && $loose[0]->isWhitespace()) { |
| 71 | + array_shift($loose); |
| 72 | + } |
| 73 | + while ($loose && ($last = end($loose)) instanceof TextNode && $last->isWhitespace()) { |
| 74 | + array_pop($loose); |
| 75 | + } |
| 76 | + |
| 77 | + if ($loose) { |
| 78 | + if (isset($parser->blocks[$layer]['default'])) { |
| 79 | + throw new CompileException( |
| 80 | + 'Cannot combine loose content with an explicit {block default} inside {embed}; both define the default block.', |
| 81 | + $loose[0]->position ?? $tag->position, |
| 82 | + ); |
60 | 83 | } |
| 84 | + |
| 85 | + $kept[] = $node->wrapInDefaultBlock($loose, $layer, $parser, $tag); |
| 86 | + $node->blocks->children = $kept; |
61 | 87 | } |
62 | 88 |
|
63 | 89 | $parser->blockLayer = $prevIndex; |
64 | 90 | return $node; |
65 | 91 | } |
66 | 92 |
|
67 | 93 |
|
| 94 | + /** |
| 95 | + * Wraps loose content into the implicit {block default}. |
| 96 | + * @param list<AreaNode> $content |
| 97 | + */ |
| 98 | + private function wrapInDefaultBlock(array $content, int|string $layer, TemplateParser $parser, Tag $tag): BlockNode |
| 99 | + { |
| 100 | + // the tag name 'block' is load-bearing: TemplateGenerator uses it to give the block |
| 101 | + // access to the caller's variables (varStack instead of $this->params) |
| 102 | + $blockTag = new Tag('block', [new Token(Token::End, '', $tag->position)], $tag->position, prefix: $tag->prefix); |
| 103 | + $block = new Block(new StringNode('default'), $layer, $blockTag); |
| 104 | + $parser->blocks[$layer]['default'] = $block; // uniqueness already verified by the caller |
| 105 | + |
| 106 | + $node = new BlockNode; |
| 107 | + $node->block = $block; |
| 108 | + $node->modifier = new ModifierNode([], position: $tag->position); |
| 109 | + $node->content = new FragmentNode($content); |
| 110 | + $node->position = $tag->position; |
| 111 | + return $node; |
| 112 | + } |
| 113 | + |
| 114 | + |
68 | 115 | public function print(PrintContext $context): string |
69 | 116 | { |
70 | 117 | $imports = ''; |
|
0 commit comments