From 563701c4451ed3eb8473b6765322f879cbb38066 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 1 Aug 2025 14:33:42 +0200 Subject: [PATCH 01/10] pom: Configure javadoc plugin to exclude specific source files from doclint --- pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index d725b50..25f752d 100644 --- a/pom.xml +++ b/pom.xml @@ -221,6 +221,15 @@ org.apache.maven.plugins maven-javadoc-plugin ${version.javadoc.plugin} + + all,-missing + + **/EfxLexer.java + **/EfxParser.java + **/EfxListener.java + **/EfxBaseListener.java + + org.apache.maven.plugins From 1ad335e849e91f85628cb44198abed2a5e135e56 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 1 Aug 2025 14:46:12 +0200 Subject: [PATCH 02/10] Add support for EFX-2 summary and navigation sections in view templates. --- .../ted/efx/interfaces/MarkupGenerator.java | 189 +++++-- .../ted/efx/interfaces/TemplateSection.java | 13 + .../ted/efx/interfaces/TranslatorContext.java | 36 ++ .../ted/efx/model/templates/ContentBlock.java | 23 +- .../model/templates/TemplateInvocation.java | 7 +- .../efx/sdk1/EfxExpressionTranslatorV1.java | 6 +- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 27 +- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 110 ++-- .../ted/efx/mock/MarkupGeneratorMock.java | 65 ++- .../efx/sdk1/EfxTemplateTranslatorV1Test.java | 81 +-- .../efx/sdk2/EfxTemplateTranslatorV2Test.java | 519 +++++++++++------- 11 files changed, 718 insertions(+), 358 deletions(-) create mode 100644 src/main/java/eu/europa/ted/efx/interfaces/TemplateSection.java create mode 100644 src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java diff --git a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java index 7c2c4bd..b32d9fb 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -38,35 +38,40 @@ */ public interface MarkupGenerator { + /** * Given a body (main content) and a set of fragments, this method returns the - * full content of the target template file. + * full content of the + * target template file. * - * @deprecated This method is deprecated and will be removed in future versions. - * Use {@link #composeOutputFile(List, List, List)} instead. - * We are keeping the method temporarily to prevent build errors. - * This method is being deprecated as of version 2.0.0-alpha.4 and - * will be removed before version 2.0.0 is released - * - * @param content the body (main content) of the template. + * @param globals the global variables and functions to be included in the + * template file. + * @param mainSection the main section (body) of the template. + * @param summarySection the summary section of the template. + * @param navigationSection the navigation section of the template. * @param fragments the fragments to be included in the template file. * @return the full content of the target template file. */ - @Deprecated(since = "2.0.0-alpha.4", forRemoval = true) - Markup composeOutputFile(final List content, final List fragments); + Markup composeOutputFile(List globals, final List mainSection, final List summarySection, final List navigationSection, final List fragments); /** * Given a body (main content) and a set of fragments, this method returns the - * full content of the - * target template file. + * full content of the target template file. * - * @param globals the global variables and functions to be included in the - * template file. + * @deprecated This method is deprecated and will be removed in future versions. + * Use {@link #composeOutputFile(List, List, List, List, List)} instead. + * We are keeping the method temporarily to prevent build errors. + * This method is being deprecated as of version 2.0.0-alpha.6 and + * will be removed before version 2.0.0 is released + * * @param content the body (main content) of the template. * @param fragments the fragments to be included in the template file. * @return the full content of the target template file. */ - Markup composeOutputFile(List globals, final List content, final List fragments); + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup composeOutputFile(final List content, final List fragments) { + return this.composeOutputFile(List.of(), content, List.of(), List.of(), fragments); + } /** * Renders the markup necessary to declare and initialize the variable making it @@ -116,10 +121,16 @@ public interface MarkupGenerator { * template. * * @param variableExpression the expression to be evaluated and rendered. + * @param translatorContext additional context information provided by the template translator. * @return the template code that dereferences the expression in the target * template. */ - Markup renderVariableExpression(final Expression variableExpression); + Markup renderVariableExpression(final Expression variableExpression, final TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderVariableExpression(final Expression variableExpression) { + return this.renderVariableExpression(variableExpression, TranslatorContext.DEFAULT); + } /** * Given a label key (which will eventually, at runtime, be dereferenced to a @@ -127,11 +138,18 @@ public interface MarkupGenerator { * method returns the template code that renders this label in the target * template language. * - * @param key the label key to be dereferenced. - * @return the template code that renders the label in the target template - * language. + * @param key the label key to be dereferenced. + * @param translatorContext additional context information provided by the template + * translator. + * @return the template code that renders the label in the target template + * language. */ - Markup renderLabelFromKey(final StringExpression key); + Markup renderLabelFromKey(final StringExpression key, TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderLabelFromKey(final StringExpression key) { + return this.renderLabelFromKey(key, TranslatorContext.DEFAULT); + } /** * Given a label key (which will eventually, at runtime, be dereferenced to a @@ -142,10 +160,16 @@ public interface MarkupGenerator { * @param key the label key to be dereferenced. * @param quantity a numeric quantity used to decide if the label needs to be * pluralized. + * @param translatorContext additional context information provided by the template translator. * @return the template code that renders the label in the target template * language. */ - Markup renderLabelFromKey(final StringExpression key, final NumericExpression quantity); + Markup renderLabelFromKey(final StringExpression key, final NumericExpression quantity, TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderLabelFromKey(final StringExpression key, final NumericExpression quantity) { + return this.renderLabelFromKey(key, quantity, TranslatorContext.DEFAULT); + } /** * Given an expression (which will eventually, at runtime, be evaluated to a @@ -155,10 +179,16 @@ public interface MarkupGenerator { * this label in the target template language. * * @param expression the expression that returns the label key. + * @param translatorContext additional context information provided by the template translator. * @return the template code that renders the label in the target template * language. */ - Markup renderLabelFromExpression(final Expression expression); + Markup renderLabelFromExpression(final Expression expression, TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderLabelFromExpression(final Expression expression) { + return this.renderLabelFromExpression(expression, TranslatorContext.DEFAULT); + } /** * Given an expression (which will eventually, at runtime, be evaluated to a @@ -170,10 +200,16 @@ public interface MarkupGenerator { * @param expression the expression that returns the label key. * @param quantity a numeric quantity used to decide if the label needs to be * pluralized. + * @param translatorContext additional context information provided by the template translator. * @return the template code that renders the label in the target template * language. */ - Markup renderLabelFromExpression(final Expression expression, final NumericExpression quantity); + Markup renderLabelFromExpression(final Expression expression, final NumericExpression quantity, TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderLabelFromExpression(final Expression expression, final NumericExpression quantity) { + return this.renderLabelFromExpression(expression, quantity, TranslatorContext.DEFAULT); + } /** * Given a string of free text, this method returns the template code that adds @@ -183,9 +219,27 @@ public interface MarkupGenerator { * {@link #escapeSpecialCharacters(String)} and then pass the escaped text to this method. * * @param freeText the free text to be rendered. + * @param translatorContext additional context information provided by the template translator. * @return the template code that adds this text in the target template. */ - Markup renderFreeText(final String freeText); + Markup renderFreeText(final String freeText, TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderFreeText(final String freeText) { + return this.renderFreeText(freeText, TranslatorContext.DEFAULT); + } + + /** + * Given a label and a URL, this method returns the template code that renders + * a hyperlink in the target template language. + * + * @param label the label to be displayed for the hyperlink. + * @param url the URL to be linked to. + * @param translatorContext additional context information provided by the template translator. + * @return the template code that renders the hyperlink in the target template language. + */ + Markup renderHyperlink(final Markup label, final StringExpression url, TranslatorContext translatorContext); + /** * Returns the markup that represents a line break in the target template. @@ -196,7 +250,12 @@ public interface MarkupGenerator { * * @return the markup that represents a line break in the target template. */ - Markup renderLineBreak(); + Markup renderLineBreak(TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderLineBreak() { + return this.renderLineBreak(TranslatorContext.DEFAULT); + } /** * Given a fragment name (identifier) and some pre-rendered content, this method @@ -205,7 +264,7 @@ public interface MarkupGenerator { * * @deprecated This method is deprecated and will be removed in future versions. * Use {@link #composeFragmentDefinition(String, String, Set, Markup, Markup, Set)} instead. * We are keeping the method temporarily to prevent build errors. - * This method is being deprecated as of version 2.0.0-alpha.4 and + * This method is being deprecated as of version 2.0.0-alpha.6 and * will be removed before version 2.0.0 is released * The default implementation provided here only throws * UnsupportedOperationException to prevent accidental use of this @@ -217,7 +276,7 @@ public interface MarkupGenerator { * @param parameters the parameters of the fragment. * @return the code that encapsulates the fragment in the target template. */ - @Deprecated(since = "2.0.0-alpha.4", forRemoval = true) + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default Markup composeFragmentDefinition(final String name, String number, Markup content, Set parameters) { throw new UnsupportedOperationException( @@ -235,34 +294,38 @@ default Markup composeFragmentDefinition(final String name, String number, Marku * @param content the content of the fragment. * @param children the children of the fragment. * @param parameters the parameters of the fragment. + * @param translatorContext additional context information provided by the template translator. * @return the code that encapsulates the fragment in the target template. */ Markup composeFragmentDefinition(final String name, String number, Set conditionals, Markup content, Markup children, - Set parameters); + Set parameters, TranslatorContext translatorContext); + + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup composeFragmentDefinition(final String name, String number, Set conditionals, + Markup content, Markup children, + Set parameters) { + return composeFragmentDefinition(name, number, conditionals, content, children, parameters, + TranslatorContext.DEFAULT); + } + /** * Given a fragment name (identifier), and an evaluation context, this method * returns the code that invokes the fragment. - * * @deprecated This method is deprecated and will be removed in future versions. - * Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead. - * We are keeping the method temporarily to prevent build errors. - * This method is being deprecated as of version 2.0.0-alpha.4 and - * will be removed before version 2.0.0 is released. - * The default implementation provided here only throws - * UnsupportedOperationException to prevent accidental use of this method. - * . The EfxTemplateTranslator will not call this method. - * + * + * The EfxTemplateTranslator will call this method to generate the + * fragment invocation code and then will pass the generated Markup to the + * {@link #renderContextLoop(PathExpression, Markup, Set)}. + * + * In EFX to XSLT translation, this method would generate the xsl:call-template + * that will invoke the fragment. + * * @param name the name of the fragment. - * @param context the context of the fragment. - * @param variables the variables of the fragment. + * @param arguments the arguments of the fragment. + * @param translatorContext additional context information provided by the template translator. * @return the code that invokes (uses) the fragment. */ - @Deprecated(since = "2.0.0-alpha.4", forRemoval = true) - default Markup renderFragmentInvocation(final String name, final PathExpression context, - final Set> variables) { - throw new UnsupportedOperationException( - "This method is deprecated and will be removed in future versions. Use renderFragmentInvocation(String, Set) instead."); - } + Markup renderFragmentInvocation(final String name, final Set arguments, TranslatorContext translatorContext); /** * Given a fragment name (identifier), and an evaluation context, this method @@ -275,16 +338,48 @@ default Markup renderFragmentInvocation(final String name, final PathExpression * In EFX to XSLT translation, this method would generate the xsl:call-template * that will invoke the fragment. * + * @deprecated This method is deprecated and will be removed in future versions. + * Use {@link #renderFragmentInvocation(String, Set, TranslatorContext)} + * instead. + * * @param name the name of the fragment. * @param arguments the arguments of the fragment. * @return the code that invokes (uses) the fragment. */ - Markup renderFragmentInvocation(final String name, final Set arguments); + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default public Markup renderFragmentInvocation(final String name, final Set arguments) { + return this.renderFragmentInvocation(name, arguments, TranslatorContext.DEFAULT); + } + + /** + * Given a fragment name (identifier), and an evaluation context, this method + * returns the code that invokes the fragment. + * @deprecated This method is deprecated and will be removed in future versions. + * Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead. + * We are keeping the method temporarily to prevent build errors. + * This method is being deprecated as of version 2.0.0-alpha.6 and + * will be removed before version 2.0.0 is released. + * The default implementation provided here only throws + * UnsupportedOperationException to prevent accidental use of this method. + * . The EfxTemplateTranslator will not call this method. + * + * @param name the name of the fragment. + * @param context the context of the fragment. + * @param variables the variables of the fragment. + * @return the code that invokes (uses) the fragment. + */ + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default Markup renderFragmentInvocation(final String name, final PathExpression context, + final Set> variables) { + throw new UnsupportedOperationException( + "This method is deprecated and will be removed in future versions. Use renderFragmentInvocation(String, Set) instead."); + } + /** * Given an evaluation context, and some pre-rendered content, this method returns the code that * iterates over the context and repeats the content for each item in the context. - * As of version 2.0.0-alpha.4, the method composeFragmentInvocation(String, Set) + * As of version 2.0.0-alpha.6, the method composeFragmentInvocation(String, Set) * has been split into two methods: * 1. {@link #renderFragmentInvocation(String, Set)} * for rendering the fragment invocation itself, which is the used by the diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TemplateSection.java b/src/main/java/eu/europa/ted/efx/interfaces/TemplateSection.java new file mode 100644 index 0000000..8a38ed0 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/interfaces/TemplateSection.java @@ -0,0 +1,13 @@ +package eu.europa.ted.efx.interfaces; + +/** + * Enum representing different sections of a template. + * + * Used in the {@link TranslatorContext} to communicate the section of the template + * being processed to the {@link MarkupGenerator}. + */ +public enum TemplateSection { + DEFAULT, + SUMMARY, + NAVIGATION +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java new file mode 100644 index 0000000..b790542 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java @@ -0,0 +1,36 @@ +package eu.europa.ted.efx.interfaces; + +/** + * Used to pass context information from the translator to the {@link MarkupGenerator}. + */ +public class TranslatorContext { + + TemplateSection currentSection; + + /** + * Gets the current section of the template being processed. + * + * @return the current section + */ + public TemplateSection getCurrentSection() { + return currentSection; + } + + public void setCurrentSection(TemplateSection currentSection) { + this.currentSection = currentSection; + } + + /** + * Default instance of {@link TranslatorContext} with the section set to {@link TemplateSection#DEFAULT}. + * This is used throughout the code for EFX-1 processing where only the default section is relevant. + */ + final public static TranslatorContext DEFAULT = new TranslatorContext(); + + public TranslatorContext() { + this(TemplateSection.DEFAULT); + } + + public TranslatorContext(TemplateSection currentSection) { + this.currentSection = currentSection; + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java index d664e31..3935793 100644 --- a/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java +++ b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java @@ -14,6 +14,7 @@ import eu.europa.ted.efx.interfaces.Argument; import eu.europa.ted.efx.interfaces.MarkupGenerator; import eu.europa.ted.efx.interfaces.Parameter; +import eu.europa.ted.efx.interfaces.TranslatorContext; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.variables.ParsedArgument; import eu.europa.ted.efx.model.variables.ParsedArguments; @@ -34,9 +35,9 @@ public class ContentBlock { protected final ParsedParameters parameters; protected final ParsedArguments arguments; - private ContentBlock() { + private ContentBlock(String id) { this.parent = null; - this.id = "block"; + this.id = id; this.indentationLevel = -1; this.conditionals = new Conditionals(); this.content = new Markup(""); @@ -66,8 +67,8 @@ public ContentBlock(final ContentBlock parent, final String id, final int number new ParsedArguments(variables)); } - public static ContentBlock newRootBlock() { - return new ContentBlock(); + public static ContentBlock newRootBlock(String id) { + return new ContentBlock(id); } // #region Add Children @@ -233,29 +234,29 @@ protected Set getAllArguments() { // #region Render ----------------------------------------------------------- - public Markup renderChildren(MarkupGenerator markupGenerator) { + public Markup renderChildren(MarkupGenerator markupGenerator, TranslatorContext translatorContext) { StringBuilder stringBuilder = new StringBuilder(); for (ContentBlock child : this.children) { - stringBuilder.append('\n').append(child.renderInvocation(markupGenerator).script); + stringBuilder.append('\n').append(child.renderInvocation(markupGenerator, translatorContext).script); } return new Markup(stringBuilder.toString()); } - public List renderDefinition(MarkupGenerator markupGenerator) { + public List renderDefinition(MarkupGenerator markupGenerator, TranslatorContext translatorContext) { Set params = this.getAllParameters().stream() .map(param -> new Parameter.Impl(param.name, markupGenerator.getEfxDataTypeEquivalent(param.dataType))) .collect(Collectors.toCollection(LinkedHashSet::new)); List templates = new ArrayList<>(); templates.add(markupGenerator.composeFragmentDefinition(this.id, this.getOutlineNumber(), this.conditionals.stream().collect(Collectors.toCollection(LinkedHashSet::new)), - this.content, this.renderChildren(markupGenerator), params)); + this.content, this.renderChildren(markupGenerator, translatorContext), params, translatorContext)); for (ContentBlock child : this.children) { - templates.addAll(child.renderDefinition(markupGenerator)); + templates.addAll(child.renderDefinition(markupGenerator, translatorContext)); } return templates; } - public Markup renderInvocation(MarkupGenerator markupGenerator) { + public Markup renderInvocation(MarkupGenerator markupGenerator, TranslatorContext translatorContext) { Set args = new LinkedHashSet<>(); if (this.parent != null) { args.addAll(parent.getAllArguments().stream() @@ -264,7 +265,7 @@ public Markup renderInvocation(MarkupGenerator markupGenerator) { } args.addAll(this.getOwnArguments().stream().map(a -> new Argument.Impl(a.name, markupGenerator.getEfxDataTypeEquivalent(a.dataType), a.value)) .collect(Collectors.toCollection(LinkedHashSet::new))); - var invocation = markupGenerator.renderFragmentInvocation(this.id, args); + var invocation = markupGenerator.renderFragmentInvocation(this.id, args, translatorContext); return markupGenerator.renderContextLoop(this.context.relativePath(), invocation, args); } diff --git a/src/main/java/eu/europa/ted/efx/model/templates/TemplateInvocation.java b/src/main/java/eu/europa/ted/efx/model/templates/TemplateInvocation.java index 9eeb75b..4907aa4 100644 --- a/src/main/java/eu/europa/ted/efx/model/templates/TemplateInvocation.java +++ b/src/main/java/eu/europa/ted/efx/model/templates/TemplateInvocation.java @@ -8,6 +8,7 @@ import eu.europa.ted.efx.interfaces.Argument; import eu.europa.ted.efx.interfaces.MarkupGenerator; +import eu.europa.ted.efx.interfaces.TranslatorContext; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.variables.Variables; @@ -26,7 +27,7 @@ public TemplateInvocation(final ContentBlock parent, final TemplateDefinition te * Template invocations do not have own content or child content to render. */ @Override - public List renderDefinition(MarkupGenerator markupGenerator) { + public List renderDefinition(MarkupGenerator markupGenerator, TranslatorContext translatorContext) { return new ArrayList() { }; } @@ -36,10 +37,10 @@ public List renderDefinition(MarkupGenerator markupGenerator) { * should net have access to local variables. */ @Override - public Markup renderInvocation(MarkupGenerator markupGenerator) { + public Markup renderInvocation(MarkupGenerator markupGenerator, TranslatorContext translatorContext) { Set arguments = this.getOwnArguments().stream().map(a -> new Argument.Impl(a.name, markupGenerator.getEfxDataTypeEquivalent(a.dataType), a.value)) .collect(Collectors.toCollection(LinkedHashSet::new)); - var content = markupGenerator.renderFragmentInvocation(this.id, arguments); + var content = markupGenerator.renderFragmentInvocation(this.id, arguments, translatorContext); return markupGenerator.renderContextLoop(this.context.relativePath(), content, arguments); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java index dde510e..ad2c0bc 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -10,8 +10,8 @@ import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; @@ -190,10 +190,6 @@ protected static String getFieldId(FieldReferenceContext ctx) { return getFieldId(ctx.absoluteFieldReference()); } - if (ctx.fieldReferenceWithFieldContextOverride() != null) { - return getFieldId(ctx.fieldReferenceWithFieldContextOverride().fieldReferenceWithPredicate()); - } - if (ctx.fieldReferenceInOtherNotice() != null) { return getFieldId(ctx.fieldReferenceInOtherNotice()); } diff --git a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java index 6f1f86b..9cf4481 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -22,6 +22,7 @@ import eu.europa.ted.efx.interfaces.MarkupGenerator; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; +import eu.europa.ted.efx.interfaces.TranslatorContext; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; @@ -97,7 +98,7 @@ private enum Indent { */ MarkupGenerator markup; - final ContentBlock rootBlock = ContentBlock.newRootBlock(); + final ContentBlock rootBlock = ContentBlock.newRootBlock("block"); /** * The block stack is used to keep track of the indentation of template lines and adjust the EFX @@ -201,8 +202,8 @@ public void exitTemplateFile(TemplateFileContext ctx) { List templateCalls = new ArrayList<>(); List templates = new ArrayList<>(); for (ContentBlock block : this.rootBlock.getChildren()) { - templateCalls.add(block.renderInvocation(markup)); - templates.addAll(block.renderDefinition(markup)); + templateCalls.add(block.renderInvocation(markup, TranslatorContext.DEFAULT)); + templates.addAll(block.renderDefinition(markup, TranslatorContext.DEFAULT)); } Markup file = this.markup.composeOutputFile(templateCalls, templates); this.stack.push(file); @@ -217,7 +218,7 @@ public void exitTextTemplate(TextTemplateContext ctx) { Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); String text = ctx.textBlock() != null ? ctx.textBlock().getText() : ""; - this.stack.push(this.markup.renderFreeText(this.markup.escapeSpecialCharacters(text)).join(template)); + this.stack.push(this.markup.renderFreeText(this.markup.escapeSpecialCharacters(text), TranslatorContext.DEFAULT).join(template)); } @Override @@ -233,7 +234,7 @@ public void exitExpressionTemplate(ExpressionTemplateContext ctx) { Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); Expression expression = this.stack.pop(Expression.class); - this.stack.push(this.markup.renderVariableExpression(expression).join(template)); + this.stack.push(this.markup.renderVariableExpression(expression, TranslatorContext.DEFAULT).join(template)); } @@ -280,7 +281,7 @@ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, Strin this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(assetType, this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), assetId)))); + this.script.getStringLiteralFromUnquotedString("|"), assetId)), TranslatorContext.DEFAULT)); } /** @@ -312,7 +313,7 @@ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, Strin this.script.getStringLiteralFromUnquotedString("|"), new StringExpression(loopVariable.referenceExpression.getScript()))), StringSequenceExpression.class), - StringSequenceExpression.class))); + StringSequenceExpression.class), TranslatorContext.DEFAULT)); } @@ -324,7 +325,7 @@ public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_BT), this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), assetId)))); + this.script.getStringLiteralFromUnquotedString("|"), assetId)), TranslatorContext.DEFAULT)); } @Override @@ -340,7 +341,7 @@ public void exitShorthandFieldLabelReference(ShorthandFieldLabelReferenceContext List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), this.script.getStringLiteralFromUnquotedString("|"), labelType, this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(fieldId))))); + this.script.getStringLiteralFromUnquotedString(fieldId))), TranslatorContext.DEFAULT)); } } @@ -378,7 +379,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(fieldId))), StringSequenceExpression.class), - StringSequenceExpression.class))); + StringSequenceExpression.class), TranslatorContext.DEFAULT)); break; case "code": case "internal-code": @@ -398,7 +399,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.getStringLiteralFromUnquotedString("."), new StringExpression(loopVariable.referenceExpression.getScript()))), StringSequenceExpression.class), - StringSequenceExpression.class))); + StringSequenceExpression.class), TranslatorContext.DEFAULT)); break; default: throw InvalidUsageException.shorthandRequiresCodeOrIndicator(fieldId, fieldType); @@ -425,7 +426,7 @@ public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromCo this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(labelType), this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), TranslatorContext.DEFAULT)); } } else if (this.efxContext.isNodeContext()) { this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( @@ -433,7 +434,7 @@ public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromCo this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(labelType), this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), TranslatorContext.DEFAULT)); } } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java index f299a35..ac1586d 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -21,13 +21,15 @@ import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.efx.exceptions.InvalidUsageException; import eu.europa.ted.efx.exceptions.InvalidIndentationException; +import eu.europa.ted.efx.exceptions.InvalidUsageException; import eu.europa.ted.efx.interfaces.Argument; import eu.europa.ted.efx.interfaces.EfxTemplateTranslator; import eu.europa.ted.efx.interfaces.MarkupGenerator; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; +import eu.europa.ted.efx.interfaces.TemplateSection; +import eu.europa.ted.efx.interfaces.TranslatorContext; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; @@ -50,21 +52,20 @@ import eu.europa.ted.efx.model.templates.Conditionals; import eu.europa.ted.efx.model.templates.ContentBlock; import eu.europa.ted.efx.model.templates.ContentBlockStack; +import eu.europa.ted.efx.model.templates.Markup; import eu.europa.ted.efx.model.templates.TemplateDefinition; import eu.europa.ted.efx.model.templates.TemplateInvocation; -import eu.europa.ted.efx.model.templates.Markup; import eu.europa.ted.efx.model.types.EfxDataType; import eu.europa.ted.efx.model.types.FieldTypes; import eu.europa.ted.efx.model.variables.Dictionary; import eu.europa.ted.efx.model.variables.Function; -import eu.europa.ted.efx.model.variables.StrictArguments; import eu.europa.ted.efx.model.variables.Identifier; import eu.europa.ted.efx.model.variables.ParsedParameter; import eu.europa.ted.efx.model.variables.ParsedParameters; +import eu.europa.ted.efx.model.variables.StrictArguments; import eu.europa.ted.efx.model.variables.Template; import eu.europa.ted.efx.model.variables.Variable; import eu.europa.ted.efx.model.variables.Variables; -import eu.europa.ted.efx.sdk2.EfxExpressionTranslatorV2.ExpressionPreprocessor; import eu.europa.ted.efx.sdk2.EfxParser.AssetIdContext; import eu.europa.ted.efx.sdk2.EfxParser.AssetTypeContext; import eu.europa.ted.efx.sdk2.EfxParser.BooleanFunctionDeclarationContext; @@ -88,6 +89,7 @@ import eu.europa.ted.efx.sdk2.EfxParser.InvokeTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.LabelTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.LabelTypeContext; +import eu.europa.ted.efx.sdk2.EfxParser.NavigationSectionContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericParameterDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericVariableInitializerContext; @@ -103,8 +105,9 @@ import eu.europa.ted.efx.sdk2.EfxParser.StringFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.StringParameterDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.StringVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateDefinitionContext; +import eu.europa.ted.efx.sdk2.EfxParser.SummarySectionContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.TemplateDefinitionContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateFileContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateLineContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateVariableDeclarationContext; @@ -114,7 +117,6 @@ import eu.europa.ted.efx.sdk2.EfxParser.TimeVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.WhenDisplayTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.WhenInvokeTemplateContext; -import eu.europa.ted.efx.sdk2.EfxTemplateTranslatorV2.TemplatePreprocessor; /** * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV2} to provide additional @@ -154,7 +156,12 @@ private enum Indent { */ MarkupGenerator markup; - final ContentBlock rootBlock = ContentBlock.newRootBlock(); + TranslatorContext translatorContext = TranslatorContext.DEFAULT; + + final ContentBlock bodySectionRoot = ContentBlock.newRootBlock("body"); + final ContentBlock summarySectionRoot = ContentBlock.newRootBlock("summary"); + final ContentBlock navigationSectionRoot = ContentBlock.newRootBlock("nav"); + ContentBlock rootBlock = bodySectionRoot; /** * The block stack is used to keep track of the indentation of template lines and adjust the EFX @@ -368,16 +375,24 @@ public void exitTemplateFile(TemplateFileContext ctx) { } } - List templateCalls = new ArrayList<>(); - List templates = new ArrayList<>(); - for (ContentBlock block : this.rootBlock.getChildren()) { + List fragments = new ArrayList<>(); + List mainSection = renderSection(TemplateSection.DEFAULT, bodySectionRoot, fragments); + List summarySection = renderSection(TemplateSection.SUMMARY, summarySectionRoot, fragments); + List navigationSection = renderSection(TemplateSection.NAVIGATION, navigationSectionRoot, fragments); + Markup file = this.markup.composeOutputFile(globals, mainSection, summarySection, navigationSection, fragments); + this.stack.push(file); + } + + private List renderSection(TemplateSection section, ContentBlock sectionRoot, List templates) { + List markupList = new ArrayList<>(); + this.translatorContext.setCurrentSection(section); + for (ContentBlock block : sectionRoot.getChildren()) { if (!(block instanceof TemplateDefinition)) { - templateCalls.add(block.renderInvocation(markup)); + markupList.add(block.renderInvocation(markup, this.translatorContext)); } - templates.addAll(block.renderDefinition(markup)); + templates.addAll(block.renderDefinition(markup, this.translatorContext)); } - Markup file = this.markup.composeOutputFile(globals, templateCalls, templates); - this.stack.push(file); + return markupList; } // #endregion Template File ------------------------------------------------- @@ -386,26 +401,23 @@ public void exitTemplateFile(TemplateFileContext ctx) { @Override public void exitTextTemplate(TextTemplateContext ctx) { - Markup template = - ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); + Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); String text = ctx.textBlock() != null ? ctx.textBlock().getText() : ""; - this.stack.push(this.markup.renderFreeText(this.markup.escapeSpecialCharacters(text)).join(template)); + this.stack.push(this.markup.renderFreeText(this.markup.escapeSpecialCharacters(text), this.translatorContext).join(template)); } @Override public void exitLabelTemplate(LabelTemplateContext ctx) { - Markup template = - ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); + Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); Markup label = ctx.labelBlock() != null ? this.stack.pop(Markup.class) : Markup.empty(); this.stack.push(label.join(template)); } @Override public void exitExpressionTemplate(ExpressionTemplateContext ctx) { - Markup template = - ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); + Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); Expression expression = this.stack.pop(Expression.class); - this.stack.push(this.markup.renderVariableExpression(expression).join(template)); + this.stack.push(this.markup.renderVariableExpression(expression, this.translatorContext).join(template)); } // #region New in EFX-2: Secondary templates -------------------------------- @@ -413,7 +425,7 @@ public void exitExpressionTemplate(ExpressionTemplateContext ctx) { @Override public void exitSecondaryTemplate(SecondaryTemplateContext ctx) { Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); - this.stack.push(this.markup.renderLineBreak().join(template)); + this.stack.push(this.markup.renderLineBreak(this.translatorContext).join(template)); } // #endregion New in EFX-2: Secondary templates ----------------------------- @@ -467,7 +479,7 @@ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, Strin this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(assetType, this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), assetId)), quantity)); + this.script.getStringLiteralFromUnquotedString("|"), assetId)), quantity, this.translatorContext)); } /** @@ -499,7 +511,7 @@ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, Strin this.script.getStringLiteralFromUnquotedString("|"), new StringExpression(loopVariable.referenceExpression.getScript()))), StringSequenceExpression.class), - StringSequenceExpression.class))); + StringSequenceExpression.class), this.translatorContext)); } @@ -512,7 +524,7 @@ public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_BT), this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), assetId)), quantity)); + this.script.getStringLiteralFromUnquotedString("|"), assetId)), quantity, this.translatorContext)); } @Override @@ -529,7 +541,7 @@ public void exitShorthandFieldLabelReference(ShorthandFieldLabelReferenceContext List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), this.script.getStringLiteralFromUnquotedString("|"), labelType, this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(fieldId))), quantity)); + this.script.getStringLiteralFromUnquotedString(fieldId))), quantity, this.translatorContext)); } } @@ -570,7 +582,7 @@ private void shorthandIndirectLabelReference(final String fieldId, final Numeric this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(fieldId))), StringSequenceExpression.class), - StringSequenceExpression.class), quantity)); + StringSequenceExpression.class), quantity, this.translatorContext)); break; case "code": case "internal-code": @@ -591,7 +603,7 @@ private void shorthandIndirectLabelReference(final String fieldId, final Numeric new StringExpression(loopVariable.referenceExpression.getScript()))), StringSequenceExpression.class), StringSequenceExpression.class), - quantity)); + quantity, this.translatorContext)); break; default: throw InvalidUsageException.shorthandRequiresCodeOrIndicator(fieldId, fieldType); @@ -622,7 +634,7 @@ public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromCo this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(labelType), this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), quantity)); + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), quantity, this.translatorContext)); } } else if (this.efxContext.isNodeContext()) { this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( @@ -630,7 +642,7 @@ public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromCo this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(labelType), this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), quantity)); + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), quantity, this.translatorContext)); } } @@ -675,7 +687,7 @@ public void exitAssetId(AssetIdContext ctx) { public void exitComputedLabelReference(ComputedLabelReferenceContext ctx) { NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : NumericExpression.empty(); StringExpression expression = this.stack.pop(StringExpression.class); - this.stack.push(this.markup.renderLabelFromExpression(expression, quantity)); + this.stack.push(this.markup.renderLabelFromExpression(expression, quantity, this.translatorContext)); } @Override @@ -885,7 +897,7 @@ public void exitInvokeTemplate(InvokeTemplateContext ctx) { final Set args = arguments.stream() .map(a -> new Argument.Impl(a.name, this.markup.getEfxDataTypeEquivalent(a.dataType), a.value)) .collect(Collectors.toCollection(LinkedHashSet::new)); - this.stack.push(this.markup.renderFragmentInvocation(templateName, args)); + this.stack.push(this.markup.renderFragmentInvocation(templateName, args, this.translatorContext)); } // #endregion Conditional template blocks --------------------------------- @@ -993,6 +1005,36 @@ private void exitParameterDeclaration(String parameterName, Class dataType, String variableName, Expression initialiser) { @@ -49,17 +51,17 @@ public Markup renderFunctionDeclaration(Class type, Strin } @Override - public Markup renderVariableExpression(Expression valueReference) { + public Markup renderVariableExpression(Expression valueReference, TranslatorContext translatorContext) { return new Markup(String.format("eval(%s)", valueReference.getScript())); } @Override - public Markup renderLabelFromKey(StringExpression key) { - return this.renderLabelFromKey(key, NumericExpression.empty()); + public Markup renderLabelFromKey(StringExpression key, TranslatorContext translatorContext) { + return this.renderLabelFromKey(key, NumericExpression.empty(), translatorContext); } @Override - public Markup renderLabelFromKey(StringExpression key, NumericExpression quantity) { + public Markup renderLabelFromKey(StringExpression key, NumericExpression quantity, TranslatorContext translatorContext) { if (quantity.isEmpty()) { return new Markup(String.format("label(%s)", key.getScript())); } @@ -67,12 +69,12 @@ public Markup renderLabelFromKey(StringExpression key, NumericExpression quantit } @Override - public Markup renderLabelFromExpression(Expression expression) { - return this.renderLabelFromExpression(expression, NumericExpression.empty()); + public Markup renderLabelFromExpression(Expression expression, TranslatorContext translatorContext) { + return this.renderLabelFromExpression(expression, NumericExpression.empty(), TranslatorContext.DEFAULT); } @Override - public Markup renderLabelFromExpression(Expression expression, NumericExpression quantity) { + public Markup renderLabelFromExpression(Expression expression, NumericExpression quantity, TranslatorContext translatorContext) { if (quantity.isEmpty()) { return new Markup(String.format("label(%s)", expression.getScript())); } @@ -80,10 +82,15 @@ public Markup renderLabelFromExpression(Expression expression, NumericExpression } @Override - public Markup renderFreeText(String freeText) { + public Markup renderFreeText(String freeText, TranslatorContext translatorContext) { return new Markup(String.format("text('%s')", freeText)); } + @Override + public Markup renderHyperlink(Markup label, StringExpression url, TranslatorContext translatorContext) { + return new Markup(String.format("hyperlink(%s, %s)", label.script, url.getScript())); + } + @Override public String escapeSpecialCharacters(String text) { if (text == null) { @@ -100,13 +107,13 @@ public String escapeSpecialCharacters(String text) { } @Override - public Markup renderLineBreak() { + public Markup renderLineBreak(TranslatorContext translatorContext) { return new Markup("line-break()"); } @Override public Markup composeFragmentDefinition(String name, String number, Set conditionals, - Markup content, Markup children, Set parameters) { + Markup content, Markup children, Set parameters, TranslatorContext translatorContext) { String contents = ""; if (conditionals != null && !conditionals.isEmpty()) { @@ -144,7 +151,7 @@ public Markup renderContextLoop(PathExpression context, final Markup content, } @Override - public Markup renderFragmentInvocation(String name, Set arguments) { + public Markup renderFragmentInvocation(String name, Set arguments, TranslatorContext translatorContext) { return new Markup(String.format("call(%s(%s))", name, arguments.stream() .map(arg -> String.format("%s:%s=%s", arg.getType(), arg.getName(), arg.getValue())) @@ -152,16 +159,29 @@ public Markup renderFragmentInvocation(String name, Set arguments) { } @Override - public Markup composeOutputFile(List body, List templates) { - return this.composeOutputFile(new ArrayList(), body, templates); - } + public Markup composeOutputFile(List globals, List body, List summary, List navigation, final List fragments) { + var renderedGlobals = globals.size() > 0 + ? String.format("GLOBALS:%2$s%1$s",globals.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') + : ""; + var renderedTemplates = fragments.size() > 0 + ? String.format("TEMPLATES:%2$s%1$s", fragments.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') + : ""; + var renderedMain = body.size() > 0 + ? String.format("MAIN:%2$s%1$s", body.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') + : ""; + var renderedSummary = summary.size() > 0 + ? String.format("SUMMARY:%2$s%1$s", summary.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') + : ""; + var renderedNavigation = navigation.size() > 0 + ? String.format("NAV:%2$s%1$s", navigation.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') + : ""; - @Override - public Markup composeOutputFile(List globals, List body, List templates) { - return new Markup(String.format("%1$s%4$s%2$s%4$s%3$s", - globals.stream().map(t -> t.script).collect(Collectors.joining("\n")), - templates.stream().map(t -> t.script).collect(Collectors.joining("\n")), - body.stream().map(t -> t.script).collect(Collectors.joining("\n")), "\n").trim()); + return new Markup(String.format("%1$s%6$s%2$s%6$s%3$s%6$s%4$s%6$s%5$s", + renderedGlobals, + renderedTemplates, + renderedMain, + renderedSummary, + renderedNavigation, "\n").trim()); } @Override @@ -174,5 +194,4 @@ public Markup renderDictionaryDeclaration(final String name, final PathExpressio final StringExpression key) { return new Markup(String.format("let %s index %s by %s;", name, match.getScript(), key.getScript())); } - } diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java index 02153f6..cf92627 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java @@ -17,7 +17,7 @@ protected String getSdkVersion() { @Test void testTemplateLineNoIdent() { assertEquals( - "let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { text('foo') }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} foo")); } @@ -26,11 +26,13 @@ void testTemplateLineNoIdent() { */ @Test void testTemplateLineOutline_Autogenerated() { - assertEquals(lines("let block01() -> { #1: text('foo')", + assertEquals(lines("TEMPLATES:", + "let block01() -> { #1: text('foo')", "for-each(../..).call(block0101()) }", "let block0101() -> { #1.1: text('bar')", "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate(lines("{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -40,11 +42,13 @@ void testTemplateLineOutline_Autogenerated() { */ @Test void testTemplateLineOutline_Explicit() { - assertEquals(lines("let block01() -> { #2: text('foo')", + assertEquals(lines("TEMPLATES:", + "let block01() -> { #2: text('foo')", "for-each(../..).call(block0101()) }", "let block0101() -> { #2.3: text('bar')", "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate( lines("2{BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); @@ -56,11 +60,13 @@ void testTemplateLineOutline_Explicit() { */ @Test void testTemplateLineOutline_Mixed() { - assertEquals(lines("let block01() -> { #2: text('foo')", + assertEquals(lines("TEMPLATES:", + "let block01() -> { #2: text('foo')", "for-each(../..).call(block0101()) }", "let block0101() -> { #2.1: text('bar')", "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -71,11 +77,13 @@ void testTemplateLineOutline_Mixed() { */ @Test void testTemplateLineOutline_Suppressed() { - assertEquals(lines("let block01() -> { #2: text('foo')", + assertEquals(lines("TEMPLATES:", + "let block01() -> { #2: text('foo')", "for-each(../..).call(block0101()) }", "let block0101() -> { text('bar')", "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate( lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); @@ -88,11 +96,12 @@ void testTemplateLineOutline_Suppressed() { @Test void testTemplateLineOutline_SuppressedAtParent() { // Outline is ignored if the line has no children - assertEquals(lines("let block01() -> { text('foo')", + assertEquals(lines("TEMPLATES:", "let block01() -> { text('foo')", "for-each(../..).call(block0101()) }", "let block0101() -> { #1: text('bar')", "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -105,8 +114,10 @@ void testTemplateLineFirstIndented() { @Test void testTemplateLineIdentTab() { assertEquals( - lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // + lines("TEMPLATES:", + "let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // "let block0101() -> { text('bar') }", // + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); } @@ -114,8 +125,10 @@ void testTemplateLineIdentTab() { @Test void testTemplateLineIdentSpaces() { assertEquals( - lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // + lines("TEMPLATES:", + "let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // "let block0101() -> { text('bar') }", // + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate(lines("{BT-00-Text} foo", " {BT-00-Text} bar"))); } @@ -137,9 +150,11 @@ void testTemplateLineIdentMixedSpaceThenTab() { @Test void testTemplateLineIdentLower() { assertEquals( - lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", + lines("TEMPLATES:", + "let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", "let block0101() -> { text('bar') }", "let block02() -> { text('code') }", + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())", "for-each(/*/PathNode/CodeField).call(block02())"), translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar", "{BT-00-Code} code"))); @@ -155,9 +170,11 @@ void testTemplateLineIdentUnexpected() { @Test void testTemplateLine_VariableScope() { assertEquals( - lines("let block01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // + lines("TEMPLATES:", + "let block01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // "for-each(.).call(block0101()) }", // "let block0101() -> { eval(for $x in ./normalize-space(text()) return $x) }", // + "MAIN:", "for-each(/*/PathNode/TextField).call(block01())"), // translateTemplate(lines("{BT-00-Text} ${for text:$x in BT-00-Text return $x}", " {BT-00-Text} ${for text:$x in BT-00-Text return $x}"))); @@ -169,35 +186,35 @@ void testTemplateLine_VariableScope() { @Test void testStandardLabelReference() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{field|name|BT-00-Text}")); } @Test void testStandardLabelReference_UsingLabelTypeAsAssetId() { assertEquals( - "let block01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{auxiliary|text|value}")); } @Test void testStandardLabelReference_WithAssetIdIterator() { assertEquals( - "let block01() -> { label(distinct-values(for $item in for $t in ./normalize-space(text()) return $t return concat('field', '|', 'name', '|', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in for $t in ./normalize-space(text()) return $t return concat('field', '|', 'name', '|', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{field|name|${for text:$t in BT-00-Text return $t}}")); } @Test void testShorthandBtLabelReference() { assertEquals( - "let block01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{name|BT-00}")); } @Test void testShorthandFieldLabelReference() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{name|BT-00-Text}")); } @@ -210,42 +227,42 @@ void testShorthandBtLabelReference_MissingLabelType() { @Test void testShorthandIndirectLabelReferenceForIndicator() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-Indicator}")); } @Test void testShorthandIndirectLabelReferenceForCode() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-Code}")); } @Test void testShorthandIndirectLabelReferenceForInternalCode() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameAttributeInContext() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())", translateTemplate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} #{BT-00-CodeAttribute}")); } @@ -264,14 +281,14 @@ void testShorthandIndirectLabelReferenceForAttribute() { @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndIndicatorField() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/IndicatorField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nMAIN:\nfor-each(/*/PathNode/IndicatorField).call(block01())", translateTemplate("{BT-00-Indicator} #{name}")); } @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndCodeField() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} #{name}")); } @@ -284,7 +301,7 @@ void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndTextField() { @Test void testShorthandLabelReferenceFromContext_WithOtherLabelType() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{name}")); } @@ -297,14 +314,14 @@ void testShorthandLabelReferenceFromContext_WithUnknownLabelType() { @Test void testShorthandLabelReferenceFromContext_WithNodeContext() { assertEquals( - "let block01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nMAIN:\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} #{name}")); } @Test void testShorthandIndirectLabelReferenceFromContextField() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} #value")); } @@ -318,14 +335,14 @@ void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() { @Test void testShorthandFieldValueReferenceFromContextField() { - assertEquals("let block01() -> { eval(./normalize-space(text())) }\nfor-each(/*/PathNode/CodeField).call(block01())", + assertEquals("TEMPLATES:\nlet block01() -> { eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} $value")); } @Test void testShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( - "let block01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' ')text('blah ')eval(./normalize-space(text()))text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet block01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' ')text('blah ')eval(./normalize-space(text()))text(' ')text('blah') }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} blah #value blah $value blah")); } @@ -340,24 +357,24 @@ void testShorthandFieldValueReferenceFromContextField_WithNodeContext() { @Test void testNestedExpression() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{field|name|${BT-00-Text}}")); } @Test void testEndOfLineComments() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nMAIN:\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); } @Test void testImplicitFormatting_Dates() { - assertEquals("let block01() -> { eval(for $item in PathNode/StartDateField/xs:date(text()) return format-date($item, '[D01]/[M01]/[Y0001]')) }\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} ${BT-00-StartDate}")); + assertEquals("TEMPLATES:\nlet block01() -> { eval(for $item in PathNode/StartDateField/xs:date(text()) return format-date($item, '[D01]/[M01]/[Y0001]')) }\nMAIN:\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} ${BT-00-StartDate}")); } @Test void testImplicitFormatting_Times() { - assertEquals("let block01() -> { eval(for $item in PathNode/StartTimeField/xs:time(text()) return format-time($item, '[H01]:[m01] [Z]')) }\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} ${BT-00-StartTime}")); + assertEquals("TEMPLATES:\nlet block01() -> { eval(for $item in PathNode/StartTimeField/xs:time(text()) return format-time($item, '[H01]:[m01] [Z]')) }\nMAIN:\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} ${BT-00-StartTime}")); } } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java index 9806c4c..4a2a5b5 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -22,9 +22,10 @@ protected String getSdkVersion() { void testTemplateDefinition_InvokeTemplate() { assertEquals( lines( - "let some-template(string:content) -> { text('Content: ')eval($content) }", - "let block02() -> { call(some-template(string:content='test')) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let some-template(string:content) -> { text('Content: ')eval($content) }", + "let body02() -> { call(some-template(string:content='test')) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:some-template(text:$content) display Content: ${$content};", "invoke some-template('test');"))); @@ -34,10 +35,11 @@ void testTemplateDefinition_InvokeTemplate() { void testTemplateDefinition_NestedInvokeTemplate() { assertEquals( lines( - "let other-template(string:value) -> { text('Value: ')eval($value) }", + "TEMPLATES:", "let other-template(string:value) -> { text('Value: ')eval($value) }", "let invoke-template(string:param) -> { call(other-template(string:value=$param)) }", - "let block03() -> { call(invoke-template(string:param='test')) }", - "for-each(/*).call(block03())"), + "let body03() -> { call(invoke-template(string:param='test')) }", + "MAIN:", + "for-each(/*).call(body03())"), translateTemplate(lines( "let template:other-template(text:$value) display Value: ${$value};", "let template:invoke-template(text:$param) invoke other-template($param);", @@ -48,9 +50,10 @@ void testTemplateDefinition_NestedInvokeTemplate() { void testTemplateDefinition_ChooseTemplate() { assertEquals( lines( - "let conditional-template(boolean:condition, string:value) -> { choose { when $condition: text('Condition met: ')eval($value), when $condition = false(): text('Condition not met'), otherwise: text('Unknown condition: ')eval($condition) } }", - "let block02() -> { call(conditional-template(boolean:condition=true(), string:value='test')) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let conditional-template(boolean:condition, string:value) -> { choose { when $condition: text('Condition met: ')eval($value), when $condition = false(): text('Condition not met'), otherwise: text('Unknown condition: ')eval($condition) } }", + "let body02() -> { call(conditional-template(boolean:condition=true(), string:value='test')) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:conditional-template(indicator:$condition, text:$value)", "when $condition display Condition met: ${$value}", @@ -63,9 +66,10 @@ void testTemplateDefinition_ChooseTemplate() { void testTemplateDefinition_ParameterValidation() { assertEquals( lines( - "let param-validation-template(string:param1, decimal:param2) -> { text('Valid parameters') }", - "let block02() -> { call(param-validation-template(string:param1='test', decimal:param2=42)) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let param-validation-template(string:param1, decimal:param2) -> { text('Valid parameters') }", + "let body02() -> { call(param-validation-template(string:param1='test', decimal:param2=42)) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:param-validation-template(text:$param1, number:$param2) display Valid parameters;", "invoke param-validation-template('test', 42);"))); @@ -79,9 +83,10 @@ void testTemplateDefinition_ParameterValidation() { void testTemplateDeclaration_NoParameters() { assertEquals( lines( - "let simple-template() -> { text('Hello World') }", - "let block02() -> { call(simple-template()) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let simple-template() -> { text('Hello World') }", + "let body02() -> { call(simple-template()) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:simple-template() display Hello World;", "invoke simple-template();"))); @@ -91,9 +96,11 @@ void testTemplateDeclaration_NoParameters() { void testTemplateDeclaration_SingleParameter() { assertEquals( lines( + "TEMPLATES:", "let greeting-template(string:name) -> { text('Hello ')eval($name) }", - "let block02() -> { call(greeting-template(string:name='World')) }", - "for-each(/*).call(block02())"), + "let body02() -> { call(greeting-template(string:name='World')) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:greeting-template(text:$name) display Hello ${$name};", "invoke greeting-template('World');"))); @@ -103,9 +110,10 @@ void testTemplateDeclaration_SingleParameter() { void testTemplateDeclaration_MultipleParameters() { assertEquals( lines( - "let complex-template(string:first, decimal:second, boolean:third) -> { text('Values: ')eval($first)text(', ')eval($second)text(', ')eval($third) }", - "let block02() -> { call(complex-template(string:first='test', decimal:second=42, boolean:third=true())) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let complex-template(string:first, decimal:second, boolean:third) -> { text('Values: ')eval($first)text(', ')eval($second)text(', ')eval($third) }", + "let body02() -> { call(complex-template(string:first='test', decimal:second=42, boolean:third=true())) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:complex-template(text:$first, number:$second, indicator:$third) display Values: ${$first}, ${$second}, ${$third};", "invoke complex-template('test', 42, TRUE);"))); @@ -115,9 +123,10 @@ void testTemplateDeclaration_MultipleParameters() { void testTemplateDeclaration_AllParameterTypes() { assertEquals( lines( - "let all-types-template(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('Params: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }", - "let block02() -> { call(all-types-template(string:str='text', decimal:num=123, boolean:bool=true(), date:dt=xs:date('2023-01-01'), time:tm=xs:time('12:00:00'), duration:dur=xs:dayTimeDuration('P1D'))) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let all-types-template(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('Params: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }", + "let body02() -> { call(all-types-template(string:str='text', decimal:num=123, boolean:bool=true(), date:dt=xs:date('2023-01-01'), time:tm=xs:time('12:00:00'), duration:dur=xs:dayTimeDuration('P1D'))) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:all-types-template(text:$str, number:$num, indicator:$bool, date:$dt, time:$tm, measure:$dur) display Params: ${$str}, ${$num}, ${$bool}, ${$dt}, ${$tm}, ${$dur};", "invoke all-types-template('text', 123, TRUE, date('2023-01-01'), time('12:00:00'), day-time-duration('P1D'));"))); @@ -127,9 +136,10 @@ void testTemplateDeclaration_AllParameterTypes() { void testTemplateDeclaration_SpecialCharactersInName() { assertEquals( lines( - "let template-with-dashes(string:param) -> { text('Template: ')eval($param) }", - "let block02() -> { call(template-with-dashes(string:param='test')) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let template-with-dashes(string:param) -> { text('Template: ')eval($param) }", + "let body02() -> { call(template-with-dashes(string:param='test')) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:template-with-dashes(text:$param) display Template: ${$param};", "invoke template-with-dashes('test');"))); @@ -142,42 +152,42 @@ void testTemplateDeclaration_SpecialCharactersInName() { @Test void testTemplateFragment_TextAndExpression() { assertEquals( - "let block01() -> { text('Value is: ')eval(./normalize-space(text())) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Value is: ')eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} Value is: ${BT-00-Text}")); } @Test void testTemplateFragment_TextAndLabel() { assertEquals( - "let block01() -> { text('Field: ')label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Field: ')label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} Field: #{field|name|BT-00-Text}")); } @Test void testTemplateFragment_MixedContent() { assertEquals( - "let block01() -> { text('Label: ')label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' Value: ')eval(./normalize-space(text()))text(' End') }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Label: ')label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' Value: ')eval(./normalize-space(text()))text(' End') }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} Label: #{field|name|BT-00-Text} Value: ${BT-00-Text} End")); } @Test void testTemplateFragment_OnlyExpression() { assertEquals( - "let block01() -> { eval(./normalize-space(text())) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} ${BT-00-Text}")); } @Test void testTemplateFragment_OnlyLabel() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{field|name|BT-00-Text}")); } @Test void testTemplateFragment_MultipleExpressions() { assertEquals( - "let block01() -> { eval(./number())text(' + ')eval(./number())text(' = ')eval(./number() + ./number()) }\nfor-each(/*/PathNode/NumberField).call(block01())", + "TEMPLATES:\nlet body01() -> { eval(./number())text(' + ')eval(./number())text(' = ')eval(./number() + ./number()) }\nMAIN:\nfor-each(/*/PathNode/NumberField).call(body01())", translateTemplate("{BT-00-Number} ${BT-00-Number} + ${BT-00-Number} = ${BT-00-Number + BT-00-Number}")); } @@ -186,56 +196,83 @@ void testTemplateFragment_MultipleExpressions() { @Test void testTextBlock_SimpleText() { assertEquals( - "let block01() -> { text('Simple text content') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Simple text content') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("display Simple text content;")); } @Test void testTextBlock_WithWhitespace() { assertEquals( - "let block01() -> { text('Text with spaces') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Text with spaces') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("display Text with spaces ;")); } @Test void testTextBlock_WithSpecialCharacters() { assertEquals( - "let block01() -> { text('Text with special chars: <>&"'') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Text with special chars: <>&"'') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("display Text with special chars: <>&\"';")); } @Test void testTextBlock_MultipleTextBlocks() { assertEquals( - "let block01() -> { text('First block ')eval('')text(' Second block') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { text('First block ')eval('')text(' Second block') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("display First block ${''} Second block;")); } @Test void testTextBlock_WithNewlines() { assertEquals( - "let block01() -> { text('Line 1')line-break()text('Line 2')line-break()text('Line 3') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Line 1')line-break()text('Line 2')line-break()text('Line 3') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("display Line 1 \\nLine 2\\n Line 3;")); } @Test void testTextBlock_WithEscapedCharacters() { assertEquals( - "let block01() -> { text('Text with quotes: "' and backslash: \\\\') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Text with quotes: "' and backslash: \\\\') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("display Text with quotes: \"' and backslash: \\\\;")); } // #endregion textBlock ----------------------------------------------------- + // #region linkedTextBlock -------------------------------------------------- + + @Test + void testLinkedTextBlock() { + assertEquals( + "TEMPLATES:\nlet body01() -> { text('here is a ')hyperlink(text('Link'), 'http://example.com')text('. How about it?') }\nMAIN:\nfor-each(/*).call(body01())", + translateTemplate("DISPLAY here is a Link@{'http://example.com'}. How about it?;")); + } + + @Test + void testLinkedLabelBlock() { + assertEquals( + "TEMPLATES:\nlet body01() -> { text('here is a linked label: ')hyperlink(label(concat('field', '|', 'name', '|', 'BT-00-Text')), 'http://example.com')text('. How about it?') }\nMAIN:\nfor-each(/*).call(body01())", + translateTemplate("DISPLAY here is a linked label: #{field|name|BT-00-Text}@{'http://example.com'}. How about it?;")); + } + + @Test + void testLinkedExpressionBlock() { + assertEquals( + "TEMPLATES:\nlet body01() -> { text('here is a ')hyperlink(eval('multi word link'), 'http://example.com')text('. How about it?') }\nMAIN:\nfor-each(/*).call(body01())", + translateTemplate("DISPLAY here is a ${'multi word link'}@{'http://example.com'}. How about it?;")); + } + + // #endregion linkedTextBlock ----------------------------------------------- + // #endregion templateFragment ---------------------------------------------- @Test void testTemplate_ComplexNesting() { assertEquals( lines( + "TEMPLATES:", "let complex-template(string:field) -> { text('name ')eval($field)text(' label ')label(concat('field', '|', 'name', '|', $field)) }", - "let block02() -> { call(complex-template(string:field='BT-00-Text')) }", - "for-each(/*).call(block02())"), + "let body02() -> { call(complex-template(string:field='BT-00-Text')) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:complex-template(text:$field) display name ${$field} label #{field|name|${$field}};", "invoke complex-template('BT-00-Text');"))); @@ -249,14 +286,17 @@ void testTemplate_ComplexNesting() { void testGlobals_VariableDeclaration() { assertEquals( lines( + "GLOBALS:", "string:t3='a'", "decimal:n1=12", "string:t1=$t3", "string:test(string:p1, decimal:p2) -> { concat($p1, $t1) }", "decimal:n2=$n1 + 1", "string:t4=udf:test($t3, 22)", - "let block01(string:t2) -> { eval(PathNode/TextField/normalize-space(text())) }", - "for-each(/*).call(block01(string:t2=udf:test($t3, 99)))"), // + "TEMPLATES:", + "let body01(string:t2) -> { eval(PathNode/TextField/normalize-space(text())) }", + "MAIN:", + "for-each(/*).call(body01(string:t2=udf:test($t3, 99)))"), // translateTemplate(lines( "// comment", // "let text:$t3='a'; // comment", @@ -273,23 +313,27 @@ void testGlobals_VariableDeclaration() { void testGlobals_NamedTemplates() { assertEquals( lines( + + "GLOBALS:", "string:t3='a'", "decimal:n1=12", "string:t1=$t3", "string:test(string:p1, decimal:p2) -> { concat($p1, $t1) }", "decimal:n2=$n1 + 1", "string:t4=udf:test($t3, 22)", + "TEMPLATES:", "let some-template(string:text1, string:text2) -> { eval(/*/PathNode/TextField/normalize-space(text()))text(' dokimi; ')eval($text2)text(' ;')", "for-each(/*/PathNode/NumberField).call(some-template01(string:text1=$text1, string:text2=$text2)) }", "let some-template01(string:text1, string:text2) -> { eval(../TextField/normalize-space(text())) }", - "let block02(string:ctx2) -> { #2: eval(./normalize-space(text()))text(' lala')", - "for-each(../NumberField).call(block0201(string:ctx2=$ctx2, decimal:ctx3=.))", - "for-each(.).call(block0202(string:ctx2=$ctx2, string:ctx=., string:t2=udf:test($t3, 99)))", - "for-each(.).call(block0203(string:ctx2=$ctx2, string:ctx4=., string:t5=udf:test($t3, 99))) }", - "let block0201(string:ctx2, decimal:ctx3) -> { eval(../TextField/normalize-space(text())) }", - "let block0202(string:ctx2, string:ctx, string:t2) -> { call(some-template(string:text1=$ctx, string:text2=$t2)) }", - "let block0203(string:ctx2, string:ctx4, string:t5) -> { eval(./normalize-space(text())) }", - "for-each(/*/PathNode/TextField).call(block02(string:ctx2=.))"), + "let body02(string:ctx2) -> { #2: eval(./normalize-space(text()))text(' lala')", + "for-each(../NumberField).call(body0201(string:ctx2=$ctx2, decimal:ctx3=.))", + "for-each(.).call(body0202(string:ctx2=$ctx2, string:ctx=., string:t2=udf:test($t3, 99)))", + "for-each(.).call(body0203(string:ctx2=$ctx2, string:ctx4=., string:t5=udf:test($t3, 99))) }", + "let body0201(string:ctx2, decimal:ctx3) -> { eval(../TextField/normalize-space(text())) }", + "let body0202(string:ctx2, string:ctx, string:t2) -> { call(some-template(string:text1=$ctx, string:text2=$t2)) }", + "let body0203(string:ctx2, string:ctx4, string:t5) -> { eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body02(string:ctx2=.))"), translateTemplate(lines( "// comment", // "let text:$t3='a';// comment", @@ -310,11 +354,12 @@ void testGlobals_NamedTemplates() { void testInvokeTemplate_Nesting() { assertEquals( lines( - "let some-template(string:t) -> { text('--')eval($t)text('--') }", - "let block02(string:ctx1, string:tx) -> { #1: call(some-template(string:t=$tx))", - "for-each(../StartDateField).call(block0201(string:ctx1=$ctx1, string:tx=$tx)) }", - "let block0201(string:ctx1, string:tx) -> { text('Nested content allowed') }", - "for-each(/*/PathNode/TextField).call(block02(string:ctx1=., string:tx='++'))"), // + "TEMPLATES:", "let some-template(string:t) -> { text('--')eval($t)text('--') }", + "let body02(string:ctx1, string:tx) -> { #1: call(some-template(string:t=$tx))", + "for-each(../StartDateField).call(body0201(string:ctx1=$ctx1, string:tx=$tx)) }", + "let body0201(string:ctx1, string:tx) -> { text('Nested content allowed') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body02(string:ctx1=., string:tx='++'))"), // translateTemplate(lines( "let template:some-template(text:$t) display --${$t}--;", "with context:$ctx1 = BT-00-Text, text:$tx='++' invoke some-template($tx);", @@ -325,6 +370,7 @@ void testInvokeTemplate_Nesting() { void testGlobals_NoTemplateLines() { assertEquals( lines( + "GLOBALS:", "string:t3='a'", "decimal:n1=12", "string:t1=$t3", @@ -345,9 +391,12 @@ void testGlobals_NoTemplateLines() { void testGlobals_DictionaryDeclaration() { assertEquals( lines( - "let dic index /*/PathNode/NumberField by /*/PathNode/TextField/normalize-space(text());", - "let block01() -> { eval(key('dic', 'key')) }", - "for-each(/*).call(block01())"), + "GLOBALS:", + "let dic index /*/PathNode/NumberField by ../TextField/normalize-space(text());", + "TEMPLATES:", + "let body01() -> { eval(key('dic', 'key')) }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate(lines( "let $dic index BT-00-Number by BT-00-Text;", "display ${$dic['key']};"))); @@ -358,9 +407,13 @@ void testGlobals_DictionaryDeclaration() { @Test void testDisplayTemplate() { assertEquals( - lines("string:t='test'", - "let block01() -> { text('this is a ')eval($t) }", - "for-each(/*).call(block01())"), + lines( + "GLOBALS:", + "string:t='test'", + "TEMPLATES:", + "let body01() -> { text('this is a ')eval($t) }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate(lines( "let text:$t = 'test';", "display this is a ${$t};"))); @@ -371,7 +424,7 @@ void testDisplayTemplate() { @Test void testTemplateLine_NoIndentation() { assertEquals( - "let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { text('foo') }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} foo")); } @@ -380,12 +433,14 @@ void testTemplateLine_NoIndentation() { */ @Test void testTemplateLine_AutogeneratedOutline() { - assertEquals(lines("let block01() -> { #1: text('Implicit 1 (shown)')", - "for-each(../..).call(block0101()) }", - "let block0101() -> { #1.1: text('Implicit 1.1 (shown)')", - "for-each(PathNode/NumberField).call(block010101()) }", - "let block010101() -> { text('Implicit 1.1.1 (hidden)') }", - "for-each(/*/PathNode/TextField).call(block01())"), // + assertEquals(lines("TEMPLATES:", + "let body01() -> { #1: text('Implicit 1 (shown)')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #1.1: text('Implicit 1.1 (shown)')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('Implicit 1.1.1 (hidden)') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate(lines("{BT-00-Text} Implicit 1 (shown)", "\t{ND-Root} Implicit 1.1 (shown)", "\t\t{BT-00-Number} Implicit 1.1.1 (hidden)"))); } @@ -395,12 +450,14 @@ void testTemplateLine_AutogeneratedOutline() { */ @Test void testTemplateLine_ExplicitOutline() { - assertEquals(lines("let block01() -> { #2: text('foo')", - "for-each(../..).call(block0101()) }", - "let block0101() -> { #2.3: text('bar')", - "for-each(PathNode/NumberField).call(block010101()) }", - "let block010101() -> { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01())"), // + assertEquals(lines("TEMPLATES:", + "let body01() -> { #2: text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #2.3: text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate( lines("2 {BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -412,12 +469,14 @@ void testTemplateLine_ExplicitOutline() { */ @Test void testTemplateLine_MixedOutline() { - assertEquals(lines("let block01() -> { #2: text('foo')", - "for-each(../..).call(block0101()) }", - "let block0101() -> { #2.1: text('bar')", - "for-each(PathNode/NumberField).call(block010101()) }", - "let block010101() -> { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01())"), // + assertEquals(lines("TEMPLATES:", + "let body01() -> { #2: text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #2.1: text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -428,12 +487,14 @@ void testTemplateLine_MixedOutline() { */ @Test void testTemplateLine_SuppressedOutline() { - assertEquals(lines("let block01() -> { #2: text('foo')", - "for-each(../..).call(block0101()) }", - "let block0101() -> { text('bar')", - "for-each(PathNode/NumberField).call(block010101()) }", - "let block010101() -> { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01())"), // + assertEquals(lines("TEMPLATES:", + "let body01() -> { #2: text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate( lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -447,52 +508,62 @@ void testTemplateLine_SuppressedOutline() { @Test void testTemplateLine_SuppressedOutlineAtParent() { // Outline is ignored if the line has no children - assertEquals(lines("let block01() -> { text('foo')", - "for-each(../..).call(block0101()) }", - "let block0101() -> { #1: text('bar')", - "for-each(PathNode/NumberField).call(block010101()) }", - "let block010101() -> { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01())"), // + assertEquals(lines("TEMPLATES:", + "let body01() -> { text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #1: text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @Test void testTemplateLine_IndentationWithTabs() { assertEquals( - lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // - "let block0101() -> { text('bar') }", // - "for-each(/*/PathNode/TextField).call(block01())"), // + lines("TEMPLATES:", // + "let body01() -> { #1: text('foo')", "for-each(.).call(body0101()) }", // + "let body0101() -> { text('bar') }", // + "MAIN:", // + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); } @Test void testTemplateLine_IndentationWithSpaces() { assertEquals( - lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // - "let block0101() -> { text('bar') }", // - "for-each(/*/PathNode/TextField).call(block01())"), // + lines("TEMPLATES:", // + "let body01() -> { #1: text('foo')", "for-each(.).call(body0101()) }", // + "let body0101() -> { text('bar') }", // + "MAIN:", // + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate(lines("{BT-00-Text} foo", " {BT-00-Text} bar"))); } @Test void testTemplateLine_LowerIndentation() { assertEquals( - lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", - "let block0101() -> { text('bar') }", - "let block02() -> { text('code') }", - "for-each(/*/PathNode/TextField).call(block01())", - "for-each(/*/PathNode/CodeField).call(block02())"), + lines("TEMPLATES:", + "let body01() -> { #1: text('foo')", "for-each(.).call(body0101()) }", + "let body0101() -> { text('bar') }", + "let body02() -> { text('code') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())", + "for-each(/*/PathNode/CodeField).call(body02())"), translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar", "{BT-00-Code} code"))); } @Test void testTemplateLine_LineJoining() { assertEquals( - lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", - "let block0101() -> { text('bar joined more') }", - "let block02() -> { text('code') }", - "for-each(/*/PathNode/TextField).call(block01())", - "for-each(/*/PathNode/CodeField).call(block02())"), + lines("TEMPLATES:", + "let body01() -> { #1: text('foo')", "for-each(.).call(body0101()) }", + "let body0101() -> { text('bar joined more') }", + "let body02() -> { text('code') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())", + "for-each(/*/PathNode/CodeField).call(body02())"), translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar \\ \n joined \\\n\\\nmore", "{BT-00-Code} code"))); } @@ -500,10 +571,12 @@ void testTemplateLine_LineJoining() { @Test void testTemplateLine_VariableScope() { assertEquals( - lines("let block01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // - "for-each(.).call(block0101()) }", // - "let block0101() -> { eval(for $x in ./normalize-space(text()) return $x) }", // - "for-each(/*/PathNode/TextField).call(block01())"), // + lines("TEMPLATES:", // + "let body01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // + "for-each(.).call(body0101()) }", // + "let body0101() -> { eval(for $x in ./normalize-space(text()) return $x) }", // + "MAIN:", // + "for-each(/*/PathNode/TextField).call(body01())"), // translateTemplate(lines("{BT-00-Text} ${for text:$x in BT-00-Text return $x}", " {BT-00-Text} ${for text:$x in BT-00-Text return $x}"))); @@ -513,16 +586,17 @@ void testTemplateLine_VariableScope() { void testTemplateLine_ContextVariable() { assertEquals( lines( - "let block01(string:xyz, string:ctx, string:t) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", // - "for-each(.).call(block0101(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2='test'))", // - "for-each(.).call(block0102(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2='test3')) }", // - "let block0101(string:xyz, string:ctx, string:t, string:t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", // - "for-each(.).call(block010101(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2=$t2))", // - "for-each(.).call(block010102(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2=$t2)) }", // - "let block010101(string:xyz, string:ctx, string:t, string:t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // - "let block010102(string:xyz, string:ctx, string:t, string:t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // - "let block0102(string:xyz, string:ctx, string:t, string:t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t2, $ctx)) }", // - "for-each(/*/PathNode/TextField).call(block01(string:xyz='a', string:ctx=., string:t=./normalize-space(text())))"), // + "TEMPLATES:", "let body01(string:xyz, string:ctx, string:t) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", // + "for-each(.).call(body0101(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2='test'))", // + "for-each(.).call(body0102(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2='test3')) }", // + "let body0101(string:xyz, string:ctx, string:t, string:t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", // + "for-each(.).call(body010101(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2=$t2))", // + "for-each(.).call(body010102(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2=$t2)) }", // + "let body010101(string:xyz, string:ctx, string:t, string:t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // + "let body010102(string:xyz, string:ctx, string:t, string:t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // + "let body0102(string:xyz, string:ctx, string:t, string:t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t2, $ctx)) }", // + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01(string:xyz='a', string:ctx=., string:t=./normalize-space(text())))"), // translateTemplate(lines( "{text:$xyz='a', context:$ctx = BT-00-Text, text:$t = BT-00-Text} ${for text:$x in BT-00-Text return concat($x, $t)}", " {BT-00-Text, text:$t2 = 'test'} ${for text:$y in BT-00-Text return concat($y, $t, $t2)}", @@ -564,21 +638,21 @@ void testTemplateLine_ContextDeclarationShortcuts() { @Test void testTemplateLine_SecondaryTemplate() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break()text('some text') }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break()text('some text') }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n some text")); } @Test void testTemplateLine_LineBreak() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break() }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break() }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n")); } @Test void testTemplateLine_EndOfLineComments() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' blah blah') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' blah blah') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); } @@ -586,12 +660,13 @@ void testTemplateLine_EndOfLineComments() { void testTemplateLine_Indentation_DeepNesting() { assertEquals( lines( - "let block01() -> { #1: text('Level 1')", - "for-each(.).call(block0101()) }", - "let block0101() -> { #1.1: text('Level 2')", - "for-each(.).call(block010101()) }", - "let block010101() -> { text('Level 3') }", - "for-each(/*/PathNode/TextField).call(block01())"), + "TEMPLATES:", "let body01() -> { #1: text('Level 1')", + "for-each(.).call(body0101()) }", + "let body0101() -> { #1.1: text('Level 2')", + "for-each(.).call(body010101()) }", + "let body010101() -> { text('Level 3') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate(lines( "{BT-00-Text} Level 1", " {BT-00-Text} Level 2", @@ -600,47 +675,82 @@ void testTemplateLine_Indentation_DeepNesting() { // #endregion templateLine ------------------------------------------------- + + // #region otherSections ---------------------------------------------------- + + @Test + void testOtherSections_SummarySection() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let summary01() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let summary02() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let summary03() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let nav01() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let nav02() -> { text('Summary: ')eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())", + "SUMMARY:", + "for-each(/*/PathNode/TextField).call(summary01())", + "for-each(/*/PathNode/TextField).call(summary02())", + "for-each(/*/PathNode/TextField).call(summary03())", + "NAV:", + "for-each(/*/PathNode/TextField).call(nav01())", + "for-each(/*/PathNode/TextField).call(nav02())" + ), + translateTemplate(lines("{BT-00-Text} Summary: ${BT-00-Text}", + "--- SUMMARY ---", + "{BT-00-Text} Summary: ${BT-00-Text}", + "{BT-00-Text} Summary: ${BT-00-Text}", + "{BT-00-Text} Summary: ${BT-00-Text}", + "--- NAVIGATION ---", + "{BT-00-Text} Summary: ${BT-00-Text}", + "{BT-00-Text} Summary: ${BT-00-Text}"))); + } + + // #endregion otherSections ------------------------------------------------- + // #region Labels ----------------------------------------------------------- @Test void testLabelBlock_StandardLabelReference() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{field|name|BT-00-Text}")); } @Test void testLabelBlock_StandardLabelReferenceWithPluraliser() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'), ../NumberField/number()) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'), ../NumberField/number()) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{field|name|BT-00-Text;${BT-00-Number}}")); } @Test void testStandardLabelReference_UsingLabelTypeAsAssetId() { assertEquals( - "let block01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{auxiliary|text|value}")); } @Test void testLabelBlock_ComputedLabelReference() { assertEquals( - "let block01() -> { label(string-join(('field','|','name','|','BT-00-Text'), ', ')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(string-join(('field','|','name','|','BT-00-Text'), ', ')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{${string-join(('field', '|', 'name', '|', 'BT-00-Text'), ', ')}}")); } @Test void testLabelBlock_ShorthandBtLabelReference() { assertEquals( - "let block01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{name|BT-00}")); } @Test void testLabelBlock_ShorthandFieldLabelReference() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{name|BT-00-Text}")); } @@ -653,42 +763,42 @@ void testLabelBlock_ShorthandBtLabelReferenceMissingLabelType() { @Test void testLabelBlock_ShorthandIndirectLabelReferenceForIndicator() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{BT-00-Indicator}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForCode() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{BT-00-Code}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForInternalCode() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForCodeAttribute() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{BT-00-CodeAttribute}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForCodeAttributeWithSameAttributeInContext() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())", + "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField/@attribute).call(body01())", translateTemplate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", translateTemplate("{BT-00-Code} #{BT-00-CodeAttribute}")); } @@ -707,14 +817,14 @@ void testShorthandIndirectLabelReferenceForAttribute() { @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndIndicatorField() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/IndicatorField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nMAIN:\nfor-each(/*/PathNode/IndicatorField).call(body01())", translateTemplate("{BT-00-Indicator} #{name}")); } @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndCodeField() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", translateTemplate("{BT-00-Code} #{name}")); } @@ -727,7 +837,7 @@ void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndTextField() { @Test void testShorthandLabelReferenceFromContext_WithOtherLabelType() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("{BT-00-Text} #{name}")); } @@ -740,14 +850,14 @@ void testShorthandLabelReferenceFromContext_WithUnknownLabelType() { @Test void testLabelBlock_ShorthandLabelReferenceFromContext_WithNodeContext() { assertEquals( - "let block01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("{ND-Root} #{name}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceFromContextField() { assertEquals( - "let block01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", translateTemplate("{BT-00-Code} #value")); } @@ -759,28 +869,28 @@ void testLabelBlock_ShorthandIndirectLabelReferenceFromContextField_WithNodeCont @Test void testLabelBlock_Expression_AssetId() { assertEquals( - "let block01(string:assetId) -> { label(concat('field', '|', 'name', '|', $assetId)) }\nfor-each(/*).call(block01(string:assetId='BT-00-Text'))", + "TEMPLATES:\nlet body01(string:assetId) -> { label(concat('field', '|', 'name', '|', $assetId)) }\nMAIN:\nfor-each(/*).call(body01(string:assetId='BT-00-Text'))", translateTemplate("{/, text:$assetId='BT-00-Text'} #{field|name|${$assetId}}")); } @Test void testLabelBlock_Expression_LabelType() { assertEquals( - "let block01(string:labelType) -> { label(concat('field', '|', $labelType, '|', 'BT-00-Text')) }\nfor-each(/*).call(block01(string:labelType='name'))", + "TEMPLATES:\nlet body01(string:labelType) -> { label(concat('field', '|', $labelType, '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*).call(body01(string:labelType='name'))", translateTemplate("{/, text:$labelType='name'} #{field|${$labelType}|BT-00-Text}")); } @Test void testLabelBlock_Expression_AssetType() { assertEquals( - "let block01(string:assetType) -> { label(concat($assetType, '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*).call(block01(string:assetType='field'))", + "TEMPLATES:\nlet body01(string:assetType) -> { label(concat($assetType, '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*).call(body01(string:assetType='field'))", translateTemplate("{/, text:$assetType='field'} #{${$assetType}|name|BT-00-Text}")); } @Test void testLabelBlock_Expression_NestedExpressions() { assertEquals( - "let block01(string:assetType, string:labelType, string:assetId) -> { label(concat($assetType, '|', $labelType, '|', $assetId)) }\nfor-each(/*).call(block01(string:assetType='field', string:labelType='name', string:assetId='BT-00-Text'))", + "TEMPLATES:\nlet body01(string:assetType, string:labelType, string:assetId) -> { label(concat($assetType, '|', $labelType, '|', $assetId)) }\nMAIN:\nfor-each(/*).call(body01(string:assetType='field', string:labelType='name', string:assetId='BT-00-Text'))", translateTemplate( "{/, text:$assetType='field', text:$labelType='name', text:$assetId='BT-00-Text'} #{${$assetType}|${$labelType}|${$assetId}}")); } @@ -792,14 +902,14 @@ void testLabelBlock_Expression_NestedExpressions() { @Test void testExpressionBlock_ShorthandFieldValueReferenceFromContextField() { assertEquals( - "let block01() -> { eval(./normalize-space(text())) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet body01() -> { eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", translateTemplate("{BT-00-Code} $value")); } @Test void testExpressionBlock_ShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( - "let block01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' blah ')eval(./normalize-space(text()))text(' blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", + "TEMPLATES:\nlet body01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' blah ')eval(./normalize-space(text()))text(' blah') }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", translateTemplate("{BT-00-Code} blah #value blah $value blah")); } @@ -812,17 +922,31 @@ void testExpressionBlock_ShorthandFieldValueReferenceFromContextField_WithNodeCo // #region contextDeclarationBlock ------------------------------------------ + @Test + void testContextDeclarationBlock_ContextFieldVariable() { + assertEquals( + "TEMPLATES:\nlet body01(string:ctx) -> { text('Context: ')eval($ctx) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:ctx=.))", + translateTemplate("{context:$ctx = BT-00-Text} Context: ${$ctx}")); + } + + @Test + void testContextDeclarationBlock_ContextNodeVariable() { + assertEquals( + "TEMPLATES:\nlet body01(context:ctx) -> { text('Context: ')eval($ctx/PathNode/TextField/normalize-space(text())) }\nMAIN:\nfor-each(/*).call(body01(context:ctx=.))", + translateTemplate("{context:$ctx = ND-Root} Context: ${$ctx::BT-00-Text}")); + } + @Test void testContextDeclarationBlock_MultipleVariables() { assertEquals( - "let block01(string:var1, decimal:var2) -> { text('Variables: ')eval($var1)text(', ')eval($var2) }\nfor-each(/*/PathNode/TextField).call(block01(string:var1='hello', decimal:var2=42))", + "TEMPLATES:\nlet body01(string:var1, decimal:var2) -> { text('Variables: ')eval($var1)text(', ')eval($var2) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:var1='hello', decimal:var2=42))", translateTemplate("{text:$var1='hello', number:$var2=42, BT-00-Text} Variables: ${$var1}, ${$var2}")); } @Test void testContextDeclarationBlock_VariableBeforeContext() { assertEquals( - "let block01(string:prefix) -> { text('Prefix: ')eval($prefix)text(' Value: ')eval(./normalize-space(text())) }\nfor-each(/*/PathNode/TextField).call(block01(string:prefix='test'))", + "TEMPLATES:\nlet body01(string:prefix) -> { text('Prefix: ')eval($prefix)text(' Value: ')eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:prefix='test'))", translateTemplate("{text:$prefix='test', BT-00-Text} Prefix: ${$prefix} Value: ${BT-00-Text}")); } @@ -840,9 +964,10 @@ void testContextDeclarationBlock_OnlyVariables() { void testChooseTemplate_WhenBlock_MultipleConditions() { assertEquals( lines( - "let multi-when-template(string:status) -> { choose { when $status = 'active': text('Status: Active'), when $status = 'inactive': text('Status: Inactive'), when $status = 'pending': text('Status: Pending'), otherwise: text('Status: Unknown') } }", - "let block02() -> { call(multi-when-template(string:status='active')) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let multi-when-template(string:status) -> { choose { when $status = 'active': text('Status: Active'), when $status = 'inactive': text('Status: Inactive'), when $status = 'pending': text('Status: Pending'), otherwise: text('Status: Unknown') } }", + "let body02() -> { call(multi-when-template(string:status='active')) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:multi-when-template(text:$status)", "when $status == 'active' display Status: Active", @@ -856,9 +981,10 @@ void testChooseTemplate_WhenBlock_MultipleConditions() { void testChooseTemplate_WhenBlock_ComplexBooleanExpressions() { assertEquals( lines( - "let complex-when-template(decimal:value) -> { choose { when $value > 0 and $value < 100: text('In range'), when $value <= 0: text('Too low'), otherwise: text('Too high') } }", - "let block02() -> { call(complex-when-template(decimal:value=50)) }", - "for-each(/*).call(block02())"), + "TEMPLATES:", "let complex-when-template(decimal:value) -> { choose { when $value > 0 and $value < 100: text('In range'), when $value <= 0: text('Too low'), otherwise: text('Too high') } }", + "let body02() -> { call(complex-when-template(decimal:value=50)) }", + "MAIN:", + "for-each(/*).call(body02())"), translateTemplate(lines( "let template:complex-when-template(number:$value)", "when $value > 0 and $value < 100 display In range", @@ -871,10 +997,11 @@ void testChooseTemplate_WhenBlock_ComplexBooleanExpressions() { void testChooseTemplate_OtherwiseBlock_InvokeTemplate() { assertEquals( lines( - "let fallback-template(string:reason) -> { text('Fallback: ')eval($reason) }", + "TEMPLATES:", "let fallback-template(string:reason) -> { text('Fallback: ')eval($reason) }", "let otherwise-invoke-template(boolean:condition, string:reason) -> { choose { when $condition: text('Condition met'), otherwise: call(fallback-template(string:reason=$reason)) } }", - "let block03() -> { call(otherwise-invoke-template(boolean:condition=false(), string:reason='default')) }", - "for-each(/*).call(block03())"), + "let body03() -> { call(otherwise-invoke-template(boolean:condition=false(), string:reason='default')) }", + "MAIN:", + "for-each(/*).call(body03())"), translateTemplate(lines( "let template:fallback-template(text:$reason) display Fallback: ${$reason};", "let template:otherwise-invoke-template(indicator:$condition, text:$reason)", @@ -886,11 +1013,13 @@ void testChooseTemplate_OtherwiseBlock_InvokeTemplate() { @Test void testChooseTemplate_WhenOtherwise() { assertEquals( - lines("let some-template(string:txt) -> { text('>')eval($txt)text('<') }", - "let block02(string:t) -> { choose { when 1 > 2: text('foo'), when 2 < 3: text('bar'), when 3 > 3: call(some-template(string:txt='1')), otherwise: text('foo-bar') } }", - "let block03(string:t) -> { choose { when 1 > 2: text('foo'), when 2 < 3: text('bar'), when 3 > 3: text('foo-bar'), otherwise: call(some-template(string:txt='2')) } }", - "for-each(/*/PathNode/TextField).call(block02(string:t='test'))", - "for-each(/*/PathNode/TextField).call(block03(string:t='test'))"), + lines("TEMPLATES:", + "let some-template(string:txt) -> { text('>')eval($txt)text('<') }", + "let body02(string:t) -> { choose { when 1 > 2: text('foo'), when 2 < 3: text('bar'), when 3 > 3: call(some-template(string:txt='1')), otherwise: text('foo-bar') } }", + "let body03(string:t) -> { choose { when 1 > 2: text('foo'), when 2 < 3: text('bar'), when 3 > 3: text('foo-bar'), otherwise: call(some-template(string:txt='2')) } }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body02(string:t='test'))", + "for-each(/*/PathNode/TextField).call(body03(string:t='test'))"), translateTemplate(lines( "// test", "let template:some-template(text:$txt) display >${$txt}<;", @@ -909,9 +1038,12 @@ void testChooseTemplate_WhenOtherwise() { @Test void testChooseTemplate_WhenNoOtherwise() { assertEquals( - lines("string:t='text'", - "let block01() -> { choose { when true(): eval(./normalize-space(text()))text(' is a ')eval($t), otherwise nothing } }", - "for-each(/*/PathNode/TextField[true()]).call(block01())"), + lines("GLOBALS:", + "string:t='text'", + "TEMPLATES:", + "let body01() -> { choose { when true(): eval(./normalize-space(text()))text(' is a ')eval($t), otherwise nothing } }", + "MAIN:", + "for-each(/*/PathNode/TextField[true()]).call(body01())"), translateTemplate(lines( "let text:$t = 'text';", "with BT-00-Text[TRUE] when TRUE display ${BT-00-Text} is a ${$t};"))); @@ -920,9 +1052,12 @@ void testChooseTemplate_WhenNoOtherwise() { @Test void testChooseTemplate_WhenNoOtherwiseNoContext() { assertEquals( - lines("string:t='test'", - "let block01() -> { choose { when true(): text('this is a ')eval($t), otherwise nothing } }", - "for-each(/*).call(block01())"), + lines("GLOBALS:", + "string:t='test'", + "TEMPLATES:", + "let body01() -> { choose { when true(): text('this is a ')eval($t), otherwise nothing } }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate(lines( "let text:$t = 'test';", "when TRUE display this is a ${$t};"))); @@ -935,7 +1070,7 @@ void testChooseTemplate_WhenNoOtherwiseNoContext() { @Test void testTemplateVariableList_WithAllDataTypes() { assertEquals( - "let block01(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('All types: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }\nfor-each(/*).call(block01(string:str='text', decimal:num=42, boolean:bool=true(), date:dt=xs:date('2023-01-01'), time:tm=xs:time('12:00:00'), duration:dur=xs:dayTimeDuration('P1D')))", + "TEMPLATES:\nlet body01(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('All types: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }\nMAIN:\nfor-each(/*).call(body01(string:str='text', decimal:num=42, boolean:bool=true(), date:dt=xs:date('2023-01-01'), time:tm=xs:time('12:00:00'), duration:dur=xs:dayTimeDuration('P1D')))", translateTemplate( "{/, text:$str='text', number:$num=42, indicator:$bool=TRUE, date:$dt=date('2023-01-01'), time:$tm=time('12:00:00'), measure:$dur=day-time-duration('P1D')} All types: ${$str}, ${$num}, ${$bool}, ${$dt}, ${$tm}, ${$dur}")); } @@ -943,7 +1078,7 @@ void testTemplateVariableList_WithAllDataTypes() { @Test void testTemplateVariableList_ExpressionInitializers() { assertEquals( - "let block01(string:computed) -> { text('Computed: ')eval($computed) }\nfor-each(/*/PathNode/TextField).call(block01(string:computed=concat('prefix-', ./normalize-space(text()))))", + "TEMPLATES:\nlet body01(string:computed) -> { text('Computed: ')eval($computed) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:computed=concat('prefix-', ./normalize-space(text()))))", translateTemplate("{BT-00-Text, text:$computed=concat('prefix-', BT-00-Text)} Computed: ${$computed}")); } @@ -954,14 +1089,14 @@ void testTemplateVariableList_ExpressionInitializers() { @Test void testTemplateLine_OutlineNumber_Only() { assertEquals( - "let block01() -> { text('text') }\nfor-each(/*).call(block01())", + "TEMPLATES:\nlet body01() -> { text('text') }\nMAIN:\nfor-each(/*).call(body01())", translateTemplate("1 display text;")); } @Test void testTemplateLine_OutlineNumber_WithContext() { assertEquals( - "let block01() -> { text('Value: ')eval(./normalize-space(text())) }\nfor-each(/*/PathNode/TextField).call(block01())", + "TEMPLATES:\nlet body01() -> { text('Value: ')eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", translateTemplate("1 {BT-00-Text} Value: ${BT-00-Text}")); } @@ -1056,12 +1191,14 @@ void testTemplateDefinition_InvalidTooFewParameters() { @Test void testContextulizer_WithPredicate() { assertEquals( - lines("let block01() -> { #1: text('line1: ')eval(.[1 = 1]/normalize-space(text()))", - "for-each(.[1 = 2]).call(block0101()) }", - "let block0101() -> { #1.1: text('line2: ')eval(.[1 = 4]/normalize-space(text()))", - "for-each(.[1 = 4]).call(block010101()) }", - "let block010101() -> { text('line3: ')eval(.[1 = 5]/normalize-space(text())) }", - "for-each(/*/SubNode/SubSubNode/SubTextField[0 = 0]).call(block01())"), + lines("TEMPLATES:", + "let body01() -> { #1: text('line1: ')eval(.[1 = 1]/normalize-space(text()))", + "for-each(.[1 = 2]).call(body0101()) }", + "let body0101() -> { #1.1: text('line2: ')eval(.[1 = 4]/normalize-space(text()))", + "for-each(.[1 = 4]).call(body010101()) }", + "let body010101() -> { text('line3: ')eval(.[1 = 5]/normalize-space(text())) }", + "MAIN:", + "for-each(/*/SubNode/SubSubNode/SubTextField[0 = 0]).call(body01())"), translateTemplate(lines("{BT-01-SubSubNode-Text} line1: ${BT-01-SubSubNode-Text[1==1]}", " {BT-01-SubSubNode-Text[1==2]} line2: ${BT-01-SubSubNode-Text[1==4]}", " {BT-01-SubSubNode-Text[1==4]} line3: ${BT-01-SubSubNode-Text[1==5]}"))); @@ -1070,10 +1207,12 @@ void testContextulizer_WithPredicate() { @Test void testContextualizer_WithFieldInPredicate() { assertEquals( - lines("let block01() -> { #1: text('line1')", - "for-each(SubTextField).call(block0101()) }", - "let block0101() -> { text('line2') }", - "for-each(/*/SubNode[SubTextField]).call(block01())"), + lines("TEMPLATES:", + "let body01() -> { #1: text('line1')", + "for-each(SubTextField).call(body0101()) }", + "let body0101() -> { text('line2') }", + "MAIN:", + "for-each(/*/SubNode[SubTextField]).call(body01())"), translateTemplate(lines("{ND-SubNode[BT-01-SubNode-Text is present]} line1", " {BT-01-SubNode-Text} line2"))); } From ab073074ae913e8526fcc47c1cc641ab60e8ef4e Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 1 Aug 2025 14:49:51 +0200 Subject: [PATCH 03/10] Allow node context to be assigned to a context variable --- .../java/eu/europa/ted/efx/model/Context.java | 4 ++ .../expressions/path/NodePathExpression.java | 3 + .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 55 ++++++++++++++----- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/model/Context.java b/src/main/java/eu/europa/ted/efx/model/Context.java index b24d017..0403f2f 100644 --- a/src/main/java/eu/europa/ted/efx/model/Context.java +++ b/src/main/java/eu/europa/ted/efx/model/Context.java @@ -50,6 +50,10 @@ public NodeContext(final String nodeId, final PathExpression absolutePath, super(nodeId, absolutePath, relativePath); } + public NodeContext(final String nodeId, final PathExpression absolutePath, final Variable variable) { + super(nodeId, absolutePath, variable); + } + public NodeContext(final String nodeId, final PathExpression absolutePath) { super(nodeId, absolutePath); } diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java index 9fe125d..6fb0dde 100644 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java @@ -9,4 +9,7 @@ public class NodePathExpression extends PathExpression.Impl { public NodePathExpression(final String script) { super(script, EfxDataType.Node.class); } + public static NodePathExpression empty() { + return new NodePathExpression(""); + } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java index ac1586d..9eb48e7 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -21,8 +21,8 @@ import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.efx.exceptions.InvalidIndentationException; import eu.europa.ted.efx.exceptions.InvalidUsageException; +import eu.europa.ted.efx.exceptions.InvalidIndentationException; import eu.europa.ted.efx.interfaces.Argument; import eu.europa.ted.efx.interfaces.EfxTemplateTranslator; import eu.europa.ted.efx.interfaces.MarkupGenerator; @@ -52,17 +52,17 @@ import eu.europa.ted.efx.model.templates.Conditionals; import eu.europa.ted.efx.model.templates.ContentBlock; import eu.europa.ted.efx.model.templates.ContentBlockStack; -import eu.europa.ted.efx.model.templates.Markup; import eu.europa.ted.efx.model.templates.TemplateDefinition; import eu.europa.ted.efx.model.templates.TemplateInvocation; +import eu.europa.ted.efx.model.templates.Markup; import eu.europa.ted.efx.model.types.EfxDataType; import eu.europa.ted.efx.model.types.FieldTypes; import eu.europa.ted.efx.model.variables.Dictionary; import eu.europa.ted.efx.model.variables.Function; +import eu.europa.ted.efx.model.variables.StrictArguments; import eu.europa.ted.efx.model.variables.Identifier; import eu.europa.ted.efx.model.variables.ParsedParameter; import eu.europa.ted.efx.model.variables.ParsedParameters; -import eu.europa.ted.efx.model.variables.StrictArguments; import eu.europa.ted.efx.model.variables.Template; import eu.europa.ted.efx.model.variables.Variable; import eu.europa.ted.efx.model.variables.Variables; @@ -80,6 +80,8 @@ import eu.europa.ted.efx.sdk2.EfxParser.DateParameterDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.DateVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.DictionaryDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.DictionaryIndexClauseContext; +import eu.europa.ted.efx.sdk2.EfxParser.DictionaryKeyClauseContext; import eu.europa.ted.efx.sdk2.EfxParser.DurationFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.DurationParameterDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.DurationVariableInitializerContext; @@ -89,6 +91,12 @@ import eu.europa.ted.efx.sdk2.EfxParser.InvokeTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.LabelTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.LabelTypeContext; +import eu.europa.ted.efx.sdk2.EfxParser.LinkedExpressionBlockContext; +import eu.europa.ted.efx.sdk2.EfxParser.LinkedExpressionTemplateContext; +import eu.europa.ted.efx.sdk2.EfxParser.LinkedLabelBlockContext; +import eu.europa.ted.efx.sdk2.EfxParser.LinkedLabelTemplateContext; +import eu.europa.ted.efx.sdk2.EfxParser.LinkedTextBlockContext; +import eu.europa.ted.efx.sdk2.EfxParser.LinkedTextTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.NavigationSectionContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericParameterDeclarationContext; @@ -106,8 +114,8 @@ import eu.europa.ted.efx.sdk2.EfxParser.StringParameterDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.StringVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.SummarySectionContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateDefinitionContext; +import eu.europa.ted.efx.sdk2.EfxParser.TemplateDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateFileContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateLineContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateVariableDeclarationContext; @@ -797,11 +805,15 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) { if (ctx.contextVariableInitializer().fieldContext() != null) { String fieldId = getFieldId(ctx.contextVariableInitializer().fieldContext()); this.exitFieldContextDeclaration(fieldId, contextPath, contextVariable); + } else if (ctx.contextVariableInitializer().nodeContext() != null) { + String nodeId = getNodeId(ctx.contextVariableInitializer().nodeContext()); + assert nodeId != null : "We should have been able to locate the NodeId declared as context."; + this.exitNodeContextDeclaration(nodeId, contextPath, contextVariable); } } else if (ctx.nodeContext() != null) { String nodeId = getNodeId(ctx.nodeContext()); assert nodeId != null : "We should have been able to locate the NodeId declared as context."; - this.exitNodeContextDeclaration(nodeId, contextPath); + this.exitNodeContextDeclaration(nodeId, contextPath, null); } break; } @@ -814,7 +826,7 @@ private void exitSameContextDeclaration() { if (currentContext.isFieldContext()) { this.exitFieldContextDeclaration(symbol, contextPath, null); } else if (currentContext.isNodeContext()) { - this.exitNodeContextDeclaration(symbol, contextPath); + this.exitNodeContextDeclaration(symbol, contextPath, currentContext.variable()); } } @@ -825,25 +837,30 @@ private void exitParentContextDeclaration() { if (parentContext.isFieldContext()) { this.exitFieldContextDeclaration(symbol, contextPath, null); } else if (parentContext.isNodeContext()) { - this.exitNodeContextDeclaration(symbol, contextPath); + this.exitNodeContextDeclaration(symbol, contextPath, parentContext.variable()); } } private void exitRootContextDeclaration() { PathExpression contextPath = new NodePathExpression("/*"); String symbol = "ND-Root"; - this.exitNodeContextDeclaration(symbol, contextPath); + this.exitNodeContextDeclaration(symbol, contextPath, null); } private void exitFieldContextDeclaration(String fieldId, PathExpression contextPath, Variable contextVariable) { this.efxContext.push(new FieldContext(fieldId, contextPath, contextVariable)); if (contextVariable != null) { this.stack.declareIdentifier(contextVariable); + this.efxContext.declareContextVariable(contextVariable.name, new FieldContext(fieldId, contextPath, contextVariable)); } } - private void exitNodeContextDeclaration(String nodeId, PathExpression contextPath) { - this.efxContext.push(new NodeContext(nodeId, contextPath)); + private void exitNodeContextDeclaration(String nodeId, PathExpression contextPath, Variable contextVariable) { + this.efxContext.push(new NodeContext(nodeId, contextPath, contextVariable)); + if (contextVariable != null) { + this.stack.declareIdentifier(contextVariable); + this.efxContext.declareContextVariable(contextVariable.name, new NodeContext(nodeId, contextPath, contextVariable)); + } } private Variable getContextVariable(ContextVariableInitializerContext ctx, @@ -1227,13 +1244,21 @@ public void exitTemplateVariableDeclaration(TemplateVariableDeclarationContext a @Override public void exitContextDeclaration(ContextDeclarationContext ctx) { final var initializer = ctx.contextVariableInitializer(); - if (initializer == null || initializer.fieldContext() == null) { + if (initializer == null) { return; // No context variable initializer, nothing to do during pre-processing. } - final String fieldId = getFieldId(initializer.fieldContext()); - var fieldType = FieldTypes.fromString(this.symbols.getTypeOfField(fieldId)); - this.stack.declareIdentifier(new Variable(initializer.variableName.getText(), PathExpression.instantiate("", fieldType), - PathExpression.instantiate("", fieldType), PathExpression.instantiate("", fieldType))); + if (initializer.fieldContext() != null) { + final String fieldId = getFieldId(initializer.fieldContext()); + var fieldType = FieldTypes.fromString(this.symbols.getTypeOfField(fieldId)); + this.stack.declareIdentifier( + new Variable(initializer.variableName.getText(), PathExpression.instantiate("", fieldType), + PathExpression.instantiate("", fieldType), PathExpression.instantiate("", fieldType))); + } + if (initializer.nodeContext() != null) { + this.stack.declareIdentifier(new Variable(initializer.variableName.getText(), NodePathExpression.empty(), + NodePathExpression.empty(), NodePathExpression.empty())); + } + return; } @Override From a1263bbe4304b7de4907fb049d9c73923ddbf2f9 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 1 Aug 2025 14:51:53 +0200 Subject: [PATCH 04/10] Make the key of a dictionary evaluate relative to its context. --- .../europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java index 9eb48e7..ac8c013 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -708,6 +708,16 @@ public void exitDictionaryDeclaration(DictionaryDeclarationContext ctx) { this.stack.declareGlobalIdentifier(new Dictionary(name, match, key)); } + @Override + public void exitDictionaryIndexClause(DictionaryIndexClauseContext ctx) { + this.efxContext.pushFieldContext(getFieldId(ctx.fieldContext())); + } + + @Override + public void exitDictionaryKeyClause(DictionaryKeyClauseContext ctx) { + this.efxContext.pop(); + } + // #endregion New in EFX-2 -------------------------------------------------- // #endregion Label Blocks #{...} ------------------------------------------- @@ -1366,7 +1376,7 @@ private void exitTemplateLine(final int indentLevel) { @Override public void exitDictionaryDeclaration(DictionaryDeclarationContext ctx) { var dictionaryName = ctx.dictionaryName.getText(); - var fieldId = getFieldId(ctx.fieldContext()); + var fieldId = getFieldId(ctx.index.fieldContext()); var field = this.symbols.getAbsolutePathOfField(fieldId); this.stack.declareGlobalIdentifier(new Dictionary(dictionaryName, field, StringExpression.empty())); } From ddf7c05e26aa681aa13b735c88c97857c9b5e4a5 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 1 Aug 2025 14:53:14 +0200 Subject: [PATCH 05/10] Add support for hyperlinks in view templates. --- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java index ac8c013..169678f 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -414,6 +414,13 @@ public void exitTextTemplate(TextTemplateContext ctx) { this.stack.push(this.markup.renderFreeText(this.markup.escapeSpecialCharacters(text), this.translatorContext).join(template)); } + @Override + public void exitLinkedTextTemplate(LinkedTextTemplateContext ctx) { + Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); + Markup text = this.stack.pop(Markup.class); + this.stack.push(text.join(template)); + } + @Override public void exitLabelTemplate(LabelTemplateContext ctx) { Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); @@ -421,6 +428,13 @@ public void exitLabelTemplate(LabelTemplateContext ctx) { this.stack.push(label.join(template)); } + @Override + public void exitLinkedLabelTemplate(LinkedLabelTemplateContext ctx) { + Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); + Markup label = this.stack.pop(Markup.class); + this.stack.push(label.join(template)); + } + @Override public void exitExpressionTemplate(ExpressionTemplateContext ctx) { Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); @@ -428,6 +442,13 @@ public void exitExpressionTemplate(ExpressionTemplateContext ctx) { this.stack.push(this.markup.renderVariableExpression(expression, this.translatorContext).join(template)); } + @Override + public void exitLinkedExpressionTemplate(LinkedExpressionTemplateContext ctx) { + Markup template = ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); + Markup link = this.stack.pop(Markup.class); + this.stack.push(link.join(template)); + } + // #region New in EFX-2: Secondary templates -------------------------------- @Override @@ -698,6 +719,7 @@ public void exitComputedLabelReference(ComputedLabelReferenceContext ctx) { this.stack.push(this.markup.renderLabelFromExpression(expression, quantity, this.translatorContext)); } + @Override public void exitDictionaryDeclaration(DictionaryDeclarationContext ctx) { String name = ctx.dictionaryName.getText(); @@ -980,6 +1002,31 @@ public void exitTemplateVariableDeclaration(TemplateVariableDeclarationContext a // #endregion Variable Initializers ----------------------------------------- + // #region Hyperlinks ------------------------------------------------------- + + @Override + public void exitLinkedTextBlock(LinkedTextBlockContext ctx) { + var url = this.stack.pop(StringExpression.class); + var text = this.markup.renderFreeText(ctx.textBlock().getText(), this.translatorContext); + this.stack.push(this.markup.renderHyperlink(text, url, this.translatorContext)); + } + + @Override + public void exitLinkedLabelBlock(LinkedLabelBlockContext ctx) { + var url = this.stack.pop(StringExpression.class); + var text = this.stack.pop(Markup.class); + this.stack.push(this.markup.renderHyperlink(text, url, this.translatorContext)); + } + + @Override + public void exitLinkedExpressionBlock(LinkedExpressionBlockContext ctx) { + var url = this.stack.pop(StringExpression.class); + var text = this.markup.renderVariableExpression(this.stack.pop(Expression.class), this.translatorContext); + this.stack.push(this.markup.renderHyperlink(text, url, this.translatorContext)); + } + + // #endregion Hyperlinks ---------------------------------------------------- + // #endregion New in EFX-2 -------------------------------------------------- @Override From 70813c35713714d529f18477d9a7fa8907272820 Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Mon, 4 Aug 2025 10:24:53 +0200 Subject: [PATCH 06/10] pom: Update eforms-core dependency to released version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 25f752d..b023484 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ 1.13.0 2.0.0-SNAPSHOT - 1.5.0-SNAPSHOT + 1.5.0 4.13.1 From 1d44c56ffcb2281b881404762cef6e80cd70f2e5 Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Mon, 4 Aug 2025 10:25:55 +0200 Subject: [PATCH 07/10] MarkupGeneratorMock: Refactor composeOutputFile method for readability --- .../ted/efx/mock/MarkupGeneratorMock.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java index 55b8ce4..be99529 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -160,21 +160,11 @@ public Markup renderFragmentInvocation(String name, Set arguments, Tra @Override public Markup composeOutputFile(List globals, List body, List summary, List navigation, final List fragments) { - var renderedGlobals = globals.size() > 0 - ? String.format("GLOBALS:%2$s%1$s",globals.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') - : ""; - var renderedTemplates = fragments.size() > 0 - ? String.format("TEMPLATES:%2$s%1$s", fragments.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') - : ""; - var renderedMain = body.size() > 0 - ? String.format("MAIN:%2$s%1$s", body.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') - : ""; - var renderedSummary = summary.size() > 0 - ? String.format("SUMMARY:%2$s%1$s", summary.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') - : ""; - var renderedNavigation = navigation.size() > 0 - ? String.format("NAV:%2$s%1$s", navigation.stream().map(t -> t.script).collect(Collectors.joining("\n")), '\n') - : ""; + var renderedGlobals = renderSection("GLOBALS", globals); + var renderedTemplates = renderSection("TEMPLATES", fragments); + var renderedMain = renderSection("MAIN", body); + var renderedSummary = renderSection("SUMMARY", summary); + var renderedNavigation = renderSection("NAV", navigation); return new Markup(String.format("%1$s%6$s%2$s%6$s%3$s%6$s%4$s%6$s%5$s", renderedGlobals, @@ -184,6 +174,12 @@ public Markup composeOutputFile(List globals, List body, List markupList) { + return markupList.size() > 0 + ? String.format("%1$s:\n%2$s", heading, markupList.stream().map(t -> t.script).collect(Collectors.joining("\n"))) + : ""; + } + @Override public Markup getEfxDataTypeEquivalent(Class type) { return typeFromEfxDataType.getOrDefault(type, Markup.empty()); From 4611098bbe9e3e881b20d3ad657aa137eda4a54c Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Mon, 4 Aug 2025 10:28:32 +0200 Subject: [PATCH 08/10] EfxTemplateTranslatorV2Test: Reformat template lines for consistency Format the templates and expected results consistently across the file, to make them more readable. --- .../efx/sdk2/EfxTemplateTranslatorV2Test.java | 567 +++++++++++++----- 1 file changed, 409 insertions(+), 158 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java index 4a2a5b5..b030c4e 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -22,7 +22,8 @@ protected String getSdkVersion() { void testTemplateDefinition_InvokeTemplate() { assertEquals( lines( - "TEMPLATES:", "let some-template(string:content) -> { text('Content: ')eval($content) }", + "TEMPLATES:", + "let some-template(string:content) -> { text('Content: ')eval($content) }", "let body02() -> { call(some-template(string:content='test')) }", "MAIN:", "for-each(/*).call(body02())"), @@ -35,7 +36,8 @@ void testTemplateDefinition_InvokeTemplate() { void testTemplateDefinition_NestedInvokeTemplate() { assertEquals( lines( - "TEMPLATES:", "let other-template(string:value) -> { text('Value: ')eval($value) }", + "TEMPLATES:", + "let other-template(string:value) -> { text('Value: ')eval($value) }", "let invoke-template(string:param) -> { call(other-template(string:value=$param)) }", "let body03() -> { call(invoke-template(string:param='test')) }", "MAIN:", @@ -50,7 +52,8 @@ void testTemplateDefinition_NestedInvokeTemplate() { void testTemplateDefinition_ChooseTemplate() { assertEquals( lines( - "TEMPLATES:", "let conditional-template(boolean:condition, string:value) -> { choose { when $condition: text('Condition met: ')eval($value), when $condition = false(): text('Condition not met'), otherwise: text('Unknown condition: ')eval($condition) } }", + "TEMPLATES:", + "let conditional-template(boolean:condition, string:value) -> { choose { when $condition: text('Condition met: ')eval($value), when $condition = false(): text('Condition not met'), otherwise: text('Unknown condition: ')eval($condition) } }", "let body02() -> { call(conditional-template(boolean:condition=true(), string:value='test')) }", "MAIN:", "for-each(/*).call(body02())"), @@ -66,7 +69,8 @@ void testTemplateDefinition_ChooseTemplate() { void testTemplateDefinition_ParameterValidation() { assertEquals( lines( - "TEMPLATES:", "let param-validation-template(string:param1, decimal:param2) -> { text('Valid parameters') }", + "TEMPLATES:", + "let param-validation-template(string:param1, decimal:param2) -> { text('Valid parameters') }", "let body02() -> { call(param-validation-template(string:param1='test', decimal:param2=42)) }", "MAIN:", "for-each(/*).call(body02())"), @@ -83,7 +87,8 @@ void testTemplateDefinition_ParameterValidation() { void testTemplateDeclaration_NoParameters() { assertEquals( lines( - "TEMPLATES:", "let simple-template() -> { text('Hello World') }", + "TEMPLATES:", + "let simple-template() -> { text('Hello World') }", "let body02() -> { call(simple-template()) }", "MAIN:", "for-each(/*).call(body02())"), @@ -110,7 +115,8 @@ void testTemplateDeclaration_SingleParameter() { void testTemplateDeclaration_MultipleParameters() { assertEquals( lines( - "TEMPLATES:", "let complex-template(string:first, decimal:second, boolean:third) -> { text('Values: ')eval($first)text(', ')eval($second)text(', ')eval($third) }", + "TEMPLATES:", + "let complex-template(string:first, decimal:second, boolean:third) -> { text('Values: ')eval($first)text(', ')eval($second)text(', ')eval($third) }", "let body02() -> { call(complex-template(string:first='test', decimal:second=42, boolean:third=true())) }", "MAIN:", "for-each(/*).call(body02())"), @@ -123,7 +129,8 @@ void testTemplateDeclaration_MultipleParameters() { void testTemplateDeclaration_AllParameterTypes() { assertEquals( lines( - "TEMPLATES:", "let all-types-template(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('Params: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }", + "TEMPLATES:", + "let all-types-template(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('Params: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }", "let body02() -> { call(all-types-template(string:str='text', decimal:num=123, boolean:bool=true(), date:dt=xs:date('2023-01-01'), time:tm=xs:time('12:00:00'), duration:dur=xs:dayTimeDuration('P1D'))) }", "MAIN:", "for-each(/*).call(body02())"), @@ -136,7 +143,8 @@ void testTemplateDeclaration_AllParameterTypes() { void testTemplateDeclaration_SpecialCharactersInName() { assertEquals( lines( - "TEMPLATES:", "let template-with-dashes(string:param) -> { text('Template: ')eval($param) }", + "TEMPLATES:", + "let template-with-dashes(string:param) -> { text('Template: ')eval($param) }", "let body02() -> { call(template-with-dashes(string:param='test')) }", "MAIN:", "for-each(/*).call(body02())"), @@ -152,42 +160,66 @@ void testTemplateDeclaration_SpecialCharactersInName() { @Test void testTemplateFragment_TextAndExpression() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Value is: ')eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Value is: ')eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} Value is: ${BT-00-Text}")); } @Test void testTemplateFragment_TextAndLabel() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Field: ')label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Field: ')label(concat('field', '|', 'name', '|', 'BT-00-Text')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} Field: #{field|name|BT-00-Text}")); } @Test void testTemplateFragment_MixedContent() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Label: ')label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' Value: ')eval(./normalize-space(text()))text(' End') }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Label: ')label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' Value: ')eval(./normalize-space(text()))text(' End') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} Label: #{field|name|BT-00-Text} Value: ${BT-00-Text} End")); } @Test void testTemplateFragment_OnlyExpression() { assertEquals( - "TEMPLATES:\nlet body01() -> { eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} ${BT-00-Text}")); } @Test void testTemplateFragment_OnlyLabel() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{field|name|BT-00-Text}")); } @Test void testTemplateFragment_MultipleExpressions() { assertEquals( - "TEMPLATES:\nlet body01() -> { eval(./number())text(' + ')eval(./number())text(' = ')eval(./number() + ./number()) }\nMAIN:\nfor-each(/*/PathNode/NumberField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { eval(./number())text(' + ')eval(./number())text(' = ')eval(./number() + ./number()) }", + "MAIN:", + "for-each(/*/PathNode/NumberField).call(body01())"), translateTemplate("{BT-00-Number} ${BT-00-Number} + ${BT-00-Number} = ${BT-00-Number + BT-00-Number}")); } @@ -196,42 +228,66 @@ void testTemplateFragment_MultipleExpressions() { @Test void testTextBlock_SimpleText() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Simple text content') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Simple text content') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("display Simple text content;")); } @Test void testTextBlock_WithWhitespace() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Text with spaces') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Text with spaces') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("display Text with spaces ;")); } @Test void testTextBlock_WithSpecialCharacters() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Text with special chars: <>&"'') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Text with special chars: <>&"'') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("display Text with special chars: <>&\"';")); } @Test void testTextBlock_MultipleTextBlocks() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('First block ')eval('')text(' Second block') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('First block ')eval('')text(' Second block') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("display First block ${''} Second block;")); } @Test void testTextBlock_WithNewlines() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Line 1')line-break()text('Line 2')line-break()text('Line 3') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Line 1')line-break()text('Line 2')line-break()text('Line 3') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("display Line 1 \\nLine 2\\n Line 3;")); } @Test void testTextBlock_WithEscapedCharacters() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Text with quotes: "' and backslash: \\\\') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Text with quotes: "' and backslash: \\\\') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("display Text with quotes: \"' and backslash: \\\\;")); } @@ -242,21 +298,33 @@ void testTextBlock_WithEscapedCharacters() { @Test void testLinkedTextBlock() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('here is a ')hyperlink(text('Link'), 'http://example.com')text('. How about it?') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('here is a ')hyperlink(text('Link'), 'http://example.com')text('. How about it?') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("DISPLAY here is a Link@{'http://example.com'}. How about it?;")); } @Test void testLinkedLabelBlock() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('here is a linked label: ')hyperlink(label(concat('field', '|', 'name', '|', 'BT-00-Text')), 'http://example.com')text('. How about it?') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('here is a linked label: ')hyperlink(label(concat('field', '|', 'name', '|', 'BT-00-Text')), 'http://example.com')text('. How about it?') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("DISPLAY here is a linked label: #{field|name|BT-00-Text}@{'http://example.com'}. How about it?;")); } @Test void testLinkedExpressionBlock() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('here is a ')hyperlink(eval('multi word link'), 'http://example.com')text('. How about it?') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('here is a ')hyperlink(eval('multi word link'), 'http://example.com')text('. How about it?') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("DISPLAY here is a ${'multi word link'}@{'http://example.com'}. How about it?;")); } @@ -313,8 +381,7 @@ void testGlobals_VariableDeclaration() { void testGlobals_NamedTemplates() { assertEquals( lines( - - "GLOBALS:", + "GLOBALS:", "string:t3='a'", "decimal:n1=12", "string:t1=$t3", @@ -354,7 +421,8 @@ void testGlobals_NamedTemplates() { void testInvokeTemplate_Nesting() { assertEquals( lines( - "TEMPLATES:", "let some-template(string:t) -> { text('--')eval($t)text('--') }", + "TEMPLATES:", + "let some-template(string:t) -> { text('--')eval($t)text('--') }", "let body02(string:ctx1, string:tx) -> { #1: call(some-template(string:t=$tx))", "for-each(../StartDateField).call(body0201(string:ctx1=$ctx1, string:tx=$tx)) }", "let body0201(string:ctx1, string:tx) -> { text('Nested content allowed') }", @@ -370,7 +438,7 @@ void testInvokeTemplate_Nesting() { void testGlobals_NoTemplateLines() { assertEquals( lines( - "GLOBALS:", + "GLOBALS:", "string:t3='a'", "decimal:n1=12", "string:t1=$t3", @@ -408,7 +476,7 @@ void testGlobals_DictionaryDeclaration() { void testDisplayTemplate() { assertEquals( lines( - "GLOBALS:", + "GLOBALS:", "string:t='test'", "TEMPLATES:", "let body01() -> { text('this is a ')eval($t) }", @@ -424,7 +492,11 @@ void testDisplayTemplate() { @Test void testTemplateLine_NoIndentation() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('foo') }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} foo")); } @@ -433,15 +505,20 @@ void testTemplateLine_NoIndentation() { */ @Test void testTemplateLine_AutogeneratedOutline() { - assertEquals(lines("TEMPLATES:", - "let body01() -> { #1: text('Implicit 1 (shown)')", - "for-each(../..).call(body0101()) }", - "let body0101() -> { #1.1: text('Implicit 1.1 (shown)')", - "for-each(PathNode/NumberField).call(body010101()) }", - "let body010101() -> { text('Implicit 1.1.1 (hidden)') }", - "MAIN:", - "for-each(/*/PathNode/TextField).call(body01())"), // - translateTemplate(lines("{BT-00-Text} Implicit 1 (shown)", "\t{ND-Root} Implicit 1.1 (shown)", "\t\t{BT-00-Number} Implicit 1.1.1 (hidden)"))); + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { #1: text('Implicit 1 (shown)')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #1.1: text('Implicit 1.1 (shown)')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('Implicit 1.1.1 (hidden)') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // + translateTemplate(lines( + "{BT-00-Text} Implicit 1 (shown)", + "\t{ND-Root} Implicit 1.1 (shown)", + "\t\t{BT-00-Number} Implicit 1.1.1 (hidden)"))); } /** @@ -450,16 +527,20 @@ void testTemplateLine_AutogeneratedOutline() { */ @Test void testTemplateLine_ExplicitOutline() { - assertEquals(lines("TEMPLATES:", - "let body01() -> { #2: text('foo')", - "for-each(../..).call(body0101()) }", - "let body0101() -> { #2.3: text('bar')", - "for-each(PathNode/NumberField).call(body010101()) }", - "let body010101() -> { text('foo') }", - "MAIN:", - "for-each(/*/PathNode/TextField).call(body01())"), // - translateTemplate( - lines("2 {BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { #2: text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #2.3: text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // + translateTemplate(lines( + "2 {BT-00-Text} foo", + "\t3{ND-Root} bar", + "\t\t{BT-00-Number} foo"))); } /** @@ -469,15 +550,20 @@ void testTemplateLine_ExplicitOutline() { */ @Test void testTemplateLine_MixedOutline() { - assertEquals(lines("TEMPLATES:", - "let body01() -> { #2: text('foo')", - "for-each(../..).call(body0101()) }", - "let body0101() -> { #2.1: text('bar')", - "for-each(PathNode/NumberField).call(body010101()) }", - "let body010101() -> { text('foo') }", - "MAIN:", - "for-each(/*/PathNode/TextField).call(body01())"), // - translateTemplate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { #2: text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #2.1: text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // + translateTemplate(lines( + "2{BT-00-Text} foo", + "\t{ND-Root} bar", + "\t\t{BT-00-Number} foo"))); } /** @@ -487,16 +573,20 @@ void testTemplateLine_MixedOutline() { */ @Test void testTemplateLine_SuppressedOutline() { - assertEquals(lines("TEMPLATES:", - "let body01() -> { #2: text('foo')", - "for-each(../..).call(body0101()) }", - "let body0101() -> { text('bar')", - "for-each(PathNode/NumberField).call(body010101()) }", - "let body010101() -> { text('foo') }", - "MAIN:", - "for-each(/*/PathNode/TextField).call(body01())"), // - translateTemplate( - lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { #2: text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // + translateTemplate(lines( + "2{BT-00-Text} foo", + "\t0{ND-Root} bar", + "\t\t{BT-00-Number} foo"))); } /** @@ -508,15 +598,20 @@ void testTemplateLine_SuppressedOutline() { @Test void testTemplateLine_SuppressedOutlineAtParent() { // Outline is ignored if the line has no children - assertEquals(lines("TEMPLATES:", - "let body01() -> { text('foo')", - "for-each(../..).call(body0101()) }", - "let body0101() -> { #1: text('bar')", - "for-each(PathNode/NumberField).call(body010101()) }", - "let body010101() -> { text('foo') }", - "MAIN:", - "for-each(/*/PathNode/TextField).call(body01())"), // - translateTemplate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { text('foo')", + "for-each(../..).call(body0101()) }", + "let body0101() -> { #1: text('bar')", + "for-each(PathNode/NumberField).call(body010101()) }", + "let body010101() -> { text('foo') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), // + translateTemplate(lines( + "0{BT-00-Text} foo", + "\t{ND-Root} bar", + "\t\t{BT-00-Number} foo"))); } @Test @@ -527,7 +622,9 @@ void testTemplateLine_IndentationWithTabs() { "let body0101() -> { text('bar') }", // "MAIN:", // "for-each(/*/PathNode/TextField).call(body01())"), // - translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); + translateTemplate(lines( + "{BT-00-Text} foo", + "\t{BT-00-Text} bar"))); } @Test @@ -544,49 +641,58 @@ void testTemplateLine_IndentationWithSpaces() { @Test void testTemplateLine_LowerIndentation() { assertEquals( - lines("TEMPLATES:", + lines( + "TEMPLATES:", "let body01() -> { #1: text('foo')", "for-each(.).call(body0101()) }", "let body0101() -> { text('bar') }", "let body02() -> { text('code') }", "MAIN:", "for-each(/*/PathNode/TextField).call(body01())", "for-each(/*/PathNode/CodeField).call(body02())"), - translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar", "{BT-00-Code} code"))); + translateTemplate(lines( + "{BT-00-Text} foo", + "\t{BT-00-Text} bar", + "{BT-00-Code} code"))); } @Test void testTemplateLine_LineJoining() { assertEquals( - lines("TEMPLATES:", + lines( + "TEMPLATES:", "let body01() -> { #1: text('foo')", "for-each(.).call(body0101()) }", "let body0101() -> { text('bar joined more') }", "let body02() -> { text('code') }", "MAIN:", "for-each(/*/PathNode/TextField).call(body01())", "for-each(/*/PathNode/CodeField).call(body02())"), - translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar \\ \n joined \\\n\\\nmore", + translateTemplate(lines( + "{BT-00-Text} foo", + "\t{BT-00-Text} bar \\ \n joined \\\n\\\nmore", "{BT-00-Code} code"))); } @Test void testTemplateLine_VariableScope() { assertEquals( - lines("TEMPLATES:", // + lines( + "TEMPLATES:", // "let body01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // "for-each(.).call(body0101()) }", // "let body0101() -> { eval(for $x in ./normalize-space(text()) return $x) }", // "MAIN:", // "for-each(/*/PathNode/TextField).call(body01())"), // - translateTemplate(lines("{BT-00-Text} ${for text:$x in BT-00-Text return $x}", + translateTemplate(lines( + "{BT-00-Text} ${for text:$x in BT-00-Text return $x}", " {BT-00-Text} ${for text:$x in BT-00-Text return $x}"))); - } @Test void testTemplateLine_ContextVariable() { assertEquals( lines( - "TEMPLATES:", "let body01(string:xyz, string:ctx, string:t) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", // + "TEMPLATES:", + "let body01(string:xyz, string:ctx, string:t) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", // "for-each(.).call(body0101(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2='test'))", // "for-each(.).call(body0102(string:xyz=$xyz, string:ctx=$ctx, string:t=$t, string:t2='test3')) }", // "let body0101(string:xyz, string:ctx, string:t, string:t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", // @@ -638,21 +744,33 @@ void testTemplateLine_ContextDeclarationShortcuts() { @Test void testTemplateLine_SecondaryTemplate() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break()text('some text') }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break()text('some text') }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n some text")); } @Test void testTemplateLine_LineBreak() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break() }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))line-break() }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n")); } @Test void testTemplateLine_EndOfLineComments() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' blah blah') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' blah blah') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); } @@ -681,31 +799,32 @@ void testTemplateLine_Indentation_DeepNesting() { @Test void testOtherSections_SummarySection() { assertEquals( - lines("TEMPLATES:", - "let body01() -> { text('Summary: ')eval(./normalize-space(text())) }", - "let summary01() -> { text('Summary: ')eval(./normalize-space(text())) }", - "let summary02() -> { text('Summary: ')eval(./normalize-space(text())) }", - "let summary03() -> { text('Summary: ')eval(./normalize-space(text())) }", - "let nav01() -> { text('Summary: ')eval(./normalize-space(text())) }", - "let nav02() -> { text('Summary: ')eval(./normalize-space(text())) }", - "MAIN:", - "for-each(/*/PathNode/TextField).call(body01())", - "SUMMARY:", - "for-each(/*/PathNode/TextField).call(summary01())", - "for-each(/*/PathNode/TextField).call(summary02())", - "for-each(/*/PathNode/TextField).call(summary03())", - "NAV:", - "for-each(/*/PathNode/TextField).call(nav01())", - "for-each(/*/PathNode/TextField).call(nav02())" - ), - translateTemplate(lines("{BT-00-Text} Summary: ${BT-00-Text}", - "--- SUMMARY ---", - "{BT-00-Text} Summary: ${BT-00-Text}", - "{BT-00-Text} Summary: ${BT-00-Text}", - "{BT-00-Text} Summary: ${BT-00-Text}", - "--- NAVIGATION ---", - "{BT-00-Text} Summary: ${BT-00-Text}", - "{BT-00-Text} Summary: ${BT-00-Text}"))); + lines( + "TEMPLATES:", + "let body01() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let summary01() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let summary02() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let summary03() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let nav01() -> { text('Summary: ')eval(./normalize-space(text())) }", + "let nav02() -> { text('Summary: ')eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())", + "SUMMARY:", + "for-each(/*/PathNode/TextField).call(summary01())", + "for-each(/*/PathNode/TextField).call(summary02())", + "for-each(/*/PathNode/TextField).call(summary03())", + "NAV:", + "for-each(/*/PathNode/TextField).call(nav01())", + "for-each(/*/PathNode/TextField).call(nav02())"), + translateTemplate(lines( + "{BT-00-Text} Summary: ${BT-00-Text}", + "--- SUMMARY ---", + "{BT-00-Text} Summary: ${BT-00-Text}", + "{BT-00-Text} Summary: ${BT-00-Text}", + "{BT-00-Text} Summary: ${BT-00-Text}", + "--- NAVIGATION ---", + "{BT-00-Text} Summary: ${BT-00-Text}", + "{BT-00-Text} Summary: ${BT-00-Text}"))); } // #endregion otherSections ------------------------------------------------- @@ -715,42 +834,66 @@ void testOtherSections_SummarySection() { @Test void testLabelBlock_StandardLabelReference() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{field|name|BT-00-Text}")); } @Test void testLabelBlock_StandardLabelReferenceWithPluraliser() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'), ../NumberField/number()) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'), ../NumberField/number()) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{field|name|BT-00-Text;${BT-00-Number}}")); } @Test void testStandardLabelReference_UsingLabelTypeAsAssetId() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{auxiliary|text|value}")); } @Test void testLabelBlock_ComputedLabelReference() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(string-join(('field','|','name','|','BT-00-Text'), ', ')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(string-join(('field','|','name','|','BT-00-Text'), ', ')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{${string-join(('field', '|', 'name', '|', 'BT-00-Text'), ', ')}}")); } @Test void testLabelBlock_ShorthandBtLabelReference() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{name|BT-00}")); } @Test void testLabelBlock_ShorthandFieldLabelReference() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{name|BT-00-Text}")); } @@ -763,42 +906,66 @@ void testLabelBlock_ShorthandBtLabelReferenceMissingLabelType() { @Test void testLabelBlock_ShorthandIndirectLabelReferenceForIndicator() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{BT-00-Indicator}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForCode() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{BT-00-Code}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForInternalCode() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForCodeAttribute() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{BT-00-CodeAttribute}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceForCodeAttributeWithSameAttributeInContext() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField/@attribute).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }", + "MAIN:", + "for-each(/*/PathNode/CodeField/@attribute).call(body01())"), translateTemplate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }", + "MAIN:", + "for-each(/*/PathNode/CodeField).call(body01())"), translateTemplate("{BT-00-Code} #{BT-00-CodeAttribute}")); } @@ -817,14 +984,22 @@ void testShorthandIndirectLabelReferenceForAttribute() { @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndIndicatorField() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nMAIN:\nfor-each(/*/PathNode/IndicatorField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }", + "MAIN:", + "for-each(/*/PathNode/IndicatorField).call(body01())"), translateTemplate("{BT-00-Indicator} #{name}")); } @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndCodeField() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }", + "MAIN:", + "for-each(/*/PathNode/CodeField).call(body01())"), translateTemplate("{BT-00-Code} #{name}")); } @@ -837,7 +1012,11 @@ void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndTextField() { @Test void testShorthandLabelReferenceFromContext_WithOtherLabelType() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("{BT-00-Text} #{name}")); } @@ -850,14 +1029,22 @@ void testShorthandLabelReferenceFromContext_WithUnknownLabelType() { @Test void testLabelBlock_ShorthandLabelReferenceFromContext_WithNodeContext() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("{ND-Root} #{name}")); } @Test void testLabelBlock_ShorthandIndirectLabelReferenceFromContextField() { assertEquals( - "TEMPLATES:\nlet body01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }", + "MAIN:", + "for-each(/*/PathNode/CodeField).call(body01())"), translateTemplate("{BT-00-Code} #value")); } @@ -869,28 +1056,44 @@ void testLabelBlock_ShorthandIndirectLabelReferenceFromContextField_WithNodeCont @Test void testLabelBlock_Expression_AssetId() { assertEquals( - "TEMPLATES:\nlet body01(string:assetId) -> { label(concat('field', '|', 'name', '|', $assetId)) }\nMAIN:\nfor-each(/*).call(body01(string:assetId='BT-00-Text'))", + lines( + "TEMPLATES:", + "let body01(string:assetId) -> { label(concat('field', '|', 'name', '|', $assetId)) }", + "MAIN:", + "for-each(/*).call(body01(string:assetId='BT-00-Text'))"), translateTemplate("{/, text:$assetId='BT-00-Text'} #{field|name|${$assetId}}")); } @Test void testLabelBlock_Expression_LabelType() { assertEquals( - "TEMPLATES:\nlet body01(string:labelType) -> { label(concat('field', '|', $labelType, '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*).call(body01(string:labelType='name'))", + lines( + "TEMPLATES:", + "let body01(string:labelType) -> { label(concat('field', '|', $labelType, '|', 'BT-00-Text')) }", + "MAIN:", + "for-each(/*).call(body01(string:labelType='name'))"), translateTemplate("{/, text:$labelType='name'} #{field|${$labelType}|BT-00-Text}")); } @Test void testLabelBlock_Expression_AssetType() { assertEquals( - "TEMPLATES:\nlet body01(string:assetType) -> { label(concat($assetType, '|', 'name', '|', 'BT-00-Text')) }\nMAIN:\nfor-each(/*).call(body01(string:assetType='field'))", + lines( + "TEMPLATES:", + "let body01(string:assetType) -> { label(concat($assetType, '|', 'name', '|', 'BT-00-Text')) }", + "MAIN:", + "for-each(/*).call(body01(string:assetType='field'))"), translateTemplate("{/, text:$assetType='field'} #{${$assetType}|name|BT-00-Text}")); } @Test void testLabelBlock_Expression_NestedExpressions() { assertEquals( - "TEMPLATES:\nlet body01(string:assetType, string:labelType, string:assetId) -> { label(concat($assetType, '|', $labelType, '|', $assetId)) }\nMAIN:\nfor-each(/*).call(body01(string:assetType='field', string:labelType='name', string:assetId='BT-00-Text'))", + lines( + "TEMPLATES:", + "let body01(string:assetType, string:labelType, string:assetId) -> { label(concat($assetType, '|', $labelType, '|', $assetId)) }", + "MAIN:", + "for-each(/*).call(body01(string:assetType='field', string:labelType='name', string:assetId='BT-00-Text'))"), translateTemplate( "{/, text:$assetType='field', text:$labelType='name', text:$assetId='BT-00-Text'} #{${$assetType}|${$labelType}|${$assetId}}")); } @@ -902,14 +1105,22 @@ void testLabelBlock_Expression_NestedExpressions() { @Test void testExpressionBlock_ShorthandFieldValueReferenceFromContextField() { assertEquals( - "TEMPLATES:\nlet body01() -> { eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/CodeField).call(body01())"), translateTemplate("{BT-00-Code} $value")); } @Test void testExpressionBlock_ShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' blah ')eval(./normalize-space(text()))text(' blah') }\nMAIN:\nfor-each(/*/PathNode/CodeField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' blah ')eval(./normalize-space(text()))text(' blah') }", + "MAIN:", + "for-each(/*/PathNode/CodeField).call(body01())"), translateTemplate("{BT-00-Code} blah #value blah $value blah")); } @@ -925,28 +1136,44 @@ void testExpressionBlock_ShorthandFieldValueReferenceFromContextField_WithNodeCo @Test void testContextDeclarationBlock_ContextFieldVariable() { assertEquals( - "TEMPLATES:\nlet body01(string:ctx) -> { text('Context: ')eval($ctx) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:ctx=.))", + lines( + "TEMPLATES:", + "let body01(string:ctx) -> { text('Context: ')eval($ctx) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01(string:ctx=.))"), translateTemplate("{context:$ctx = BT-00-Text} Context: ${$ctx}")); } @Test void testContextDeclarationBlock_ContextNodeVariable() { assertEquals( - "TEMPLATES:\nlet body01(context:ctx) -> { text('Context: ')eval($ctx/PathNode/TextField/normalize-space(text())) }\nMAIN:\nfor-each(/*).call(body01(context:ctx=.))", + lines( + "TEMPLATES:", + "let body01(context:ctx) -> { text('Context: ')eval($ctx/PathNode/TextField/normalize-space(text())) }", + "MAIN:", + "for-each(/*).call(body01(context:ctx=.))"), translateTemplate("{context:$ctx = ND-Root} Context: ${$ctx::BT-00-Text}")); } @Test void testContextDeclarationBlock_MultipleVariables() { assertEquals( - "TEMPLATES:\nlet body01(string:var1, decimal:var2) -> { text('Variables: ')eval($var1)text(', ')eval($var2) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:var1='hello', decimal:var2=42))", + lines( + "TEMPLATES:", + "let body01(string:var1, decimal:var2) -> { text('Variables: ')eval($var1)text(', ')eval($var2) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01(string:var1='hello', decimal:var2=42))"), translateTemplate("{text:$var1='hello', number:$var2=42, BT-00-Text} Variables: ${$var1}, ${$var2}")); } @Test void testContextDeclarationBlock_VariableBeforeContext() { assertEquals( - "TEMPLATES:\nlet body01(string:prefix) -> { text('Prefix: ')eval($prefix)text(' Value: ')eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:prefix='test'))", + lines( + "TEMPLATES:", + "let body01(string:prefix) -> { text('Prefix: ')eval($prefix)text(' Value: ')eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01(string:prefix='test'))"), translateTemplate("{text:$prefix='test', BT-00-Text} Prefix: ${$prefix} Value: ${BT-00-Text}")); } @@ -964,7 +1191,8 @@ void testContextDeclarationBlock_OnlyVariables() { void testChooseTemplate_WhenBlock_MultipleConditions() { assertEquals( lines( - "TEMPLATES:", "let multi-when-template(string:status) -> { choose { when $status = 'active': text('Status: Active'), when $status = 'inactive': text('Status: Inactive'), when $status = 'pending': text('Status: Pending'), otherwise: text('Status: Unknown') } }", + "TEMPLATES:", + "let multi-when-template(string:status) -> { choose { when $status = 'active': text('Status: Active'), when $status = 'inactive': text('Status: Inactive'), when $status = 'pending': text('Status: Pending'), otherwise: text('Status: Unknown') } }", "let body02() -> { call(multi-when-template(string:status='active')) }", "MAIN:", "for-each(/*).call(body02())"), @@ -981,7 +1209,8 @@ void testChooseTemplate_WhenBlock_MultipleConditions() { void testChooseTemplate_WhenBlock_ComplexBooleanExpressions() { assertEquals( lines( - "TEMPLATES:", "let complex-when-template(decimal:value) -> { choose { when $value > 0 and $value < 100: text('In range'), when $value <= 0: text('Too low'), otherwise: text('Too high') } }", + "TEMPLATES:", + "let complex-when-template(decimal:value) -> { choose { when $value > 0 and $value < 100: text('In range'), when $value <= 0: text('Too low'), otherwise: text('Too high') } }", "let body02() -> { call(complex-when-template(decimal:value=50)) }", "MAIN:", "for-each(/*).call(body02())"), @@ -997,7 +1226,8 @@ void testChooseTemplate_WhenBlock_ComplexBooleanExpressions() { void testChooseTemplate_OtherwiseBlock_InvokeTemplate() { assertEquals( lines( - "TEMPLATES:", "let fallback-template(string:reason) -> { text('Fallback: ')eval($reason) }", + "TEMPLATES:", + "let fallback-template(string:reason) -> { text('Fallback: ')eval($reason) }", "let otherwise-invoke-template(boolean:condition, string:reason) -> { choose { when $condition: text('Condition met'), otherwise: call(fallback-template(string:reason=$reason)) } }", "let body03() -> { call(otherwise-invoke-template(boolean:condition=false(), string:reason='default')) }", "MAIN:", @@ -1013,7 +1243,8 @@ void testChooseTemplate_OtherwiseBlock_InvokeTemplate() { @Test void testChooseTemplate_WhenOtherwise() { assertEquals( - lines("TEMPLATES:", + lines( + "TEMPLATES:", "let some-template(string:txt) -> { text('>')eval($txt)text('<') }", "let body02(string:t) -> { choose { when 1 > 2: text('foo'), when 2 < 3: text('bar'), when 3 > 3: call(some-template(string:txt='1')), otherwise: text('foo-bar') } }", "let body03(string:t) -> { choose { when 1 > 2: text('foo'), when 2 < 3: text('bar'), when 3 > 3: text('foo-bar'), otherwise: call(some-template(string:txt='2')) } }", @@ -1038,7 +1269,8 @@ void testChooseTemplate_WhenOtherwise() { @Test void testChooseTemplate_WhenNoOtherwise() { assertEquals( - lines("GLOBALS:", + lines( + "GLOBALS:", "string:t='text'", "TEMPLATES:", "let body01() -> { choose { when true(): eval(./normalize-space(text()))text(' is a ')eval($t), otherwise nothing } }", @@ -1052,7 +1284,8 @@ void testChooseTemplate_WhenNoOtherwise() { @Test void testChooseTemplate_WhenNoOtherwiseNoContext() { assertEquals( - lines("GLOBALS:", + lines( + "GLOBALS:", "string:t='test'", "TEMPLATES:", "let body01() -> { choose { when true(): text('this is a ')eval($t), otherwise nothing } }", @@ -1070,7 +1303,11 @@ void testChooseTemplate_WhenNoOtherwiseNoContext() { @Test void testTemplateVariableList_WithAllDataTypes() { assertEquals( - "TEMPLATES:\nlet body01(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('All types: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }\nMAIN:\nfor-each(/*).call(body01(string:str='text', decimal:num=42, boolean:bool=true(), date:dt=xs:date('2023-01-01'), time:tm=xs:time('12:00:00'), duration:dur=xs:dayTimeDuration('P1D')))", + lines( + "TEMPLATES:", + "let body01(string:str, decimal:num, boolean:bool, date:dt, time:tm, duration:dur) -> { text('All types: ')eval($str)text(', ')eval($num)text(', ')eval($bool)text(', ')eval(for $item in $dt return format-date($item, '[D01]/[M01]/[Y0001]'))text(', ')eval(for $item in $tm return format-time($item, '[H01]:[m01] [Z]'))text(', ')eval($dur) }", + "MAIN:", + "for-each(/*).call(body01(string:str='text', decimal:num=42, boolean:bool=true(), date:dt=xs:date('2023-01-01'), time:tm=xs:time('12:00:00'), duration:dur=xs:dayTimeDuration('P1D')))"), translateTemplate( "{/, text:$str='text', number:$num=42, indicator:$bool=TRUE, date:$dt=date('2023-01-01'), time:$tm=time('12:00:00'), measure:$dur=day-time-duration('P1D')} All types: ${$str}, ${$num}, ${$bool}, ${$dt}, ${$tm}, ${$dur}")); } @@ -1078,7 +1315,11 @@ void testTemplateVariableList_WithAllDataTypes() { @Test void testTemplateVariableList_ExpressionInitializers() { assertEquals( - "TEMPLATES:\nlet body01(string:computed) -> { text('Computed: ')eval($computed) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01(string:computed=concat('prefix-', ./normalize-space(text()))))", + lines( + "TEMPLATES:", + "let body01(string:computed) -> { text('Computed: ')eval($computed) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01(string:computed=concat('prefix-', ./normalize-space(text()))))"), translateTemplate("{BT-00-Text, text:$computed=concat('prefix-', BT-00-Text)} Computed: ${$computed}")); } @@ -1089,14 +1330,22 @@ void testTemplateVariableList_ExpressionInitializers() { @Test void testTemplateLine_OutlineNumber_Only() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('text') }\nMAIN:\nfor-each(/*).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('text') }", + "MAIN:", + "for-each(/*).call(body01())"), translateTemplate("1 display text;")); } @Test void testTemplateLine_OutlineNumber_WithContext() { assertEquals( - "TEMPLATES:\nlet body01() -> { text('Value: ')eval(./normalize-space(text())) }\nMAIN:\nfor-each(/*/PathNode/TextField).call(body01())", + lines( + "TEMPLATES:", + "let body01() -> { text('Value: ')eval(./normalize-space(text())) }", + "MAIN:", + "for-each(/*/PathNode/TextField).call(body01())"), translateTemplate("1 {BT-00-Text} Value: ${BT-00-Text}")); } @@ -1189,17 +1438,19 @@ void testTemplateDefinition_InvalidTooFewParameters() { // #endregion IllegalArgumentException -------------------------------------- @Test - void testContextulizer_WithPredicate() { + void testContextualizer_WithPredicate() { assertEquals( - lines("TEMPLATES:", - "let body01() -> { #1: text('line1: ')eval(.[1 = 1]/normalize-space(text()))", + lines( + "TEMPLATES:", + "let body01() -> { #1: text('line1: ')eval(.[1 = 1]/normalize-space(text()))", "for-each(.[1 = 2]).call(body0101()) }", "let body0101() -> { #1.1: text('line2: ')eval(.[1 = 4]/normalize-space(text()))", "for-each(.[1 = 4]).call(body010101()) }", "let body010101() -> { text('line3: ')eval(.[1 = 5]/normalize-space(text())) }", "MAIN:", "for-each(/*/SubNode/SubSubNode/SubTextField[0 = 0]).call(body01())"), - translateTemplate(lines("{BT-01-SubSubNode-Text} line1: ${BT-01-SubSubNode-Text[1==1]}", + translateTemplate(lines( + "{BT-01-SubSubNode-Text} line1: ${BT-01-SubSubNode-Text[1==1]}", " {BT-01-SubSubNode-Text[1==2]} line2: ${BT-01-SubSubNode-Text[1==4]}", " {BT-01-SubSubNode-Text[1==4]} line3: ${BT-01-SubSubNode-Text[1==5]}"))); } @@ -1207,15 +1458,15 @@ void testContextulizer_WithPredicate() { @Test void testContextualizer_WithFieldInPredicate() { assertEquals( - lines("TEMPLATES:", + lines( + "TEMPLATES:", "let body01() -> { #1: text('line1')", "for-each(SubTextField).call(body0101()) }", "let body0101() -> { text('line2') }", "MAIN:", "for-each(/*/SubNode[SubTextField]).call(body01())"), - translateTemplate(lines("{ND-SubNode[BT-01-SubNode-Text is present]} line1", + translateTemplate(lines( + "{ND-SubNode[BT-01-SubNode-Text is present]} line1", " {BT-01-SubNode-Text} line2"))); } - - } \ No newline at end of file From cc5e06d52e23823993c90916aaa1029bb3eec4e8 Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Mon, 4 Aug 2025 10:37:11 +0200 Subject: [PATCH 09/10] TranslatorContext: Put field modifiers in the usual order --- .../java/eu/europa/ted/efx/interfaces/TranslatorContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java index b790542..49c7168 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorContext.java @@ -24,7 +24,7 @@ public void setCurrentSection(TemplateSection currentSection) { * Default instance of {@link TranslatorContext} with the section set to {@link TemplateSection#DEFAULT}. * This is used throughout the code for EFX-1 processing where only the default section is relevant. */ - final public static TranslatorContext DEFAULT = new TranslatorContext(); + public static final TranslatorContext DEFAULT = new TranslatorContext(); public TranslatorContext() { this(TemplateSection.DEFAULT); From acf8f0a93a14b0b3629db8968a02a8de0d0cef2e Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Mon, 4 Aug 2025 10:50:05 +0200 Subject: [PATCH 10/10] NodePathExpression: Fix code formatting --- .../ted/efx/model/expressions/path/NodePathExpression.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java index 6fb0dde..b81591f 100644 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java @@ -9,7 +9,8 @@ public class NodePathExpression extends PathExpression.Impl { public NodePathExpression(final String script) { super(script, EfxDataType.Node.class); } - public static NodePathExpression empty() { + + public static NodePathExpression empty() { return new NodePathExpression(""); } } \ No newline at end of file