Skip to content

Commit

Permalink
Merge pull request #15842 from mkouba/issue-8750
Browse files Browse the repository at this point in the history
Qute: simplify the syntax used in the Include section
  • Loading branch information
gsmet authored Mar 19, 2021
2 parents a19c4c9 + b0e0b05 commit 69c1551
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 88 deletions.
13 changes: 6 additions & 7 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -831,27 +831,26 @@ Template inheritance makes it possible to reuse template layouts.
<title>{#insert title}Default Title{/}</title> <1>
</head>
<body>
{#insert body}No body!{/} <2>
{#insert}No body!{/} <2>
</body>
</html>
----
<1> `insert` sections are used to specify parts that could be overridden by a template that includes the given template.
<2> An `insert` section may define the default content that is rendered if not overridden.
<2> An `insert` section may define the default content that is rendered if not overridden. If no name parameter is supplied then the main block of the relevant `{#include}` section is used.

.Template "detail"
[source,html]
----
{#include base} <1>
{#title}My Title{/title} <2>
{#body}
<div>
My body.
</div>
{/body}
<div> <3>
My body.
</div>
{/include}
----
<1> `include` section is used to specify the extended template.
<2> Nested blocks are used to specify the parts that should be overridden.
<3> The content of the main block is used for an `{#insert}` section with no name parameter specified.

NOTE: Section blocks can also define an optional end tag - `{/title}`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

public class IncludeSectionHelper implements SectionHelper {

static final String DEFAULT_NAME = "$default$";
private static final String TEMPLATE = "template";

private final Supplier<Template> templateSupplier;
Expand Down Expand Up @@ -80,9 +81,12 @@ public boolean treatUnknownSectionsAsBlocks() {
public IncludeSectionHelper initialize(SectionInitContext context) {

Map<String, SectionBlock> extendingBlocks = new HashMap<>();
if (context.getBlocks().size() > 1) {
for (SectionBlock block : context.getBlocks().subList(1, context.getBlocks().size())) {
extendingBlocks.put(block.label, block);
for (SectionBlock block : context.getBlocks()) {
String name = block.id.equals(MAIN_BLOCK_NAME) ? DEFAULT_NAME : block.label;
if (extendingBlocks.put(name, block) != null) {
throw new TemplateException(context.getBlocks().get(0).origin, String.format(
"Multiple blocks define the content for the insert section '%s' in template %s",
name, block.origin.getTemplateId()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public List<String> getDefaultAliases() {

@Override
public ParametersInfo getParameters() {
return ParametersInfo.builder().addParameter("name").build();
return ParametersInfo.builder().addParameter("name", IncludeSectionHelper.DEFAULT_NAME).build();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ class Parser implements Function<String, Expression>, ParserHelper {
private int line;
private int lineCharacter;
private final Deque<SectionNode.Builder> sectionStack;
private final Deque<SectionBlock.Builder> sectionBlockStack;
private final Deque<ParametersInfo> paramsStack;
private final Deque<Scope> scopeStack;
private int sectionBlockIdx;
Expand All @@ -86,12 +85,6 @@ public Parser(EngineImpl engine, Reader reader, String templateId, String genera
this.state = State.TEXT;
this.buffer = new StringBuilder();
this.sectionStack = new ArrayDeque<>();
this.sectionStack
.addFirst(SectionNode.builder(ROOT_HELPER_NAME, origin(0))
.setEngine(engine)
.setHelperFactory(ROOT_SECTION_HELPER_FACTORY));
this.sectionBlockStack = new ArrayDeque<>();
this.sectionBlockStack.addFirst(SectionBlock.builder(SectionHelperFactory.MAIN_BLOCK_NAME, this, this::parserError));
this.sectionBlockIdx = 0;
this.paramsStack = new ArrayDeque<>();
this.paramsStack.addFirst(ParametersInfo.EMPTY);
Expand All @@ -118,6 +111,11 @@ public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
}

Template parse() {

sectionStack.addFirst(SectionNode.builder(ROOT_HELPER_NAME, origin(0), this, this::parserError)
.setEngine(engine)
.setHelperFactory(ROOT_SECTION_HELPER_FACTORY));

long start = System.currentTimeMillis();
Reader r = reader;

Expand Down Expand Up @@ -162,11 +160,6 @@ Template parse() {
if (!root.helperName.equals(ROOT_HELPER_NAME)) {
throw parserError("unterminated section [" + root.helperName + "] detected");
}
SectionBlock.Builder part = sectionBlockStack.peek();
if (part == null) {
throw parserError("no root section part found");
}
root.addBlock(part.build());
TemplateImpl template = new TemplateImpl(engine, root.build(), generatedId, variant);

Set<TemplateNode> nodesToRemove;
Expand Down Expand Up @@ -270,7 +263,7 @@ private void comment(char character) {
buffer = new StringBuilder();
if (engine.removeStandaloneLines) {
// Add a dummy comment block to detect standalone lines
sectionBlockStack.peek().addNode(COMMENT_NODE);
sectionStack.peek().currentBlock().addNode(COMMENT_NODE);
}
} else {
buffer.append(character);
Expand Down Expand Up @@ -365,15 +358,15 @@ private boolean isLineSeparatorStart(char character) {

private void flushText() {
if (buffer.length() > 0 && !ignoreContent) {
SectionBlock.Builder block = sectionBlockStack.peek();
SectionBlock.Builder block = sectionStack.peek().currentBlock();
block.addNode(new TextNode(buffer.toString(), origin(0)));
}
this.buffer = new StringBuilder();
}

private void flushNextLine() {
if (buffer.length() > 0 && !ignoreContent) {
SectionBlock.Builder block = sectionBlockStack.peek();
SectionBlock.Builder block = sectionStack.peek().currentBlock();
block.addNode(new LineSeparatorNode(buffer.toString(), origin(0)));
}
this.buffer = new StringBuilder();
Expand All @@ -387,7 +380,8 @@ private void flushTag() {
String tag = START_DELIMITER + content + END_DELIMITER;

if (content.charAt(0) == Tag.SECTION.command) {

// It's a section/block start
// {#if}, {#else}, etc.
boolean isEmptySection = false;
if (content.charAt(content.length() - 1) == Tag.SECTION_END.command) {
content = content.substring(0, content.length() - 1);
Expand All @@ -402,114 +396,95 @@ private void flushTag() {
sectionName = sectionName.substring(1, sectionName.length());

SectionNode.Builder lastSection = sectionStack.peek();
// Add a section block if the section name matches a section block label or does not map to any section helper and the last section treats unknown subsections as blocks
// Add a section block if the section name matches a section block label
// or does not map to any section helper and the last section treats unknown subsections as blocks
if (lastSection != null && lastSection.factory.getBlockLabels().contains(sectionName)
|| (lastSection.factory.treatUnknownSectionsAsBlocks()
&& !engine.getSectionHelperFactories().containsKey(sectionName))) {

// Section block
if (!ignoreContent) {
// E.g. {#else if valid}
// Build the previous block
sectionStack.peek().addBlock(sectionBlockStack.pop().build());
}
// Add the new block
// => New section block
SectionBlock.Builder block = SectionBlock.builder("" + sectionBlockIdx++, this, this::parserError)
.setOrigin(origin(0));
sectionBlockStack.addFirst(block.setLabel(sectionName));
processParams(tag, sectionName, iter);
.setOrigin(origin(0)).setLabel(sectionName);
lastSection.addBlock(block);

processParams(tag, sectionName, iter, block);

// Initialize the block
Scope currentScope = scopeStack.peek();
Scope newScope = sectionStack.peek().factory.initializeBlock(currentScope, block);
Scope newScope = lastSection.factory.initializeBlock(currentScope, block);
scopeStack.addFirst(newScope);

// A new block - stop ignoring the block content
ignoreContent = false;

} else {
// New section
// => New section
SectionHelperFactory<?> factory = engine.getSectionHelperFactory(sectionName);
if (factory == null) {
throw parserError("no section helper found for " + tag);
}
SectionNode.Builder sectionNode = SectionNode
.builder(sectionName, origin(0), this, this::parserError)
.setEngine(engine)
.setHelperFactory(factory);

paramsStack.addFirst(factory.getParameters());
SectionBlock.Builder mainBlock = SectionBlock
.builder(SectionHelperFactory.MAIN_BLOCK_NAME, this, this::parserError)
.setOrigin(origin(0));
sectionBlockStack.addFirst(mainBlock);
processParams(tag, SectionHelperFactory.MAIN_BLOCK_NAME, iter);
processParams(tag, SectionHelperFactory.MAIN_BLOCK_NAME, iter, sectionNode.currentBlock());

// Init section block
Scope currentScope = scopeStack.peek();
Scope newScope = factory.initializeBlock(currentScope, mainBlock);
SectionNode.Builder sectionNode = SectionNode
.builder(sectionName, origin(0))
.setEngine(engine)
.setHelperFactory(factory);
Scope newScope = factory.initializeBlock(currentScope, sectionNode.currentBlock());

if (isEmptySection) {
sectionNode.addBlock(mainBlock.build());
// Remove params from the stack
paramsStack.pop();
// Remove the block from the stack
sectionBlockStack.pop();
// Add node to the parent block
sectionBlockStack.peek().addNode(sectionNode.build());
sectionStack.peek().currentBlock().addNode(sectionNode.build());
} else {
scopeStack.addFirst(newScope);
sectionStack.addFirst(sectionNode);
}
}
} else if (content.charAt(0) == Tag.SECTION_END.command) {
SectionBlock.Builder block = sectionBlockStack.peek();
// It's a section/block end
SectionNode.Builder section = sectionStack.peek();
SectionBlock.Builder block = section.currentBlock();
String name = content.substring(1, content.length());
if (block != null && !block.getLabel().equals(SectionHelperFactory.MAIN_BLOCK_NAME)
&& !section.helperName.equals(name)) {
// Block end
// Non-main block end, e.g. {/else}
if (!name.isEmpty() && !block.getLabel().equals(name)) {
throw parserError(
"section block end tag [" + name + "] does not match the start tag [" + block.getLabel() + "]");
}
section.addBlock(sectionBlockStack.pop().build());
// Ignore the block content until a next block starts or the current section ends
ignoreContent = true;
section.endBlock();
} else {
// Section end
// Section end, e.g. {/if}
if (section.helperName.equals(ROOT_HELPER_NAME)) {
throw parserError("no section start tag found for " + tag);
}
if (!name.isEmpty() && !section.helperName.equals(name)) {
throw parserError(
"section end tag [" + name + "] does not match the start tag [" + section.helperName + "]");
}
// Pop the section and its main block
section = sectionStack.pop();
if (!ignoreContent) {
// Add the current block to the current section
section.addBlock(sectionBlockStack.pop().build());
} else {
// The current section ends - stop ignoring the block content
ignoreContent = false;
}
sectionBlockStack.peek().addNode(section.build());
sectionStack.peek().currentBlock().addNode(section.build());
}

// Remove the last type info map from the stack
scopeStack.pop();

} else if (content.charAt(0) == Tag.PARAM.command) {

// Parameter declaration
// {@org.acme.Foo foo}
Scope currentScope = scopeStack.peek();
int spaceIdx = content.indexOf(" ");
String key = content.substring(spaceIdx + 1, content.length());
String value = content.substring(1, spaceIdx);
currentScope.putBinding(key, Expressions.TYPE_INFO_SEPARATOR + value + Expressions.TYPE_INFO_SEPARATOR);
sectionBlockStack.peek().addNode(new ParameterDeclarationNode(content, origin(0)));

sectionStack.peek().currentBlock().addNode(new ParameterDeclarationNode(content, origin(0)));
} else {
sectionBlockStack.peek().addNode(new ExpressionNode(apply(content), engine, origin(content.length() + 1)));
// Expression
sectionStack.peek().currentBlock()
.addNode(new ExpressionNode(apply(content), engine, origin(content.length() + 1)));
}
this.buffer = new StringBuilder();
}
Expand All @@ -525,7 +500,7 @@ private TemplateException parserError(String message) {
builder.toString());
}

private void processParams(String tag, String label, Iterator<String> iter) {
private void processParams(String tag, String label, Iterator<String> iter, SectionBlock.Builder block) {
Map<String, String> params = new LinkedHashMap<>();
List<Parameter> factoryParams = paramsStack.peek().get(label);
List<String> paramValues = new ArrayList<>();
Expand Down Expand Up @@ -591,7 +566,7 @@ private void processParams(String tag, String label, Iterator<String> iter) {
throw parserError("mandatory section parameters not declared for " + tag + ": " + undeclaredParams);
}

params.forEach(sectionBlockStack.peek()::addParameter);
params.forEach(block::addParameter);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,6 @@ SectionBlock.Builder addNode(TemplateNode node) {
return this;
}

SectionBlock.Builder addNodes(TemplateNode... nodes) {
Collections.addAll(this.nodes, nodes);
return this;
}

SectionBlock.Builder setLabel(String label) {
this.label = label;
return this;
Expand Down
Loading

0 comments on commit 69c1551

Please sign in to comment.