From 91ecaa0ce12a16eb9064324c575a2c57d7d9bf82 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 28 Jul 2022 12:40:04 +0200 Subject: [PATCH] Qute - more lenient parsing of named section parameters - resolves #26975 --- docs/src/main/asciidoc/qute-reference.adoc | 26 ++++++++++--------- .../src/main/java/io/quarkus/qute/Parser.java | 20 ++++++++++++++ .../test/java/io/quarkus/qute/ParserTest.java | 1 + .../java/io/quarkus/qute/SetSectionTest.java | 2 +- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index a7a68ac44c678..0c5a1fb082c55 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -569,32 +569,34 @@ NOTE: Similar errors are detected at build time if <> 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 diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java index 0afaec1cddb8c..0d235df0c083e 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java @@ -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; @@ -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 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(); } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java index c64079d8a6ac5..a94a9f74b1ac2 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java @@ -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 diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java index 25511136b6c7c..0d1e41316d097 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java @@ -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 expressions = template.getExpressions(); assertEquals(2, expressions.size()); for (Expression expression : expressions) {