Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qute - more lenient parsing of named section parameters #26993

Merged
merged 1 commit into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -569,32 +569,34 @@ NOTE: Similar errors are detected at build time if <<typesafe_expressions>> and
[[sections]]
=== Sections

A section:
A section has a start tag that starts with `#`, followed by the name of the section such as `{#if}` and `{#each}`.
It may be empty, i.e. the start tag ends with `/`: `{#myEmptySection /}`.
Sections usually contain nested expressions and other sections.
The end tag starts with `/` and contains the name of the section (optional): `{#if foo}Foo!{/if}` or `{#if foo}Foo!{/}`.

* has a start tag
** starts with `#`, followed by the name of the section such as `{#if}` and `{#each}`,
* may be empty
** tag ends with `/`, ie. `{#emptySection /}`
* may contain other expression, sections, etc.
** the end tag starts with `/` and contains the name of the section (optional): `{#if foo}Foo!{/if}` or `{#if foo}Foo!{/}`,
The start tag can also define parameters: `{#if item.isActive}`.
Parameters are separated by one or more spaces.
However, parameters can have optional names separated by the equals sign prefixed and suffixed with any number of spaces, e.g. `{#let id='Foo'}` and `{#let id = 'Foo'}` are equivalents where the name of the parameter is `id` and the value is `Foo`.

The start tag can also define parameters.
The parameters have optional names.
A section may contain several content *blocks*.
The "main" block is always present.
Additional/nested blocks also start with `#` and can have parameters too - `{#else if item.isActive}`.
A section helper that defines the logic of a section can "execute" any of the blocks and evaluate the parameters.

.`#if` Section Example
[source]
----
{#if item.name is 'sword'}
It's a sword!
It's a sword! <1>
{#else if item.name is 'shield'}
It's a shield!
It's a shield! <2>
{#else}
Item is neither a sword nor a shield.
Item is neither a sword nor a shield. <3>
{/if}
----
<1> This is the main block.
<2> Additional block.
<3> Additional block.

[[loop_section]]
==== Loop Section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
Expand Down Expand Up @@ -823,6 +824,25 @@ && isCompositeEnd(c) && composite > 0) {
}
parts.add(buffer.toString());
}

// Try to find/replace "standalone" equals signs used as param names separators
// This allows for more lenient parsing of named section parameters, e.g. item.name = 'foo' instead of item.name='foo'
for (ListIterator<String> it = parts.listIterator(); it.hasNext();) {
if (it.next().equals("=") && it.previousIndex() != 0 && it.hasNext()) {
// move cursor back
it.previous();
String merged = parts.get(it.previousIndex()) + it.next() + it.next();
// replace the element with the merged value
it.set(merged);
// move cursor back and remove previous two elements
it.previous();
it.previous();
it.remove();
it.previous();
it.remove();
}
}

return parts.iterator();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ public void testSectionParameters() {
assertParams("(item.name != 'foo') || (item.name == false)", "(item.name != 'foo')", "||", "(item.name == false)");
assertParams("foo.codePointCount(0, foo.length) baz=bar", "foo.codePointCount(0, foo.length)", "baz=bar");
assertParams("foo.codePointCount( 0 , foo.length( 1)) baz=bar", "foo.codePointCount( 0 , foo.length( 1))", "baz=bar");
assertParams("item.name = 'foo' item.surname = 'bar'", "item.name='foo'", "item.surname='bar'");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void testDefaultValues() {
@Test
public void testParameterOrigin() {
Engine engine = Engine.builder().addDefaults().build();
Template template = engine.parse(" {#let item=1 foo=bar}{/let}");
Template template = engine.parse(" {#let item = 1 foo=bar}{/let}");
List<Expression> expressions = template.getExpressions();
assertEquals(2, expressions.size());
for (Expression expression : expressions) {
Expand Down