diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 5b97c298ee690e..724c4384efffe4 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -753,17 +753,15 @@ public void beforeParsing(ParserHelper parserHelper) { } } - analysis.add(new TemplateAnalysis(null, template.getGeneratedId(), template.getExpressions(), - template.getParameterDeclarations(), path.getPath(), template.getFragmentIds())); + analysis.add(new TemplateAnalysis(null, template, path.getPath())); } } // Message bundle templates for (MessageBundleMethodBuildItem messageBundleMethod : messageBundleMethods) { Template template = dummyEngine.parse(messageBundleMethod.getTemplate(), null, messageBundleMethod.getTemplateId()); - analysis.add(new TemplateAnalysis(messageBundleMethod.getTemplateId(), template.getGeneratedId(), - template.getExpressions(), template.getParameterDeclarations(), messageBundleMethod.getPathForAnalysis(), - template.getFragmentIds())); + analysis.add(new TemplateAnalysis(messageBundleMethod.getTemplateId(), template, + messageBundleMethod.getPathForAnalysis())); } LOGGER.debugf("Finished analysis of %s templates in %s ms", analysis.size(), @@ -1500,7 +1498,8 @@ private static NamespaceResult processNamespace(Expression expression, MatchResu // However, this might result in confusing behavior when type-safe templates are used together with type-safe expressions. // But this should not be a common use case. ParameterDeclaration paramDeclaration = null; - for (ParameterDeclaration pd : templateAnalysis.getSortedParameterDeclarations()) { + for (ParameterDeclaration pd : TemplateAnalysis + .getSortedParameterDeclarations(templateAnalysis.parameterDeclarations)) { if (pd.getKey().equals(firstPartName)) { paramDeclaration = pd; break; diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java index d67efdf86ab795..6bf63ee4032037 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java @@ -1,14 +1,18 @@ package io.quarkus.qute.deployment; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.qute.Expression; import io.quarkus.qute.ParameterDeclaration; +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateNode; /** * Represents the result of analysis of all templates. @@ -45,23 +49,35 @@ public static final class TemplateAnalysis { public final Set fragmentIds; - public TemplateAnalysis(String id, String generatedId, List expressions, - List parameterDeclarations, String path, Set fragmentIds) { + // Parsed template; should never be used directly + private final Template template; + + TemplateAnalysis(String id, Template template, String path) { this.id = id; - this.generatedId = generatedId; - this.expressions = expressions; - this.parameterDeclarations = parameterDeclarations; + this.generatedId = template.getGeneratedId(); + this.expressions = template.getExpressions(); + this.parameterDeclarations = template.getParameterDeclarations(); this.path = path; - this.fragmentIds = fragmentIds; + this.fragmentIds = template.getFragmentIds(); + this.template = template; } - Expression findExpression(int id) { - for (Expression expression : expressions) { - if (expression.getGeneratedId() == id) { - return expression; - } - } - return null; + /** + * + * @return the child nodes of the root node + * @see Template#getNodes() + */ + public List getNodes() { + return template.getNodes(); + } + + /** + * + * @return the collection of nodes that match the given predicate + * @see Template#findNodes(Predicate) + */ + public Collection findNodes(Predicate predicate) { + return template.findNodes(predicate); } /** @@ -70,6 +86,16 @@ Expression findExpression(int id) { * @return the sorted list of parameter declarations */ public List getSortedParameterDeclarations() { + return getSortedParameterDeclarations(parameterDeclarations); + } + + /** + * Non-synthetic declarations go first, then sorted by the line. + * + * @return the sorted list of parameter declarations + */ + public static List getSortedParameterDeclarations( + List parameterDeclarations) { List ret = new ArrayList<>(parameterDeclarations); ret.sort(new Comparator() { @Override @@ -81,6 +107,15 @@ public int compare(ParameterDeclaration pd1, ParameterDeclaration pd2) { return ret; } + Expression findExpression(int id) { + for (Expression expression : expressions) { + if (expression.getGeneratedId() == id) { + return expression; + } + } + return null; + } + @Override public int hashCode() { final int prime = 31; diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/QuteProcessorTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/QuteProcessorTest.java index 4dd266f9204d79..866d75bb91770a 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/QuteProcessorTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/QuteProcessorTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Collections; import java.util.List; import java.util.Set; import java.util.regex.Pattern; @@ -37,8 +36,7 @@ public void testTemplateDataIgnorePattern() { @Test public void testCollectNamespaceExpressions() { Template template = Engine.builder().build().parse("{msg:hello} {msg2:hello_alpha} {foo:baz.get(foo:bar)}"); - TemplateAnalysis analysis = new TemplateAnalysis("foo", "1", template.getExpressions(), Collections.emptyList(), null, - Collections.emptySet()); + TemplateAnalysis analysis = new TemplateAnalysis("foo", template, null); Set msg = QuteProcessor.collectNamespaceExpressions(analysis, "msg"); assertEquals(1, msg.size()); assertEquals("msg:hello", msg.iterator().next().toOriginalString()); diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateAnalysisTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateAnalysisTest.java index 143d778592b4fe..95e6a9845a832a 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateAnalysisTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateAnalysisTest.java @@ -18,9 +18,8 @@ public class TemplateAnalysisTest { @Test public void testSortedParamDeclarations() { - TemplateAnalysis analysis = new TemplateAnalysis(null, null, null, List.of(paramDeclaration("foo", -1), - paramDeclaration("bar", -1), paramDeclaration("qux", 10), paramDeclaration("baz", 1)), null, null); - List sorted = analysis.getSortedParameterDeclarations(); + List sorted = TemplateAnalysis.getSortedParameterDeclarations(List.of(paramDeclaration("foo", -1), + paramDeclaration("bar", -1), paramDeclaration("qux", 10), paramDeclaration("baz", 1))); assertEquals(4, sorted.size()); assertEquals("baz", sorted.get(0).getKey()); assertEquals("qux", sorted.get(1).getKey()); diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java index 6cd776eee8a409..55cf3151aec9a9 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java @@ -4,6 +4,7 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -36,6 +37,7 @@ import io.quarkus.qute.Template; import io.quarkus.qute.TemplateInstance; import io.quarkus.qute.TemplateInstanceBase; +import io.quarkus.qute.TemplateNode; import io.quarkus.qute.Variant; import io.quarkus.qute.runtime.QuteRecorder.QuteContext; import io.quarkus.runtime.LaunchMode; @@ -237,6 +239,22 @@ public Set getFragmentIds() { throw ambiguousTemplates("getFragmentIds()"); } + @Override + public List getNodes() { + if (unambiguousTemplate != null) { + return unambiguousTemplate.get().getNodes(); + } + throw ambiguousTemplates("getNodes()"); + } + + @Override + public Collection findNodes(Predicate predicate) { + if (unambiguousTemplate != null) { + return unambiguousTemplate.get().findNodes(predicate); + } + throw ambiguousTemplates("findNodes()"); + } + private UnsupportedOperationException ambiguousTemplates(String method) { return new UnsupportedOperationException("Ambiguous injected templates do not support " + method); } @@ -299,6 +317,16 @@ public Set getFragmentIds() { return InjectableTemplate.this.getFragmentIds(); } + @Override + public List getNodes() { + return InjectableTemplate.this.getNodes(); + } + + @Override + public Collection findNodes(Predicate predicate) { + return InjectableTemplate.this.findNodes(predicate); + } + @Override public TemplateInstance instance() { TemplateInstance instance = new InjectableFragmentTemplateInstanceImpl(identifier); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineConfiguration.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineConfiguration.java index fb6284cfbf05a9..f103b54208d463 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineConfiguration.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineConfiguration.java @@ -13,7 +13,8 @@ * * Enables registration of additional components to the preconfigured {@link Engine}. *

- * A top-level or static nested class that implements one of the supported component interface and is annotated with this + * A top-level or static nested class that implements one of the supported component interfaces and is annotated with + * this * annotation: *

    *
  • can be used during validation of templates at build time,
  • diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ExpressionNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ExpressionNode.java index 3cc1425adbf9bd..94d573a0fd863b 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ExpressionNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ExpressionNode.java @@ -10,7 +10,7 @@ /** * This node holds a single expression such as {@code foo.bar}. */ -class ExpressionNode implements TemplateNode { +public class ExpressionNode implements TemplateNode { private static final Logger LOG = Logger.getLogger("io.quarkus.qute.nodeResolve"); @@ -36,6 +36,38 @@ public CompletionStage resolve(ResolutionContext context) { return context.evaluate(expression).thenCompose(this::toResultNode); } + @Override + public Origin getOrigin() { + return expression.getOrigin(); + } + + @Override + public boolean isConstant() { + return expression.isLiteral(); + } + + @Override + public List getExpressions() { + return Collections.singletonList(expression); + } + + @Override + public Kind kind() { + return Kind.EXPRESSION; + } + + @Override + public ExpressionNode asExpression() { + return this; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ExpressionNode [expression=").append(expression).append("]"); + return builder.toString(); + } + CompletionStage toResultNode(Object result) { if (traceLevel) { LOG.tracef("Resolve {%s} completed:%s", expression.toOriginalString(), expression.getOrigin()); @@ -53,30 +85,10 @@ CompletionStage toResultNode(Object result) { } } - public Origin getOrigin() { - return expression.getOrigin(); - } - - @Override - public boolean isConstant() { - return expression.isLiteral(); - } - Engine getEngine() { return engine; } - public List getExpressions() { - return Collections.singletonList(expression); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("ExpressionNode [expression=").append(expression).append("]"); - return builder.toString(); - } - boolean hasEngineResultMappers() { return hasEngineResultMappers; } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java index 583fe35aadbe7f..e1726b774bd628 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java @@ -84,6 +84,14 @@ public CompletionStage resolve(SectionResolutionContext context) { } } + public Map getParameters() { + return parameters; + } + + public boolean isIsolated() { + return isIsolated; + } + protected boolean optimizeIfNoParams() { return true; } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParameterDeclarationNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParameterDeclarationNode.java index 1259e68b9101f7..8ef8368c554469 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParameterDeclarationNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ParameterDeclarationNode.java @@ -48,4 +48,14 @@ public List getParameterDeclarations() { return Collections.singletonList(this); } + @Override + public Kind kind() { + return Kind.PARAM_DECLARATION; + } + + @Override + public ParameterDeclarationNode asParamDeclaration() { + return this; + } + } 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 3b43d28e9cb815..5b23843db8a4d0 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 @@ -1306,6 +1306,11 @@ public CompletionStage resolve(ResolutionContext context) { throw new IllegalStateException(); } + @Override + public Kind kind() { + throw new UnsupportedOperationException(); + } + @Override public Origin getOrigin() { throw new IllegalStateException(); @@ -1321,6 +1326,11 @@ public CompletionStage resolve(ResolutionContext context) { throw new UnsupportedOperationException(); } + @Override + public Kind kind() { + throw new UnsupportedOperationException(); + } + @Override public Origin getOrigin() { throw new UnsupportedOperationException(); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionBlock.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionBlock.java index a584e9be57fe37..9874def11fb64b 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionBlock.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionBlock.java @@ -15,17 +15,19 @@ /** * Each section consists of one or more blocks. The main block is always present. Additional blocks start with a label * definition: {#label param1}. + * + * @see SectionHelperFactory#MAIN_BLOCK_NAME */ public final class SectionBlock implements WithOrigin, ErrorInitializer { public final Origin origin; /** - * Id generated by the parser. {@code main} for the main block. + * Id generated by the parser. {@value SectionHelperFactory#MAIN_BLOCK_NAME} for the main block. */ public final String id; /** - * Label used for the given part. {@code main} for the main block. + * Label used for the given part. {@value SectionHelperFactory#MAIN_BLOCK_NAME} for the main block. */ public final String label; /** @@ -45,7 +47,7 @@ public final class SectionBlock implements WithOrigin, ErrorInitializer { /** * Section content - an immutable list of template nodes. */ - List nodes; + public List nodes; private final List positionalParameters; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java index bee4473a4d5004..849170227e8ecc 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java @@ -18,8 +18,13 @@ public interface SectionHelperFactory { // The validation of expressions with the metadata hint may be relaxed in some cases - public static final String HINT_METADATA = ""; + String HINT_METADATA = ""; + /** + * The name of the main block. + * + * @see SectionBlock + */ String MAIN_BLOCK_NAME = "$main"; /** diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java index e90f6cc4d3d5ff..492334f57fd96f 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java @@ -16,7 +16,7 @@ /** * Section node. */ -class SectionNode implements TemplateNode { +public class SectionNode implements TemplateNode { private static final Logger LOG = Logger.getLogger("io.quarkus.qute.nodeResolve"); @@ -63,8 +63,25 @@ public Origin getOrigin() { } @Override - public boolean isSection() { - return true; + public Kind kind() { + return Kind.SECTION; + } + + @Override + public SectionNode asSection() { + return this; + } + + public String getName() { + return name; + } + + public List getBlocks() { + return blocks; + } + + public SectionHelper getHelper() { + return helper; } void optimizeNodes(Set nodes) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java index 615d72b3da5308..3783a043de3371 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java @@ -1,5 +1,6 @@ package io.quarkus.qute; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -117,14 +118,12 @@ default String render() { } /** - * If invoked upon a fragment instance then delegate to the defining template. * * @return an immutable list of expressions used in the template */ List getExpressions(); /** - * If invoked upon a fragment instance then delegate to the defining template. * * @param predicate * @return the first expression matching the given predicate or {@code null} if no such expression is used in the template @@ -192,6 +191,21 @@ default boolean isFragment() { return false; } + /** + * Returns the child nodes of the root node. + * + * @return the child nodes of the root node + */ + List getNodes(); + + /** + * Returns all nodes of this template that match the given predicate. + * + * @param predicate + * @return the collection of nodes that match the given predicate + */ + Collection findNodes(Predicate predicate); + /** * A fragment represents a part of the template that can be treated as a separate template. */ diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java index ba7bd1c2091bdd..3719217194df3a 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java @@ -3,6 +3,7 @@ import static io.quarkus.qute.Namespaces.DATA_NAMESPACE; import java.time.Duration; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -104,6 +105,16 @@ public Set getFragmentIds() { return fragments != null ? Set.copyOf(fragments.get().keySet()) : Set.of(); } + @Override + public List getNodes() { + return root.blocks.get(0).nodes; + } + + @Override + public Collection findNodes(Predicate predicate) { + return root.findNodes(predicate); + } + private LazyValue> initFragments(SectionNode section) { if (section.name.equals(Parser.ROOT_HELPER_NAME)) { // Initialize the lazy map for root sections only @@ -271,7 +282,7 @@ public String toString() { class FragmentImpl extends TemplateImpl implements Fragment { - public FragmentImpl(EngineImpl engine, SectionNode root, String fragmentId, String generatedId, + FragmentImpl(EngineImpl engine, SectionNode root, String fragmentId, String generatedId, Optional variant) { super(engine, root, fragmentId, generatedId, variant); } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java index 23096cb61698cf..cf2b4a0e8dd902 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java @@ -6,7 +6,10 @@ import java.util.concurrent.CompletionStage; /** - * Node of a template tree. + * Tree node of a parsed template. + * + * @see Template#getNodes() + * @see Template#findNodes(java.util.function.Predicate) */ public interface TemplateNode { @@ -41,8 +44,11 @@ default List getParameterDeclarations() { Origin getOrigin(); /** + * Constant means a static text or a literal output expression. * * @return {@code true} if the node represents a constant + * @see TextNode + * @see Expression#isLiteral() */ default boolean isConstant() { return false; @@ -54,7 +60,69 @@ default boolean isConstant() { * @see SectionNode */ default boolean isSection() { - return false; + return kind() == Kind.SECTION; + } + + /** + * + * @return {@code true} if the node represents a text + * @see TextNode + */ + default boolean isText() { + return kind() == Kind.TEXT; + } + + /** + * + * @return{@code true} if the node represents an output expression + * @see ExpressionNode + */ + default boolean isExpression() { + return kind() == Kind.EXPRESSION; + } + + /** + * Returns the kind of this node. + *

    + * Note that comments and line separators are never preserved in the parsed template tree. + * + * @return the kind + */ + Kind kind(); + + default TextNode asText() { + throw new IllegalStateException(); + } + + default SectionNode asSection() { + throw new IllegalStateException(); + } + + default ExpressionNode asExpression() { + throw new IllegalStateException(); + } + + default ParameterDeclarationNode asParamDeclaration() { + throw new IllegalStateException(); + } + + public enum Kind { + /** + * @see TextNode + */ + TEXT, + /** + * @see SectionNode + */ + SECTION, + /** + * @see ExpressionNode + */ + EXPRESSION, + /** + * @see ParameterDeclarationNode + */ + PARAM_DECLARATION, } /** diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TextNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TextNode.java index 9bd0a4a66184ba..44d2b460e23a3c 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TextNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TextNode.java @@ -23,10 +23,16 @@ public CompletionStage resolve(ResolutionContext context) { return result; } + @Override public Origin getOrigin() { return origin; } + @Override + public Kind kind() { + return Kind.TEXT; + } + @Override public boolean isConstant() { return true; @@ -41,6 +47,11 @@ public void process(Consumer consumer) { consumer.accept(value); } + @Override + public TextNode asText() { + return this; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/UserTagSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/UserTagSectionHelper.java index 7754638e9f4761..2b350e6164b3a1 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/UserTagSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/UserTagSectionHelper.java @@ -49,6 +49,18 @@ protected void addAdditionalEvaluatedParams(SectionResolutionContext context, Ma } } + public boolean isNestedContentNeeded() { + return isNestedContentNeeded; + } + + public HtmlEscaper getHtmlEscaper() { + return htmlEscaper; + } + + public String getItKey() { + return itKey; + } + private boolean isNestedContent(Expression expr) { return expr.getParts().size() == 1 && expr.getParts().get(0).getName().equals(NESTED_CONTENT); } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java index 9b73992a9fca26..b72fed5a3d0efb 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; @@ -28,6 +29,9 @@ public void testSimpleFragment() { assertEquals(template.getFragment("another").getGeneratedId(), another.getGeneratedId()); assertEquals("fragments.html", template.getFragment("another").getOriginalTemplate().getId()); assertEquals(Set.of("foo_and_bar", "another"), template.getFragmentIds()); + List anotherNodes = another.getNodes(); + assertEquals(1, anotherNodes.size()); + assertTrue(anotherNodes.get(0) instanceof ExpressionNode); } @Test 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 6337cadc49d36f..55ee9a3f510360 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 @@ -280,8 +280,8 @@ public void testValidIdentifiers() { @Test public void testTextNodeCollapse() { - TemplateImpl template = (TemplateImpl) Engine.builder().addDefaults().build().parse("Hello\nworld!{foo}next"); - List rootNodes = template.root.blocks.get(0).nodes; + Template template = Engine.builder().addDefaults().build().parse("Hello\nworld!{foo}next"); + List rootNodes = template.getNodes(); assertEquals(3, rootNodes.size()); assertEquals("Hello\nworld!", ((TextNode) rootNodes.get(0)).getValue()); assertEquals(1, ((ExpressionNode) rootNodes.get(1)).getExpressions().size()); diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/TemplateNodesTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/TemplateNodesTest.java new file mode 100644 index 00000000000000..70bd55a4d91a05 --- /dev/null +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/TemplateNodesTest.java @@ -0,0 +1,61 @@ +package io.quarkus.qute; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +public class TemplateNodesTest { + + @Test + public void testGetNodes() { + Engine engine = Engine.builder() + .addDefaults() + .build(); + Template t1 = engine.parse("Hello\nworld!{#if true}next{level}{/}"); + List rootNodes = t1.getNodes(); + assertEquals(2, rootNodes.size()); + assertTrue(rootNodes.get(0).isText()); + assertTrue(rootNodes.get(1).isSection()); + SectionNode iftrue = rootNodes.get(1).asSection(); + assertEquals(1, iftrue.getBlocks().size()); + assertEquals(SectionHelperFactory.MAIN_BLOCK_NAME, iftrue.getBlocks().get(0).label); + assertTrue(iftrue.getBlocks().get(0).nodes.get(0).isText()); + } + + @Test + public void testFindNodes() { + Engine engine = Engine.builder() + .addDefaults() + .addSectionHelper(new UserTagSectionHelper.Factory("bundle", "bundle.html")) + .build(); + Template t1 = engine.parse("Hello\nworld!{#if true}next{level}{/}"); + Collection sections = t1.findNodes(TemplateNode::isSection); + assertEquals(1, sections.size()); + assertEquals("if", sections.iterator().next().asSection().name); + assertThat(sections.iterator().next().asSection().getHelper()).isInstanceOf(IfSectionHelper.class); + Collection texts = t1.findNodes(TemplateNode::isText).stream().map(TemplateNode::asText) + .map(TextNode::getValue).collect(Collectors.toList()); + assertEquals(2, texts.size()); + assertThat(texts).containsAll(List.of("Hello\nworld!", "next")); + assertEquals("level", + t1.findNodes(TemplateNode::isExpression).iterator().next().asExpression().expression + .toOriginalString()); + Template t2 = engine.parse("{#bundle tag=script}foo{/bundle}"); + SectionNode bundle = t2.findNodes(TemplateNode::isSection).stream().map(TemplateNode::asSection) + .filter(s -> s.getName().equals("bundle")).findFirst().orElse(null); + assertNotNull(bundle); + assertTrue(bundle.getHelper() instanceof UserTagSectionHelper); + UserTagSectionHelper helper = (UserTagSectionHelper) bundle.getHelper(); + Expression e1 = helper.getParameters().get("tag"); + assertNotNull(e1); + assertEquals("script", e1.toOriginalString()); + } + +}