From 14f1614d27786af1d6a4a399d111259343d1c52e Mon Sep 17 00:00:00 2001 From: Ioannis ROUSOCHATZAKIS Date: Tue, 18 Oct 2022 09:39:52 +0200 Subject: [PATCH 01/57] Added SDK 2.0.0 --- pom.xml | 14 +- .../efx/sdk2/EfxExpressionTranslatorV2.java | 1459 +++++++++++++++++ .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 543 ++++++ .../ted/efx/sdk2/entity/SdkCodelistV2.java | 19 + .../ted/efx/sdk2/entity/SdkFieldV2.java | 19 + .../europa/ted/efx/sdk2/entity/SdkNodeV2.java | 22 + 6 files changed, 2075 insertions(+), 1 deletion(-) create mode 100644 src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java create mode 100644 src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java create mode 100644 src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java create mode 100644 src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java create mode 100644 src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java diff --git a/pom.xml b/pom.xml index 1b6ced85..4d7b0d13 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ eu.europa.ted.eforms efx-toolkit-java - 1.1.1-SNAPSHOT + 2.0.0-SNAPSHOT jar EFX Toolkit for Java @@ -218,6 +218,18 @@ + + eu.europa.ted.eforms + eforms-sdk + 2.0.0-SNAPSHOT + jar + eforms-sdk/efx-grammar/**/*.g4 + ${sdk.antlr4.dir}/eu/europa/ted/efx/sdk2 + + + + + eu.europa.ted.eforms eforms-sdk diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java new file mode 100644 index 00000000..b336c738 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -0,0 +1,1459 @@ +package eu.europa.ted.efx.sdk2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +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; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponent; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponentType; +import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; +import eu.europa.ted.efx.interfaces.ScriptGenerator; +import eu.europa.ted.efx.interfaces.SymbolResolver; +import eu.europa.ted.efx.model.CallStack; +import eu.europa.ted.efx.model.CallStackObjectBase; +import eu.europa.ted.efx.model.Context; +import eu.europa.ted.efx.model.Context.FieldContext; +import eu.europa.ted.efx.model.Context.NodeContext; +import eu.europa.ted.efx.model.ContextStack; +import eu.europa.ted.efx.model.Expression; +import eu.europa.ted.efx.model.Expression.BooleanExpression; +import eu.europa.ted.efx.model.Expression.BooleanListExpression; +import eu.europa.ted.efx.model.Expression.ContextExpression; +import eu.europa.ted.efx.model.Expression.DateExpression; +import eu.europa.ted.efx.model.Expression.DateListExpression; +import eu.europa.ted.efx.model.Expression.DurationExpression; +import eu.europa.ted.efx.model.Expression.DurationListExpression; +import eu.europa.ted.efx.model.Expression.IteratorExpression; +import eu.europa.ted.efx.model.Expression.IteratorListExpression; +import eu.europa.ted.efx.model.Expression.ListExpression; +import eu.europa.ted.efx.model.Expression.NumericExpression; +import eu.europa.ted.efx.model.Expression.NumericListExpression; +import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.Expression.StringExpression; +import eu.europa.ted.efx.model.Expression.StringListExpression; +import eu.europa.ted.efx.model.Expression.TimeExpression; +import eu.europa.ted.efx.model.Expression.TimeListExpression; +import eu.europa.ted.efx.sdk2.EfxParser.*; +import eu.europa.ted.efx.xpath.XPathAttributeLocator; + +/** + * The the goal of the EfxExpressionTranslator is to take an EFX expression and translate it to a + * target scripting language. + * + * The target language syntax is not hardcoded into the translator so that this class can be reused + * to translate to several different languages. Instead a {@link ScriptGenerator} interface is used + * to provide specifics on the syntax of the target scripting language. + * + * Apart from writing expressions that can be translated and evaluated in a target scripting + * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that + * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The + * {@link EfxExpressionTranslatorV1} only focuses on EFX expressions. To translate EFX templates + * you need to use the {@link EfxTemplateTranslatorV1} which derives from this class. + */ +@VersionDependentComponent(versions = {"2"}, componentType = VersionDependentComponentType.EFX_EXPRESSION_TRANSLATOR) +public class EfxExpressionTranslatorV2 extends EfxBaseListener + implements EfxExpressionTranslator { + + private static final String NOT_MODIFIER = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); + + private static final String BEGIN_EXPRESSION_BLOCK = "{"; + private static final String END_EXPRESSION_BLOCK = "}"; + + /** + * + */ + private static final String TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES = + "Type mismatch. Cannot compare values of different types: "; + + /** + * The stack is used by the methods of this listener to pass data to each other as the parse tree + * is being walked. + */ + protected CallStack stack = new CallStack(); + + /** + * The context stack is used to keep track of context switching in nested expressions. + */ + protected ContextStack efxContext; + + /** + * Symbols are the field identifiers and node identifiers. The symbols map is used to resolve them + * to their location in the data source (typically their XPath). + */ + protected SymbolResolver symbols; + + protected BaseErrorListener errorListener; + + /** + * The ScriptGenerator is called to determine the target language syntax whenever needed. + */ + protected ScriptGenerator script; + + private LinkedList expressionParameters = new LinkedList<>(); + + protected EfxExpressionTranslatorV2() {} + + public EfxExpressionTranslatorV2(final SymbolResolver symbolResolver, + final ScriptGenerator scriptGenerator, final BaseErrorListener errorListener) { + this.symbols = symbolResolver; + this.script = scriptGenerator; + this.errorListener = errorListener; + + this.efxContext = new ContextStack(symbols); + } + + @Override + public String translateExpression(final String expression, final String... parameters) { + this.expressionParameters.addAll(Arrays.asList(parameters)); + + final EfxLexer lexer = + new EfxLexer(CharStreams.fromString(expression)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + final EfxParser parser = new EfxParser(tokens); + + if (errorListener != null) { + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + } + + final ParseTree tree = parser.singleExpression(); + final ParseTreeWalker walker = new ParseTreeWalker(); + + walker.walk(this, tree); + + return getTranslatedScript(); + } + + private T translateParameter(final String parameterValue, final Class parameterType) { + final EfxExpressionTranslatorV2 translator = new EfxExpressionTranslatorV2(this.symbols, this.script, + this.errorListener); + + final EfxLexer lexer = + new EfxLexer(CharStreams.fromString(BEGIN_EXPRESSION_BLOCK + parameterValue + END_EXPRESSION_BLOCK)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + final EfxParser parser = new EfxParser(tokens); + + if (errorListener != null) { + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + } + + final ParseTree tree = parser.parameterValue(); + final ParseTreeWalker walker = new ParseTreeWalker(); + + walker.walk(translator, tree); + + return Expression.instantiate(translator.getTranslatedScript(), parameterType); + } + + /** + * Used to get the translated target language script, after the walker finished its walk. + * + * @return The translated code, trimmed + */ + private String getTranslatedScript() { + final StringBuilder sb = new StringBuilder(this.stack.size() * 100); + while (!this.stack.empty()) { + sb.insert(0, '\n').insert(0, this.stack.pop(Expression.class).script); + } + return sb.toString().trim(); + } + + /** + * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a + * {@link SimpleFieldReferenceContext} to locate a field identifier. + */ + protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { + + if (ctx instanceof SimpleFieldReferenceContext) { + return ((SimpleFieldReferenceContext) ctx).FieldId().getText(); + } + + if (ctx instanceof AbsoluteFieldReferenceContext) { + return ((AbsoluteFieldReferenceContext) ctx).reference.reference.simpleFieldReference().FieldId() + .getText(); + } + + if (ctx instanceof FieldReferenceWithFieldContextOverrideContext) { + return ((FieldReferenceWithFieldContextOverrideContext) ctx).reference.reference.simpleFieldReference() + .FieldId().getText(); + } + + if (ctx instanceof FieldReferenceWithNodeContextOverrideContext) { + return ((FieldReferenceWithNodeContextOverrideContext) ctx).reference.reference + .reference.simpleFieldReference().FieldId().getText(); + } + + SimpleFieldReferenceContext fieldReferenceContext = + ctx.getChild(SimpleFieldReferenceContext.class, 0); + if (fieldReferenceContext != null) { + return fieldReferenceContext.FieldId().getText(); + } + + for (ParseTree child : ctx.children) { + if (child instanceof ParserRuleContext) { + String fieldId = getFieldIdFromChildSimpleFieldReferenceContext((ParserRuleContext) child); + if (fieldId != null) { + return fieldId; + } + } + } + + return null; + } + + /** + * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a + * {@link SimpleNodeReferenceContext} to locate a node identifier. + */ + protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { + + if (ctx instanceof SimpleNodeReferenceContext) { + return ((SimpleNodeReferenceContext) ctx).NodeId().getText(); + } + + for (ParseTree child : ctx.children) { + if (child instanceof ParserRuleContext) { + String nodeId = getNodeIdFromChildSimpleNodeReferenceContext((ParserRuleContext) child); + if (nodeId != null) { + return nodeId; + } + } + } + + return null; + } + + @Override + public void enterSingleExpression(SingleExpressionContext ctx) { + final TerminalNode fieldContext = ctx.FieldId(); + if (fieldContext != null) { + this.efxContext.pushFieldContext(fieldContext.getText()); + } else { + final TerminalNode nodeContext = ctx.NodeId(); + if (nodeContext != null) { + this.efxContext.pushNodeContext(nodeContext.getText()); + } + } + } + + @Override + public void exitSingleExpression(SingleExpressionContext ctx) { + this.efxContext.pop(); + } + + /*** Boolean expressions ***/ + + @Override + public void exitParenthesizedBooleanExpression( + EfxParser.ParenthesizedBooleanExpressionContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(BooleanExpression.class), BooleanExpression.class)); + } + + @Override + public void exitLogicalAndCondition(EfxParser.LogicalAndConditionContext ctx) { + BooleanExpression right = this.stack.pop(BooleanExpression.class); + BooleanExpression left = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeLogicalAnd(left, right)); + } + + @Override + public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { + BooleanExpression right = this.stack.pop(BooleanExpression.class); + BooleanExpression left = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeLogicalOr(left, right)); + } + + /*** Boolean expressions - Comparisons ***/ + + @Override + public void exitFieldValueComparison(FieldValueComparisonContext ctx) { + Expression right = this.stack.pop(Expression.class); + Expression left = this.stack.pop(Expression.class); + if (!left.getClass().equals(right.getClass())) { + throw new ParseCancellationException(TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES + + left.getClass() + " and " + right.getClass()); + } + this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitStringComparison(StringComparisonContext ctx) { + StringExpression right = this.stack.pop(StringExpression.class); + StringExpression left = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitNumericComparison(NumericComparisonContext ctx) { + NumericExpression right = this.stack.pop(NumericExpression.class); + NumericExpression left = this.stack.pop(NumericExpression.class); + this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitBooleanComparison(BooleanComparisonContext ctx) { + BooleanExpression right = this.stack.pop(BooleanExpression.class); + BooleanExpression left = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitDateComparison(DateComparisonContext ctx) { + DateExpression right = this.stack.pop(DateExpression.class); + DateExpression left = this.stack.pop(DateExpression.class); + this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitTimeComparison(TimeComparisonContext ctx) { + TimeExpression right = this.stack.pop(TimeExpression.class); + TimeExpression left = this.stack.pop(TimeExpression.class); + this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitDurationComparison(DurationComparisonContext ctx) { + DurationExpression right = this.stack.pop(DurationExpression.class); + DurationExpression left = this.stack.pop(DurationExpression.class); + this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); + } + + /*** Boolean expressions - Conditions ***/ + + @Override + public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { + StringExpression expression = this.stack.pop(StringExpression.class); + String operator = + ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; + this.stack.push(this.script.composeComparisonOperation(expression, operator, + this.script.getStringLiteralFromUnquotedString(""))); + } + + @Override + public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { + PathExpression reference = this.stack.pop(PathExpression.class); + if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { + this.stack.push(this.script.composeLogicalNot(this.script.composeExistsCondition(reference))); + } else { + this.stack.push(this.script.composeExistsCondition(reference)); + } + } + + @Override + public void exitUniqueValueCondition(EfxParser.UniqueValueConditionContext ctx) { + PathExpression haystack = this.stack.pop(PathExpression.class); + PathExpression needle = this.stack.pop(PathExpression.class); + + if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { + this.stack.push( + this.script.composeLogicalNot(this.script.composeUniqueValueCondition(needle, haystack))); + } else { + this.stack.push(this.script.composeUniqueValueCondition(needle, haystack)); + } + } + + @Override + public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { + StringExpression expression = this.stack.pop(StringExpression.class); + + BooleanExpression condition = + this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); + if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { + condition = this.script.composeLogicalNot(condition); + } + this.stack.push(condition); + } + + /*** Boolean expressions - List membership conditions ***/ + + @Override + public void exitStringInListCondition(EfxParser.StringInListConditionContext ctx) { + this.exitInListCondition(ctx.modifier, StringExpression.class, StringListExpression.class); + } + + @Override + public void exitBooleanInListCondition(BooleanInListConditionContext ctx) { + this.exitInListCondition(ctx.modifier, BooleanExpression.class, BooleanListExpression.class); + } + + @Override + public void exitNumberInListCondition(NumberInListConditionContext ctx) { + this.exitInListCondition(ctx.modifier, NumericExpression.class, NumericListExpression.class); + } + + @Override + public void exitDateInListCondition(DateInListConditionContext ctx) { + this.exitInListCondition(ctx.modifier, DateExpression.class, DateListExpression.class); + } + + @Override + public void exitTimeInListCondition(TimeInListConditionContext ctx) { + this.exitInListCondition(ctx.modifier, TimeExpression.class, TimeListExpression.class); + } + + @Override + public void exitDurationInListCondition(DurationInListConditionContext ctx) { + this.exitInListCondition(ctx.modifier, DurationExpression.class, DurationListExpression.class); + } + + private > void exitInListCondition( + Token modifier, Class expressionType, Class listType) { + ListExpression list = this.stack.pop(listType); + T expression = this.stack.pop(expressionType); + BooleanExpression condition = this.script.composeContainsCondition(expression, list); + if (modifier != null && modifier.getText().equals(NOT_MODIFIER)) { + condition = this.script.composeLogicalNot(condition); + } + this.stack.push(condition); + } + + /*** Quantified expressions ***/ + + @Override + public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { + BooleanExpression booleanExpression = this.stack.pop(BooleanExpression.class); + if (ctx.Every() != null) { + this.stack.push(this.script.composeAllSatisfy(this.stack.pop(IteratorListExpression.class), + booleanExpression)); + } else { + this.stack.push(this.script.composeAnySatisfies(this.stack.pop(IteratorListExpression.class), + booleanExpression)); + } + } + + /*** Numeric expressions ***/ + + @Override + public void exitAdditionExpression(EfxParser.AdditionExpressionContext ctx) { + NumericExpression right = this.stack.pop(NumericExpression.class); + NumericExpression left = this.stack.pop(NumericExpression.class); + this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitMultiplicationExpression(EfxParser.MultiplicationExpressionContext ctx) { + NumericExpression right = this.stack.pop(NumericExpression.class); + NumericExpression left = this.stack.pop(NumericExpression.class); + this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); + } + + @Override + public void exitParenthesizedNumericExpression(ParenthesizedNumericExpressionContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(NumericExpression.class), NumericExpression.class)); + } + + /*** Duration Expressions ***/ + + @Override + public void exitDurationAdditionExpression(DurationAdditionExpressionContext ctx) { + DurationExpression right = this.stack.pop(DurationExpression.class); + DurationExpression left = this.stack.pop(DurationExpression.class); + this.stack.push(this.script.composeAddition(left, right)); + } + + @Override + public void exitDurationSubtractionExpression(DurationSubtractionExpressionContext ctx) { + DurationExpression right = this.stack.pop(DurationExpression.class); + DurationExpression left = this.stack.pop(DurationExpression.class); + this.stack.push(this.script.composeSubtraction(left, right)); + } + + @Override + public void exitDurationLeftMultiplicationExpression( + DurationLeftMultiplicationExpressionContext ctx) { + DurationExpression duration = this.stack.pop(DurationExpression.class); + NumericExpression number = this.stack.pop(NumericExpression.class); + this.stack.push(this.script.composeMultiplication(number, duration)); + } + + @Override + public void exitDurationRightMultiplicationExpression( + DurationRightMultiplicationExpressionContext ctx) { + NumericExpression number = this.stack.pop(NumericExpression.class); + DurationExpression duration = this.stack.pop(DurationExpression.class); + this.stack.push(this.script.composeMultiplication(number, duration)); + } + + @Override + public void exitDateSubtractionExpression(DateSubtractionExpressionContext ctx) { + final DateExpression startDate = this.stack.pop(DateExpression.class); + final DateExpression endDate = this.stack.pop(DateExpression.class); + this.stack.push(this.script.composeSubtraction(startDate, endDate)); + } + + @Override + public void exitCodeList(CodeListContext ctx) { + if (this.stack.empty()) { + this.stack.push(this.script.composeList(Collections.emptyList(), StringListExpression.class)); + } + } + + @Override + public void exitStringList(StringListContext ctx) { + this.exitList(ctx.stringExpression().size(), StringExpression.class, + StringListExpression.class); + } + + @Override + public void exitBooleanList(BooleanListContext ctx) { + this.exitList(ctx.booleanExpression().size(), BooleanExpression.class, + BooleanListExpression.class); + } + + @Override + public void exitNumericList(NumericListContext ctx) { + this.exitList(ctx.numericExpression().size(), NumericExpression.class, + NumericListExpression.class); + } + + @Override + public void exitDateList(DateListContext ctx) { + this.exitList(ctx.dateExpression().size(), DateExpression.class, DateListExpression.class); + } + + @Override + public void exitTimeList(TimeListContext ctx) { + this.exitList(ctx.timeExpression().size(), TimeExpression.class, TimeListExpression.class); + } + + + @Override + public void exitDurationList(DurationListContext ctx) { + this.exitList(ctx.durationExpression().size(), DurationExpression.class, + DurationListExpression.class); + } + + private > void exitList(int listSize, + Class expressionType, Class listType) { + if (this.stack.empty() || listSize == 0) { + this.stack.push(this.script.composeList(Collections.emptyList(), listType)); + return; + } + + List list = new ArrayList<>(); + for (int i = 0; i < listSize; i++) { + list.add(0, this.stack.pop(expressionType)); + } + this.stack.push(this.script.composeList(list, listType)); + } + + /*** Conditional Expressions ***/ + + @Override + public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) { + Class typeWhenFalse = this.stack.peek().getClass(); + if (typeWhenFalse == BooleanExpression.class) { + this.exitConditionalBooleanExpression(); + } else if (typeWhenFalse == NumericExpression.class) { + this.exitConditionalNumericExpression(); + } else if (typeWhenFalse == StringExpression.class) { + this.exitConditionalStringExpression(); + } else if (typeWhenFalse == DateExpression.class) { + this.exitConditionalDateExpression(); + } else if (typeWhenFalse == TimeExpression.class) { + this.exitConditionalTimeExpression(); + } else if (typeWhenFalse == DurationExpression.class) { + this.exitConditionalDurationExpression(); + } else { + throw new IllegalStateException("Unknown type " + typeWhenFalse); + } + } + + @Override + public void exitConditionalBooleanExpression(ConditionalBooleanExpressionContext ctx) { + this.exitConditionalBooleanExpression(); + } + + @Override + public void exitConditionalNumericExpression(ConditionalNumericExpressionContext ctx) { + this.exitConditionalNumericExpression(); + } + + @Override + public void exitConditionalStringExpression(ConditionalStringExpressionContext ctx) { + this.exitConditionalStringExpression(); + } + + @Override + public void exitConditionalDateExpression(ConditionalDateExpressionContext ctx) { + this.exitConditionalDateExpression(); + } + + @Override + public void exitConditionalTimeExpression(ConditionalTimeExpressionContext ctx) { + this.exitConditionalTimeExpression(); + } + + @Override + public void exitConditionalDurationExpression(ConditionalDurationExpressionContext ctx) { + this.exitConditionalDurationExpression(); + } + + private void exitConditionalBooleanExpression() { + BooleanExpression whenFalse = this.stack.pop(BooleanExpression.class); + BooleanExpression whenTrue = this.stack.pop(BooleanExpression.class); + BooleanExpression condition = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, + BooleanExpression.class)); + } + + private void exitConditionalNumericExpression() { + NumericExpression whenFalse = this.stack.pop(NumericExpression.class); + NumericExpression whenTrue = this.stack.pop(NumericExpression.class); + BooleanExpression condition = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, + NumericExpression.class)); + } + + private void exitConditionalStringExpression() { + StringExpression whenFalse = this.stack.pop(StringExpression.class); + StringExpression whenTrue = this.stack.pop(StringExpression.class); + BooleanExpression condition = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, + StringExpression.class)); + } + + private void exitConditionalDateExpression() { + DateExpression whenFalse = this.stack.pop(DateExpression.class); + DateExpression whenTrue = this.stack.pop(DateExpression.class); + BooleanExpression condition = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, + DateExpression.class)); + } + + private void exitConditionalTimeExpression() { + TimeExpression whenFalse = this.stack.pop(TimeExpression.class); + TimeExpression whenTrue = this.stack.pop(TimeExpression.class); + BooleanExpression condition = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, + TimeExpression.class)); + } + + private void exitConditionalDurationExpression() { + DurationExpression whenFalse = this.stack.pop(DurationExpression.class); + DurationExpression whenTrue = this.stack.pop(DurationExpression.class); + BooleanExpression condition = this.stack.pop(BooleanExpression.class); + this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, + DurationExpression.class)); + } + + /*** Iterators ***/ + + @Override + public void exitStringIteratorExpression(StringIteratorExpressionContext ctx) { + this.exitIteratorExpression(StringExpression.class, StringListExpression.class); + } + + @Override + public void exitBooleanIteratorExpression(BooleanIteratorExpressionContext ctx) { + this.exitIteratorExpression(BooleanExpression.class, BooleanListExpression.class); + } + + @Override + public void exitNumericIteratorExpression(NumericIteratorExpressionContext ctx) { + this.exitIteratorExpression(NumericExpression.class, NumericListExpression.class); + } + + @Override + public void exitDateIteratorExpression(DateIteratorExpressionContext ctx) { + this.exitIteratorExpression(DateExpression.class, DateListExpression.class); + } + + @Override + public void exitTimeIteratorExpression(TimeIteratorExpressionContext ctx) { + this.exitIteratorExpression(TimeExpression.class, TimeListExpression.class); + } + + @Override + public void exitDurationIteratorExpression(DurationIteratorExpressionContext ctx) { + this.exitIteratorExpression(DurationExpression.class, DurationListExpression.class); + } + + @Override + public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx) { + PathExpression path = this.stack.pop(PathExpression.class); + ContextExpression variable = this.stack.pop(ContextExpression.class); + this.stack.push(this.script.composeIteratorExpression(variable.script, path)); + if (ctx.fieldContext() != null) { + final String contextFieldId = + getFieldIdFromChildSimpleFieldReferenceContext(ctx.fieldContext()); + this.efxContext.declareContextVariable(variable.script, + new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), + this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); + + } else if (ctx.nodeContext() != null) { + final String contextNodeId = + getNodeIdFromChildSimpleNodeReferenceContext(ctx.nodeContext()); + this.efxContext.declareContextVariable(variable.script, + new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), + this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); + } + } + + @Override + public void exitIteratorList(IteratorListContext ctx) { + List iterators = new ArrayList<>(); + for (int i = 0; i < ctx.iteratorExpression().size(); i++) { + iterators.add(0, this.stack.pop(IteratorExpression.class)); + } + this.stack.push(this.script.composeIteratorList(iterators)); + } + + @Override + public void exitParenthesizedStringsFromIteration(ParenthesizedStringsFromIterationContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(StringListExpression.class), StringListExpression.class)); + } + + @Override + public void exitParenthesizedNumbersFromIteration(ParenthesizedNumbersFromIterationContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(NumericListExpression.class), NumericListExpression.class)); + } + + @Override + public void exitParenthesizedBooleansFromIteration( + ParenthesizedBooleansFromIterationContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(BooleanListExpression.class), BooleanListExpression.class)); + } + + @Override + public void exitParenthesizedDatesFromIteration(ParenthesizedDatesFromIterationContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(DateListExpression.class), DateListExpression.class)); + } + + @Override + public void exitParenthesizedTimesFromIteration(ParenthesizedTimesFromIterationContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(TimeListExpression.class), TimeListExpression.class)); + } + + @Override + public void exitParenthesizedDurationsFromITeration( + ParenthesizedDurationsFromITerationContext ctx) { + this.stack.push(this.script.composeParenthesizedExpression( + this.stack.pop(DurationListExpression.class), DurationListExpression.class)); + } + + @Override + public void exitStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { + this.exitIterationExpression(StringExpression.class, StringListExpression.class); + } + + @Override + public void exitNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { + this.exitIterationExpression(NumericExpression.class, NumericListExpression.class); + } + + @Override + public void exitBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { + this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class); + } + + @Override + public void exitDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { + this.exitIterationExpression(DateExpression.class, DateListExpression.class); + } + + @Override + public void exitTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { + this.exitIterationExpression(TimeExpression.class, TimeListExpression.class); + } + + @Override + public void exitDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { + this.exitIterationExpression(DurationExpression.class, DurationListExpression.class); + } + + public > void exitIteratorExpression( + Class variableType, Class listType) { + L list = this.stack.pop(listType); + T variable = this.stack.pop(variableType); + this.stack.push(this.script.composeIteratorExpression(variable.script, list)); + } + + public > void exitIterationExpression(Class expressionType, + Class targetListType) { + T expression = this.stack.pop(expressionType); + IteratorListExpression iterators = this.stack.pop(IteratorListExpression.class); + this.stack + .push(this.script.composeForExpression(iterators, expression, targetListType)); + } + + /*** Literals ***/ + + @Override + public void exitNumericLiteral(NumericLiteralContext ctx) { + this.stack.push(this.script.getNumericLiteralEquivalent(ctx.getText())); + } + + @Override + public void exitStringLiteral(StringLiteralContext ctx) { + this.stack.push(this.script.getStringLiteralEquivalent(ctx.getText())); + } + + @Override + public void exitTrueBooleanLiteral(TrueBooleanLiteralContext ctx) { + this.stack.push(this.script.getBooleanEquivalent(true)); + } + + @Override + public void exitFalseBooleanLiteral(FalseBooleanLiteralContext ctx) { + this.stack.push(this.script.getBooleanEquivalent(false)); + } + + @Override + public void exitDateLiteral(DateLiteralContext ctx) { + this.stack.push(this.script.getDateLiteralEquivalent(ctx.DATE().getText())); + } + + @Override + public void exitTimeLiteral(TimeLiteralContext ctx) { + this.stack.push(this.script.getTimeLiteralEquivalent(ctx.TIME().getText())); + } + + @Override + public void exitDurationLiteral(DurationLiteralContext ctx) { + this.stack.push(this.script.getDurationLiteralEquivalent(ctx.getText())); + } + + /*** References ***/ + + @Override + public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { + this.stack.push( + this.symbols.getRelativePathOfNode(ctx.NodeId().getText(), this.efxContext.absolutePath())); + } + + @Override + public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx) { + this.stack.push( + symbols.getRelativePathOfField(ctx.FieldId().getText(), this.efxContext.absolutePath())); + } + + @Override + public void enterAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { + if (ctx.Slash() != null) { + this.efxContext.push(null); + } + } + + @Override + public void exitAbsoluteFieldReference(EfxParser.AbsoluteFieldReferenceContext ctx) { + if (ctx.Slash() != null) { + this.efxContext.pop(); + } + } + + @Override + public void enterAbsoluteNodeReference(EfxParser.AbsoluteNodeReferenceContext ctx) { + if (ctx.Slash() != null) { + this.efxContext.push(null); + } + } + + @Override + public void exitAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { + if (ctx.Slash() != null) { + this.efxContext.pop(); + } + } + + + /*** References with Predicates ***/ + + @Override + public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx) { + if (ctx.predicate() != null) { + BooleanExpression predicate = this.stack.pop(BooleanExpression.class); + PathExpression nodeReference = this.stack.pop(PathExpression.class); + this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate, + PathExpression.class)); + } + } + + @Override + public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicateContext ctx) { + if (ctx.predicate() != null) { + BooleanExpression predicate = this.stack.pop(BooleanExpression.class); + PathExpression fieldReference = this.stack.pop(PathExpression.class); + this.stack.push(this.script.composeFieldReferenceWithPredicate(fieldReference, predicate, + PathExpression.class)); + } + } + + /** + * Any field references in the predicate must be resolved relative to the node or field on which + * the predicate is applied. Therefore we need to switch to that context while the predicate is + * being parsed. + */ + @Override + public void enterPredicate(EfxParser.PredicateContext ctx) { + final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx.getParent()); + if (nodeId != null) { + this.efxContext.pushNodeContext(nodeId); + } else { + final String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx.getParent()); + this.efxContext.pushFieldContext(fieldId); + } + } + + /** + * After the predicate is parsed we need to switch back to the previous context. + */ + @Override + public void exitPredicate(EfxParser.PredicateContext ctx) { + this.efxContext.pop(); + } + + @Override + public void exitFieldReferenceWithAxis(FieldReferenceWithAxisContext ctx) { + if (ctx.axis() != null) { + this.stack.push(this.script.composeFieldReferenceWithAxis( + this.stack.pop(PathExpression.class), ctx.axis().Axis().getText(), PathExpression.class)); + } + } + + /*** External References ***/ + + @Override + public void exitNoticeReference(EfxParser.NoticeReferenceContext ctx) { + this.stack.push(this.script.composeExternalReference(this.stack.pop(StringExpression.class))); + } + + @Override + public void enterFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext ctx) { + if (ctx.noticeReference() != null) { + // We push a null context as we switch to an external notice and we need XPaths to be absolute + this.efxContext.push(null); + } + } + + @Override + public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNoticeContext ctx) { + if (ctx.noticeReference() != null) { + PathExpression field = this.stack.pop(PathExpression.class); + PathExpression notice = this.stack.pop(PathExpression.class); + this.stack.push(this.script.composeFieldInExternalReference(notice, field)); + + // Finally, pop the null context we pushed during enterFieldReferenceInOtherNotice + this.efxContext.pop(); + } + } + + /*** Value References ***/ + + @Override + public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { + + PathExpression path = this.stack.pop(PathExpression.class); + String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + // TODO: Use an interface for locating attributes. A PathExpression is not necessarily an + // XPath in every implementation. + XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); + + if (parsedPath.hasAttribute()) { + this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), + parsedPath.getAttribute(), Expression.types.get(this.symbols.getTypeOfField(fieldId)))); + } else if (fieldId != null) { + this.stack.push(this.script.composeFieldValueReference(path, + Expression.types.get(this.symbols.getTypeOfField(fieldId)))); + } else { + this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); + } + } + + @Override + public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx) { + PathExpression path = this.stack.pop(PathExpression.class); + String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + // TODO: Use an interface for locating attributes. A PathExpression is not necessarily an + // XPath in every implementation. + XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); + + if (parsedPath.hasAttribute()) { + this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), + parsedPath.getAttribute(), + Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); + } else if (fieldId != null) { + this.stack.push(this.script.composeFieldValueReference(path, + Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); + } else { + this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); + } + } + + @Override + public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext ctx) { + this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), + ctx.attributeReference().Identifier().getText(), StringExpression.class)); + } + + /*** References with context override ***/ + + /** + * Handles expressions of the form ContextField::ReferencedField. Changes the context before the + * reference is resolved. + */ + @Override + public void exitContextFieldSpecifier(ContextFieldSpecifierContext ctx) { + this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for + // the context field. + final String contextFieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx.field); + this.efxContext + .push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), + this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); + } + + + /** + * Handles expressions of the form ContextField::ReferencedField. Changes the context before the + * reference is resolved. + */ + @Override + public void exitFieldReferenceWithFieldContextOverride( + FieldReferenceWithFieldContextOverrideContext ctx) { + if (ctx.contextFieldSpecifier() != null) { + final PathExpression field = this.stack.pop(PathExpression.class); + this.stack.push(this.script.joinPaths(this.efxContext.relativePath(), field)); + this.efxContext.pop(); // Restores the previous context + } + } + + /** + * Handles expressions of the form ContextNode::ReferencedField. Changes the context before the + * reference is resolved. + */ + @Override + public void exitContextNodeSpecifier(ContextNodeSpecifierContext ctx) { + this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for + // the context node. + final String contextNodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx.node); + this.efxContext + .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), + this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); + } + + /** + * Handles expressions of the form ContextNode::ReferencedField. Restores the context after the + * reference is resolved. + */ + @Override + public void exitFieldReferenceWithNodeContextOverride( + FieldReferenceWithNodeContextOverrideContext ctx) { + if (ctx.contextNodeSpecifier() != null) { + final PathExpression field = this.stack.pop(PathExpression.class); + this.stack.push(this.script.joinPaths(this.efxContext.relativePath(), field)); + this.efxContext.pop(); // Restores the previous context + } + } + + @Override + public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { + ContextExpression variableName = this.stack.pop(ContextExpression.class); + Context variableContext = this.efxContext.getContextFromVariable(variableName.script); + if (variableContext.isFieldContext()) { + this.efxContext.push(new FieldContext(variableContext.symbol(), + this.symbols.getAbsolutePathOfField(variableContext.symbol()), this.symbols + .getRelativePathOfField(variableContext.symbol(), this.efxContext.absolutePath()))); + } else if (variableContext.isNodeContext()) { + this.efxContext.push(new NodeContext(variableContext.symbol(), + this.symbols.getAbsolutePathOfNode(variableContext.symbol()), this.symbols + .getRelativePathOfNode(variableContext.symbol(), this.efxContext.absolutePath()))); + } else { + throw new IllegalStateException("Variable context is neither a field nor a node context."); + } + this.stack.push(variableName); + } + + @Override + public void exitFieldReferenceWithVariableContextOverride( + FieldReferenceWithVariableContextOverrideContext ctx) { + if (ctx.contextVariableSpecifier() != null) { + final PathExpression field = this.stack.pop(PathExpression.class); + final ContextExpression variableName = this.stack.pop(ContextExpression.class); + this.stack.push(this.script.joinPaths(new PathExpression(variableName.script), field)); + this.efxContext.pop(); // Restores the previous context + } + } + + /*** Other References ***/ + + @Override + public void exitCodelistReference(CodelistReferenceContext ctx) { + this.stack.push(this.script.composeList(this.symbols.expandCodelist(ctx.codeListId.getText()) + .stream().map(s -> this.script.getStringLiteralFromUnquotedString(s)) + .collect(Collectors.toList()), StringListExpression.class)); + } + + @Override + public void exitVariableReference(VariableReferenceContext ctx) { + this.stack.pushVariableReference(ctx.Variable().getText(), + this.script.composeVariableReference(ctx.Variable().getText(), Expression.class)); + } + + /*** Parameter Declarations ***/ + + + @Override + public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.Variable().getText(), StringExpression.class); + } + + @Override + public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.Variable().getText(), NumericExpression.class); + } + + @Override + public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.Variable().getText(), BooleanExpression.class); + } + + @Override + public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.Variable().getText(), DateExpression.class); + } + + @Override + public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.Variable().getText(), TimeExpression.class); + } + + @Override + public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.Variable().getText(), DurationExpression.class); + } + + private void exitParameterDeclaration(String parameterName, Class parameterType) { + if (this.expressionParameters.isEmpty()) { + throw new ParseCancellationException("No parameter passed for " + parameterName); + } + + this.stack.pushParameterDeclaration(parameterName, + this.script.composeParameterDeclaration(parameterName, parameterType), + this.translateParameter(this.expressionParameters.pop(), parameterType)); + } + + /*** Variable Declarations ***/ + + @Override + public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { + this.stack.pushVariableDeclaration(ctx.Variable().getText(), + this.script.composeVariableDeclaration(ctx.Variable().getText(), StringExpression.class)); + } + + @Override + public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { + this.stack.pushVariableDeclaration(ctx.Variable().getText(), + this.script.composeVariableDeclaration(ctx.Variable().getText(), BooleanExpression.class)); + } + + @Override + public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { + this.stack.pushVariableDeclaration(ctx.Variable().getText(), + this.script.composeVariableDeclaration(ctx.Variable().getText(), NumericExpression.class)); + } + + @Override + public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { + this.stack.pushVariableDeclaration(ctx.Variable().getText(), + this.script.composeVariableDeclaration(ctx.Variable().getText(), DateExpression.class)); + } + + @Override + public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { + this.stack.pushVariableDeclaration(ctx.Variable().getText(), + this.script.composeVariableDeclaration(ctx.Variable().getText(), TimeExpression.class)); + } + + @Override + public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { + this.stack.pushVariableDeclaration(ctx.Variable().getText(), + this.script.composeVariableDeclaration(ctx.Variable().getText(), DurationExpression.class)); + } + + @Override + public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) { + this.stack.pushVariableDeclaration(ctx.Variable().getText(), + this.script.composeVariableDeclaration(ctx.Variable().getText(), ContextExpression.class)); + } + + /*** Boolean functions ***/ + + @Override + public void exitNotFunction(NotFunctionContext ctx) { + this.stack.push(this.script.composeLogicalNot(this.stack.pop(BooleanExpression.class))); + } + + @Override + public void exitContainsFunction(ContainsFunctionContext ctx) { + final StringExpression needle = this.stack.pop(StringExpression.class); + final StringExpression haystack = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeContainsCondition(haystack, needle)); + } + + @Override + public void exitStartsWithFunction(StartsWithFunctionContext ctx) { + final StringExpression startsWith = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeStartsWithCondition(text, startsWith)); + } + + @Override + public void exitEndsWithFunction(EndsWithFunctionContext ctx) { + final StringExpression endsWith = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeEndsWithCondition(text, endsWith)); + } + + @Override + public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { + final ListExpression two = this.stack.pop(ListExpression.class); + final ListExpression one = this.stack.pop(ListExpression.class); + this.stack.push(this.script.composeSequenceEqualFunction(one, two)); + } + + /*** Numeric functions ***/ + + @Override + public void exitCountFunction(CountFunctionContext ctx) { + this.stack.push(this.script.composeCountOperation(this.stack.pop(ListExpression.class))); + } + + @Override + public void exitNumberFunction(NumberFunctionContext ctx) { + this.stack.push(this.script.composeToNumberConversion(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitSumFunction(SumFunctionContext ctx) { + this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericListExpression.class))); + } + + @Override + public void exitStringLengthFunction(StringLengthFunctionContext ctx) { + this.stack + .push(this.script.composeStringLengthCalculation(this.stack.pop(StringExpression.class))); + } + + /*** String functions ***/ + + @Override + public void exitSubstringFunction(SubstringFunctionContext ctx) { + final NumericExpression length = + ctx.length != null ? this.stack.pop(NumericExpression.class) : null; + final NumericExpression start = this.stack.pop(NumericExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + if (length != null) { + this.stack.push(this.script.composeSubstringExtraction(text, start, length)); + } else { + this.stack.push(this.script.composeSubstringExtraction(text, start)); + } + } + + @Override + public void exitToStringFunction(ToStringFunctionContext ctx) { + this.stack.push(this.script.composeToStringConversion(this.stack.pop(NumericExpression.class))); + } + + @Override + public void exitConcatFunction(ConcatFunctionContext ctx) { + if (this.stack.empty() || ctx.stringExpression().size() == 0) { + this.stack.push(this.script.composeStringConcatenation(Collections.emptyList())); + return; + } + + List list = new ArrayList<>(); + for (int i = 0; i < ctx.stringExpression().size(); i++) { + list.add(0, this.stack.pop(StringExpression.class)); + } + this.stack.push(this.script.composeStringConcatenation(list)); + } + + @Override + public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { + final StringExpression format = this.stack.pop(StringExpression.class); + final NumericExpression number = this.stack.pop(NumericExpression.class); + this.stack.push(this.script.composeNumberFormatting(number, format)); + } + + /*** Date functions ***/ + + @Override + public void exitDateFromStringFunction(DateFromStringFunctionContext ctx) { + this.stack.push(this.script.composeToDateConversion(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitDatePlusMeasureFunction(DatePlusMeasureFunctionContext ctx) { + DurationExpression right = this.stack.pop(DurationExpression.class); + DateExpression left = this.stack.pop(DateExpression.class); + this.stack.push(this.script.composeAddition(left, right)); + } + + @Override + public void exitDateMinusMeasureFunction(DateMinusMeasureFunctionContext ctx) { + DurationExpression right = this.stack.pop(DurationExpression.class); + DateExpression left = this.stack.pop(DateExpression.class); + this.stack.push(this.script.composeSubtraction(left, right)); + } + + /*** Time functions ***/ + + @Override + public void exitTimeFromStringFunction(TimeFromStringFunctionContext ctx) { + this.stack.push(this.script.composeToTimeConversion(this.stack.pop(StringExpression.class))); + } + + /*** Duration Functions ***/ + + @Override + public void exitDayTimeDurationFromStringFunction(DayTimeDurationFromStringFunctionContext ctx) { + this.stack.push( + this.script.composeToDayTimeDurationConversion(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitYearMonthDurationFromStringFunction( + YearMonthDurationFromStringFunctionContext ctx) { + this.stack.push( + this.script.composeToYearMonthDurationConversion(this.stack.pop(StringExpression.class))); + } + + /*** Sequence Functions ***/ + + @Override + public void exitDistinctValuesFunction(DistinctValuesFunctionContext ctx) { + final Class sequenceType = this.stack.peek().getClass(); + if (StringListExpression.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(StringListExpression.class); + } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(NumericListExpression.class); + } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(BooleanListExpression.class); + } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(DateListExpression.class); + } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(TimeListExpression.class); + } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(DurationListExpression.class); + } else { + throw new IllegalArgumentException( + "Unsupported sequence type: " + sequenceType.getSimpleName()); + } + } + + private > void exitDistinctValuesFunction( + Class listType) { + final L list = this.stack.pop(listType); + this.stack.push(this.script.composeDistinctValuesFunction(list, listType)); + } + + @Override + public void exitUnionFunction(UnionFunctionContext ctx) { + final Class sequenceType = this.stack.peek().getClass(); + if (StringListExpression.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(StringListExpression.class); + } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(NumericListExpression.class); + } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(BooleanListExpression.class); + } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(DateListExpression.class); + } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(TimeListExpression.class); + } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(DurationListExpression.class); + } else { + throw new IllegalArgumentException( + "Unsupported sequence type: " + sequenceType.getSimpleName()); + } + } + + private > void exitUnionFunction( + Class listType) { + final L two = this.stack.pop(listType); + final L one = this.stack.pop(listType); + this.stack.push(this.script.composeUnionFunction(one, two, listType)); + } + + @Override + public void exitIntersectFunction(IntersectFunctionContext ctx) { + final Class sequenceType = this.stack.peek().getClass(); + if (StringListExpression.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(StringListExpression.class); + } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(NumericListExpression.class); + } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(BooleanListExpression.class); + } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(DateListExpression.class); + } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(TimeListExpression.class); + } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(DurationListExpression.class); + } else { + throw new IllegalArgumentException( + "Unsupported sequence type: " + sequenceType.getSimpleName()); + } + } + + private > void exitIntersectFunction( + Class listType) { + final L two = this.stack.pop(listType); + final L one = this.stack.pop(listType); + this.stack.push(this.script.composeIntersectFunction(one, two, listType)); + } + + @Override + public void exitExceptFunction(ExceptFunctionContext ctx) { + final Class sequenceType = this.stack.peek().getClass(); + if (StringListExpression.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(StringListExpression.class); + } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(NumericListExpression.class); + } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(BooleanListExpression.class); + } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(DateListExpression.class); + } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(TimeListExpression.class); + } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(DurationListExpression.class); + } else { + throw new IllegalArgumentException( + "Unsupported sequence type: " + sequenceType.getSimpleName()); + } + } + + + private > void exitExceptFunction( + Class listType) { + final L two = this.stack.pop(listType); + final L one = this.stack.pop(listType); + this.stack.push(this.script.composeExceptFunction(one, two, listType)); + } + +} diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java new file mode 100644 index 00000000..b4d010df --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -0,0 +1,543 @@ +package eu.europa.ted.efx.sdk2; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponent; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponentType; +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.model.ContentBlock; +import eu.europa.ted.efx.model.ContentBlockStack; +import eu.europa.ted.efx.model.Context; +import eu.europa.ted.efx.model.Context.FieldContext; +import eu.europa.ted.efx.model.Context.NodeContext; +import eu.europa.ted.efx.model.Expression; +import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.Expression.StringExpression; +import eu.europa.ted.efx.model.Expression.StringListExpression; +import eu.europa.ted.efx.model.Markup; +import eu.europa.ted.efx.sdk2.EfxParser.*; +import eu.europa.ted.efx.xpath.XPathAttributeLocator; + +/** + * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV1} to provide additional + * translation capabilities for EFX templates. If has been implemented as an extension to the + * EfxExpressionTranslator in order to keep things simpler when one only needs to translate EFX + * expressions (like the condition associated with a business rule). + */ +@VersionDependentComponent(versions = {"2"}, componentType = VersionDependentComponentType.EFX_TEMPLATE_TRANSLATOR) +public class EfxTemplateTranslatorV2 extends EfxExpressionTranslatorV2 + implements EfxTemplateTranslator { + + private static final Logger logger = LoggerFactory.getLogger(EfxTemplateTranslatorV2.class); + + private static final String INCONSISTENT_INDENTATION_SPACES = + "Inconsistent indentation. Expected a multiple of %d spaces."; + private static final String INDENTATION_LEVEL_SKIPPED = "Indentation level skipped."; + private static final String START_INDENT_AT_ZERO = + "Incorrect indentation. Please do not indent the first level in your template."; + private static final String MIXED_INDENTATION = + "Do not mix indentation methods. Stick with either tabs or spaces."; + private static final String UNEXPECTED_INDENTATION = "Unexpected indentation tracker state."; + + private static final String LABEL_TYPE_NAME = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.LABEL_TYPE_NAME).replaceAll("^'|'$", ""); + private static final String LABEL_TYPE_WHEN = EfxLexer.VOCABULARY + .getLiteralName(EfxLexer.LABEL_TYPE_WHEN_TRUE).replaceAll("^'|'$", "").replace("-true", ""); + private static final String SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ValueKeyword).replaceAll("^'|'$", ""); + private static final String ASSET_TYPE_INDICATOR = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_INDICATOR).replaceAll("^'|'$", ""); + private static final String ASSET_TYPE_BT = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_BT).replaceAll("^'|'$", ""); + private static final String ASSET_TYPE_FIELD = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_FIELD).replaceAll("^'|'$", ""); + private static final String ASSET_TYPE_NODE = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_NODE).replaceAll("^'|'$", ""); + private static final String ASSET_TYPE_CODE = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_CODE).replaceAll("^'|'$", ""); + + + /** + * Used to control the indentation style used in a template + */ + private enum Indent { + TABS, SPACES, UNDETERMINED + } + + private Indent indentWith = Indent.UNDETERMINED; + private int indentSpaces = -1; + + /** + * The MarkupGenerator is called to retrieve markup in the target template language when needed. + */ + MarkupGenerator markup; + + final ContentBlock rootBlock = ContentBlock.newRootBlock(); + + /** + * The block stack is used to keep track of the indentation of template lines and adjust the EFX + * context accordingly. A block is a template line together with the template lines nested + * (through indentation) under it. At the top of the blockStack is the template block that is + * currently being processed. The next block in the stack is its parent block, and so on. + */ + ContentBlockStack blockStack = new ContentBlockStack(); + + @SuppressWarnings("unused") + private EfxTemplateTranslatorV2() { + super(); + } + + public EfxTemplateTranslatorV2(final MarkupGenerator markupGenerator, + final SymbolResolver symbolResolver, final ScriptGenerator scriptGenerator, + final BaseErrorListener errorListener) { + super(symbolResolver, scriptGenerator, errorListener); + + this.markup = markupGenerator; + } + + /** + * Opens the indicated EFX file and translates the EFX template it contains. + */ + @Override + public String renderTemplate(final Path pathname) throws IOException { + + return renderTemplate(CharStreams.fromPath(pathname)); + } + + /** + * Translates the template contained in the string passed as a parameter. + */ + @Override + public String renderTemplate(final String template) { + return renderTemplate(CharStreams.fromString(template)); + } + + @Override + public String renderTemplate(final InputStream stream) throws IOException { + return renderTemplate(CharStreams.fromStream(stream)); + } + + private String renderTemplate(final CharStream charStream) { + logger.debug("Rendering template"); + + final EfxLexer lexer = new EfxLexer(charStream); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + final EfxParser parser = new EfxParser(tokens); + + if (errorListener != null) { + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + } + + final ParseTree tree = parser.templateFile(); + + final ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(this, tree); + + logger.debug("Finished rendering template"); + + return getTranslatedMarkup(); + } + + /** + * Gets the translated code after the walker finished its walk. Every line in the template has now + * been translated and a {@link Markup} object is in the stack for each block in the template. The + * output template is built from the end towards the top, because the items are removed from the + * stack in reverse order. + * + * @return The translated code, trimmed + */ + private String getTranslatedMarkup() { + logger.debug("Getting translated markup."); + + final StringBuilder sb = new StringBuilder(64); + while (!this.stack.empty()) { + sb.insert(0, '\n').insert(0, this.stack.pop(Markup.class).script); + } + + logger.debug("Finished getting translated markup."); + + return sb.toString().trim(); + } + + /*** Template File ***/ + + @Override + public void enterTemplateFile(TemplateFileContext ctx) { + assert blockStack.isEmpty() : UNEXPECTED_INDENTATION; + } + + @Override + public void exitTemplateFile(TemplateFileContext ctx) { + this.blockStack.pop(); + + List templateCalls = new ArrayList<>(); + List templates = new ArrayList<>(); + for (ContentBlock rootBlock : this.rootBlock.getChildren()) { + templateCalls.add(rootBlock.renderCallTemplate(markup)); + rootBlock.renderTemplate(markup, templates); + } + Markup file = this.markup.composeOutputFile(templateCalls, templates); + this.stack.push(file); + } + + /*** Source template blocks ***/ + + @Override + 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(text).join(template)); + } + + @Override + public void exitLabelTemplate(LabelTemplateContext ctx) { + 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(); + Expression expression = this.stack.pop(Expression.class); + this.stack.push(this.markup.renderVariableExpression(expression).join(template)); + } + + + /*** Label Blocks #{...} ***/ + + @Override + public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { + StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class) + : this.script.getStringLiteralFromUnquotedString(""); + StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) + : this.script.getStringLiteralFromUnquotedString(""); + StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class) + : this.script.getStringLiteralFromUnquotedString(""); + this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( + List.of(assetType, this.script.getStringLiteralFromUnquotedString("|"), labelType, + this.script.getStringLiteralFromUnquotedString("|"), assetId)))); + } + + @Override + public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) { + StringExpression assetId = this.script.getStringLiteralFromUnquotedString(ctx.BtId().getText()); + StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) + : this.script.getStringLiteralFromUnquotedString(""); + 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)))); + } + + @Override + public void exitShorthandFieldLabelReference(ShorthandFieldLabelReferenceContext ctx) { + final String fieldId = ctx.FieldId().getText(); + StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) + : this.script.getStringLiteralFromUnquotedString(""); + + if (labelType.script.equals("value")) { + this.shorthandIndirectLabelReference(fieldId); + } else { + this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( + List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), + this.script.getStringLiteralFromUnquotedString("|"), labelType, + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(fieldId))))); + } + } + + @Override + public void exitShorthandIndirectLabelReference(ShorthandIndirectLabelReferenceContext ctx) { + this.shorthandIndirectLabelReference(ctx.FieldId().getText()); + } + + private void shorthandIndirectLabelReference(final String fieldId) { + final Context currentContext = this.efxContext.peek(); + final String fieldType = this.symbols.getTypeOfField(fieldId); + final XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(symbols.getAbsolutePathOfField(fieldId)); + final PathExpression valueReference = parsedPath.hasAttribute() + ? this.script.composeFieldAttributeReference( + symbols.getRelativePath(parsedPath.getPath(), currentContext.absolutePath()), + parsedPath.getAttribute(), PathExpression.class) + : this.script.composeFieldValueReference( + symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), + PathExpression.class); + final String loopVariableName = "$item"; + + switch (fieldType) { + case "indicator": + this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( + this.script.composeIteratorList( + List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))), + this.script.composeStringConcatenation( + List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN), + this.script.getStringLiteralFromUnquotedString("-"), + this.script.composeVariableReference(loopVariableName, StringExpression.class), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(fieldId))), + StringListExpression.class))); + break; + case "code": + case "internal-code": + this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( + this.script.composeIteratorList( + List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))), + this.script.composeStringConcatenation(List.of( + this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_NAME), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString( + this.symbols.getRootCodelistOfField(fieldId)), + this.script.getStringLiteralFromUnquotedString("."), + this.script.composeVariableReference(loopVariableName, StringExpression.class))), + StringListExpression.class))); + break; + default: + throw new ParseCancellationException(String.format( + "Unexpected field type '%s'. Expected a field of either type 'code' or 'indicator'.", + fieldType)); + } + } + + /** + * Handles the #{labelType} shorthand syntax which renders the label of the Field or Node declared + * as the context. + * + * If the labelType is 'value', then the label depends on the field's value and is rendered + * according to the field's type. The assetType is inferred from the Field or Node declared as + * context. + */ + @Override + public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromContextContext ctx) { + final String labelType = ctx.LabelType().getText(); + if (this.efxContext.isFieldContext()) { + if (labelType.equals(SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE)) { + this.shorthandIndirectLabelReference(this.efxContext.symbol()); + } else { + this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( + List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(labelType), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); + } + } else if (this.efxContext.isNodeContext()) { + this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( + List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_NODE), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(labelType), + this.script.getStringLiteralFromUnquotedString("|"), + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); + } + } + + /** + * Handles the #value shorthand syntax which renders the label corresponding to the value of the + * the field declared as the context of the current line of the template. This shorthand syntax is + * only supported for fields of type 'code' or 'indicator'. + */ + @Override + public void exitShorthandIndirectLabelReferenceFromContextField( + ShorthandIndirectLabelReferenceFromContextFieldContext ctx) { + if (!this.efxContext.isFieldContext()) { + throw new ParseCancellationException( + "The #value shorthand syntax can only be used in a field is declared as context."); + } + this.shorthandIndirectLabelReference(this.efxContext.symbol()); + } + + @Override + public void exitAssetType(AssetTypeContext ctx) { + if (ctx.expressionBlock() == null) { + this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); + } + } + + @Override + public void exitLabelType(LabelTypeContext ctx) { + if (ctx.expressionBlock() == null) { + this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); + } + } + + @Override + public void exitAssetId(AssetIdContext ctx) { + if (ctx.expressionBlock() == null) { + this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); + } + } + + /*** Expression Blocks ${...} ***/ + + /** + * Handles a standard expression block in a template line. Most of the work is done by the base + * class EfxExpressionTranslator. After the expression is translated, the result is passed + * through the renderer. + */ + @Override + public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { + this.stack.push(this.stack.pop(Expression.class)); + } + + /*** + * Handles the $value shorthand syntax which renders the value of the field declared as context in + * the current line of the template. + */ + @Override + public void exitShorthandFieldValueReferenceFromContextField( + ShorthandFieldValueReferenceFromContextFieldContext ctx) { + if (!this.efxContext.isFieldContext()) { + throw new ParseCancellationException( + "The $value shorthand syntax can only be used when a field is declared as the context."); + } + this.stack.push(this.script.composeFieldValueReference( + symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.absolutePath()), + Expression.class)); + } + + /*** Context Declaration Blocks {...} ***/ + + /** + * This method changes the current EFX context. + * + * The EFX context is always assumed to be either a Field or a Node. Any predicate included in the + * EFX context declaration is not relevant and is ignored. + */ + @Override + public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { + + final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + if (filedId != null) { + this.efxContext.push(new FieldContext(filedId, this.stack.pop(PathExpression.class))); + } else { + final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); + assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; + this.efxContext.push(new NodeContext(nodeId, this.stack.pop(PathExpression.class))); + } + + // final PathExpression absolutePath = this.stack.pop(PathExpression.class); + // final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + // if (filedId != null) { + // this.efxContext.push(new FieldContext(filedId, absolutePath, this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); + // } else { + // final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); + // assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; + // this.efxContext.push(new NodeContext(nodeId, absolutePath, this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); + // } + } + + + /*** Template lines ***/ + + @Override + public void exitTemplateLine(TemplateLineContext ctx) { + final Context lineContext = this.efxContext.pop(); + final int indentLevel = this.getIndentLevel(ctx); + final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); + final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); + final Integer outlineNumber = + ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; + assert this.stack.isEmpty() : "Stack should be empty at this point."; + this.stack.clear(); // Variable scope boundary. Clear declared variables + + if (indentChange > 1) { + throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); + } else if (indentChange == 1) { + if (this.blockStack.isEmpty()) { + throw new ParseCancellationException(START_INDENT_AT_ZERO); + } + this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext())); + } else if (indentChange < 0) { + // lower indent level + for (int i = indentChange; i < 0; i++) { + assert !this.blockStack.isEmpty() : UNEXPECTED_INDENTATION; + assert this.blockStack.currentIndentationLevel() > indentLevel : UNEXPECTED_INDENTATION; + this.blockStack.pop(); + } + assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + } else if (indentChange == 0) { + + if (blockStack.isEmpty()) { + assert indentLevel == 0 : UNEXPECTED_INDENTATION; + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()))); + } else { + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + } + } + } + + private Context relativizeContext(Context childContext, Context parentContext) { + if (parentContext == null) { + return childContext; + } + + if (FieldContext.class.isAssignableFrom(childContext.getClass())) { + return new FieldContext(childContext.symbol(), childContext.absolutePath(), + this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); + } + + assert NodeContext.class.isAssignableFrom( + childContext.getClass()) : "Child context should be either a FieldContext NodeContext."; + + return new NodeContext(childContext.symbol(), childContext.absolutePath(), + this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); + } + + /*** Helpers ***/ + + private int getIndentLevel(TemplateLineContext ctx) { + if (ctx.MixedIndent() != null) { + throw new ParseCancellationException(MIXED_INDENTATION); + } + + if (ctx.Spaces() != null) { + if (this.indentWith == Indent.UNDETERMINED) { + this.indentWith = Indent.SPACES; + this.indentSpaces = ctx.Spaces().getText().length(); + } else if (this.indentWith == Indent.TABS) { + throw new ParseCancellationException(MIXED_INDENTATION); + } + + if (ctx.Spaces().getText().length() % this.indentSpaces != 0) { + throw new ParseCancellationException( + String.format(INCONSISTENT_INDENTATION_SPACES, this.indentSpaces)); + } + return ctx.Spaces().getText().length() / this.indentSpaces; + } else if (ctx.Tabs() != null) { + if (this.indentWith == Indent.UNDETERMINED) { + this.indentWith = Indent.TABS; + } else if (this.indentWith == Indent.SPACES) { + throw new ParseCancellationException(MIXED_INDENTATION); + } + + return ctx.Tabs().getText().length(); + } + return 0; + } +} diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java new file mode 100644 index 00000000..47000fd2 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java @@ -0,0 +1,19 @@ +package eu.europa.ted.efx.sdk2.entity; + +import java.util.List; +import eu.europa.ted.efx.sdk1.entity.SdkCodelistV1; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponent; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponentType; + +/** + * Representation of an SdkCodelist for usage in the symbols map. + * + * @author rouschr + */ +@VersionDependentComponent(versions = {"2"}, componentType = VersionDependentComponentType.CODELIST) +public class SdkCodelistV2 extends SdkCodelistV1 { + + public SdkCodelistV2(String codelistId, String codelistVersion, List codes) { + super(codelistId, codelistVersion, codes); + } +} diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java new file mode 100644 index 00000000..b4b5fe20 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java @@ -0,0 +1,19 @@ +package eu.europa.ted.efx.sdk2.entity; + +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponent; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponentType; + +@VersionDependentComponent(versions = {"2"}, componentType = VersionDependentComponentType.FIELD) +public class SdkFieldV2 extends SdkFieldV1 { + + public SdkFieldV2(String id, String type, String parentNodeId, String xpathAbsolute, + String xpathRelative, String rootCodelistId) { + super(id, type, parentNodeId, xpathAbsolute, xpathRelative, rootCodelistId); + } + + public SdkFieldV2(JsonNode field) { + super(field); + } +} diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java new file mode 100644 index 00000000..1a835dd8 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java @@ -0,0 +1,22 @@ +package eu.europa.ted.efx.sdk2.entity; + +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.efx.sdk1.entity.SdkNodeV1; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponent; +import eu.europa.ted.eforms.sdk.selector.component.VersionDependentComponentType; + +/** + * A node is something like a section. Nodes can be parents of other nodes or parents of fields. + */ +@VersionDependentComponent(versions = {"2"}, componentType = VersionDependentComponentType.NODE) +public class SdkNodeV2 extends SdkNodeV1 { + + public SdkNodeV2(String id, String parentId, String xpathAbsolute, String xpathRelative, + boolean repeatable) { + super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + } + + public SdkNodeV2(JsonNode node) { + super(node); + } +} From e54ab7b9ed98532c3c5b7c0641f33e41763efc1d Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Fri, 24 Mar 2023 01:30:32 +0100 Subject: [PATCH 02/57] Added support for scoped shared variables in EFX template blocks (TEDEFO-1376) --- .../ted/eforms/sdk/SdkSymbolResolver.java | 5 +- .../ted/efx/interfaces/MarkupGenerator.java | 23 ++- .../ted/efx/interfaces/ScriptGenerator.java | 6 +- .../europa/ted/efx/model/BooleanVariable.java | 10 + .../eu/europa/ted/efx/model/CallStack.java | 153 +++++++++++---- ...ckObjectBase.java => CallStackObject.java} | 4 +- .../eu/europa/ted/efx/model/ContentBlock.java | 51 ++++- .../ted/efx/model/ContentBlockStack.java | 8 +- .../java/eu/europa/ted/efx/model/Context.java | 26 ++- .../eu/europa/ted/efx/model/DateVariable.java | 11 ++ .../ted/efx/model/DurationVariable.java | 11 ++ .../eu/europa/ted/efx/model/Expression.java | 11 +- .../eu/europa/ted/efx/model/Identifier.java | 11 ++ .../java/eu/europa/ted/efx/model/Markup.java | 2 +- .../europa/ted/efx/model/NumericVariable.java | 10 + .../europa/ted/efx/model/StringVariable.java | 10 + .../eu/europa/ted/efx/model/TimeVariable.java | 10 + .../eu/europa/ted/efx/model/Variable.java | 16 ++ .../eu/europa/ted/efx/model/VariableList.java | 29 +++ .../efx/sdk0/v6/EfxTemplateTranslator06.java | 12 +- .../sdk0/v7/EfxExpressionTranslator07.java | 7 +- .../efx/sdk0/v7/EfxTemplateTranslator07.java | 12 +- .../efx/sdk1/EfxExpressionTranslatorV1.java | 121 +++++++++--- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 23 +-- .../efx/sdk2/EfxExpressionTranslatorV2.java | 158 +++++++++++++--- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 178 +++++++++++++++--- .../ted/efx/xpath/XPathScriptGenerator.java | 16 +- .../ted/efx/EfxExpressionCombinedTest.java | 2 +- .../ted/efx/EfxExpressionTranslatorTest.java | 2 +- .../ted/efx/EfxTemplateTranslatorTest.java | 159 +++++++++------- .../ted/efx/mock/MarkupGeneratorMock.java | 25 ++- 31 files changed, 870 insertions(+), 252 deletions(-) create mode 100644 src/main/java/eu/europa/ted/efx/model/BooleanVariable.java rename src/main/java/eu/europa/ted/efx/model/{CallStackObjectBase.java => CallStackObject.java} (66%) create mode 100644 src/main/java/eu/europa/ted/efx/model/DateVariable.java create mode 100644 src/main/java/eu/europa/ted/efx/model/DurationVariable.java create mode 100644 src/main/java/eu/europa/ted/efx/model/Identifier.java create mode 100644 src/main/java/eu/europa/ted/efx/model/NumericVariable.java create mode 100644 src/main/java/eu/europa/ted/efx/model/StringVariable.java create mode 100644 src/main/java/eu/europa/ted/efx/model/TimeVariable.java create mode 100644 src/main/java/eu/europa/ted/efx/model/Variable.java create mode 100644 src/main/java/eu/europa/ted/efx/model/VariableList.java diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java index 12581f4e..02265477 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -3,8 +3,9 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; + import org.antlr.v4.runtime.misc.ParseCancellationException; -import eu.europa.ted.eforms.sdk.SdkConstants; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.eforms.sdk.entity.SdkCodelist; @@ -18,7 +19,7 @@ import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.xpath.XPathContextualizer; -@SdkComponent(versions = {"0.6", "0.7", "1"}, +@SdkComponent(versions = {"0.6", "0.7", "1", "2"}, componentType = SdkComponentType.SYMBOL_RESOLVER) public class SdkSymbolResolver implements SymbolResolver { protected Map fieldById; 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 b944d725..033a5098 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -14,6 +14,10 @@ package eu.europa.ted.efx.interfaces; import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.tuple.Pair; + import eu.europa.ted.efx.model.Expression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; @@ -63,14 +67,27 @@ public interface MarkupGenerator { Markup renderFreeText(final String freeText); /** - * Given a fragment name (identifier) and some pre-rendered content, this method returns the code - * that encapsulates it in the target template. + * @deprecated Use {@link #composeFragmentDefinition(String, String, Markup, Set)} instead. */ + @Deprecated(since = "2.0.0", forRemoval = true) Markup composeFragmentDefinition(final String name, String number, Markup content); + /** + * Given a fragment name (identifier) and some pre-rendered content, this method + * returns the code + * that encapsulates it in the target template. + */ + Markup composeFragmentDefinition(final String name, String number, Markup content, Set parameters); + + /** + * @deprecated Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead. + */ + @Deprecated(since = "2.0.0", forRemoval = true) + Markup renderFragmentInvocation(final String name, final PathExpression context); + /** * Given a fragment name (identifier), and an evaluation context, this method returns the code * that invokes (uses) the fragment. */ - Markup renderFragmentInvocation(final String name, final PathExpression context); + Markup renderFragmentInvocation(final String name, final PathExpression context, final Set> variables); } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 3fc28ed3..b3020021 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -14,6 +14,7 @@ package eu.europa.ted.efx.interfaces; import java.util.List; + import eu.europa.ted.efx.model.Expression; import eu.europa.ted.efx.model.Expression.BooleanExpression; import eu.europa.ted.efx.model.Expression.DateExpression; @@ -21,7 +22,6 @@ import eu.europa.ted.efx.model.Expression.IteratorExpression; import eu.europa.ted.efx.model.Expression.IteratorListExpression; import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.ListExpressionBase; import eu.europa.ted.efx.model.Expression.NumericExpression; import eu.europa.ted.efx.model.Expression.NumericListExpression; import eu.europa.ted.efx.model.Expression.PathExpression; @@ -250,7 +250,7 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, @Deprecated(since = "0.7.0", forRemoval = true) public NumericExpression composeCountOperation(final PathExpression set); - public NumericExpression composeCountOperation(final ListExpressionBase list); + public NumericExpression composeCountOperation(final ListExpression list); public NumericExpression composeToNumberConversion(StringExpression text); @@ -292,7 +292,7 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public BooleanExpression composeUniqueValueCondition(PathExpression needle, PathExpression haystack); - public BooleanExpression composeSequenceEqualFunction(ListExpressionBase one, ListExpressionBase two); + public BooleanExpression composeSequenceEqualFunction(ListExpression one, ListExpression two); /* * Date Functions diff --git a/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java b/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java new file mode 100644 index 00000000..59810ac0 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java @@ -0,0 +1,10 @@ +package eu.europa.ted.efx.model; + +import eu.europa.ted.efx.model.Expression.BooleanExpression; + +public class BooleanVariable extends Variable { + + public BooleanVariable(String variableName, BooleanExpression initializationExpression, BooleanExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java index 723f3e33..6ae9333d 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -2,67 +2,152 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Stack; + import org.antlr.v4.runtime.misc.ParseCancellationException; -public class CallStack extends Stack { +public class CallStack { + + class StackFrame extends Stack { + + Map> typeRegister = new HashMap>(); + + Map valueRegister = new HashMap(); + + public void pushParameterDeclaration(String parameterName, Expression parameterDeclarationExpression, + Expression parameterValue) { + this.declareIdentifier(parameterName, parameterDeclarationExpression.getClass()); + this.storeValue(parameterName, parameterValue); + this.push(parameterDeclarationExpression); + } + + public void pushVariableDeclaration(String variableName, Expression variableDeclarationExpression) { + this.declareIdentifier(variableName, variableDeclarationExpression.getClass()); + this.push(variableDeclarationExpression); + } + + public void declareIdentifier(String identifier, Class type) { + this.typeRegister.put(identifier, type); + } + + private void storeValue(String identifier, Expression value) { + this.valueRegister.put(identifier, value); + } + + public synchronized T pop(Class expectedType) { + Class actualType = peek().getClass(); + if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) { + throw new ParseCancellationException("Type mismatch. Expected " + expectedType.getSimpleName() + + " instead of " + this.peek().getClass().getSimpleName()); + } + return expectedType.cast(this.pop()); + } - Map> variableTypes = - new HashMap>(); + @Override + public void clear() { + super.clear(); + this.typeRegister.clear(); + this.valueRegister.clear(); + } + } - Map parameterValues = - new HashMap(); + Stack frames; - public CallStack() {} + public CallStack() { + this.frames = new Stack<>(); + this.frames.push(new StackFrame()); + } + + public void pushStackFrame() { + this.frames.push(new StackFrame()); + } + + public void popStackFrame() { + StackFrame droppedFrame = this.frames.pop(); + this.frames.peek().addAll(droppedFrame); // pass return values to the current stack frame + } public void pushParameterDeclaration(String parameterName, Expression parameterDeclaration, Expression parameterValue) { - if (this.variableTypes.containsKey(parameterName)) { - throw new ParseCancellationException("A parameter with the name " + parameterDeclaration.script + " already exists"); + if (this.inScope(parameterName)) { + throw new ParseCancellationException("Identifier " + parameterDeclaration.script + " already declared."); } else if (parameterDeclaration.getClass() == Expression.class) { throw new ParseCancellationException(); } else { - this.variableTypes.put(parameterName, parameterDeclaration.getClass()); - this.parameterValues.put(parameterName, parameterValue); - this.push(parameterDeclaration); + this.frames.peek().pushParameterDeclaration(parameterName, parameterDeclaration, parameterValue); } } public void pushVariableDeclaration(String variableName, Expression variableDeclaration) { - if (parameterValues.containsKey(variableName)) { - throw new ParseCancellationException("A parameter with the name " + variableDeclaration.script + " has already been declared."); - } else if (this.variableTypes.containsKey(variableName)) { - throw new ParseCancellationException("A variable with the name " + variableDeclaration.script + " has already been declared."); + if (this.inScope(variableName)) { + throw new ParseCancellationException("Identifier " + variableDeclaration.script + " already declared."); } else if (variableDeclaration.getClass() == Expression.class) { throw new ParseCancellationException(); } else { - this.variableTypes.put(variableName, variableDeclaration.getClass()); - this.push(variableDeclaration); + this.frames.peek().pushVariableDeclaration(variableName, variableDeclaration); } } - public void pushVariableReference(String variableName, Expression variableReference) { - if (this.parameterValues.containsKey(variableName)) { - this.push(parameterValues.get(variableName)); - } else if (this.variableTypes.containsKey(variableName)) { - this.push(Expression.instantiate(variableReference.script, variableTypes.get(variableName))); + public void declareTemplateVariable(String variableName, Class variableType) { + if (this.inScope(variableName)) { + throw new ParseCancellationException("Identifier " + variableName + " already declared."); + } else if (variableType == Expression.class) { + throw new ParseCancellationException(); } else { - throw new ParseCancellationException("A variable or parameter with the name " + variableName + " has not been declared."); + this.frames.peek().declareIdentifier(variableName, variableType); } } - public synchronized T pop(Class expectedType) { - Class actualType = peek().getClass(); - if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) { - throw new ParseCancellationException("Type mismatch. Expected " + expectedType.getSimpleName() - + " instead of " + this.peek().getClass().getSimpleName()); - } - return expectedType.cast(this.pop()); + boolean inScope(String identifier) { + return this.frames.stream().anyMatch(f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier) ); + } + + StackFrame findFrameContaining(String identifier) { + return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)).findFirst().orElse(null); + } + + Optional getParameter(String identifier) { + return this.frames.stream().filter(f -> f.valueRegister.containsKey(identifier)).findFirst().map(x -> x.valueRegister.get(identifier)); + } + + Optional> getVariable(String identifier) { + return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier)).findFirst().map(x -> x.typeRegister.get(identifier)); + } + + public void pushVariableReference(String variableName, Expression variableReference) { + getParameter(variableName).ifPresentOrElse(parameterValue -> this.push(parameterValue), + () -> getVariable(variableName).ifPresentOrElse( + variableType -> this.pushVariableReference(variableReference, variableType), + () -> { + throw new ParseCancellationException("Identifier " + variableName + " not declared."); + })); + } + + private void pushVariableReference(Expression variableReference, Class variableType) { + this.frames.peek().push(Expression.instantiate(variableReference.script, variableType)); + } + + public void push(CallStackObject item) { + this.frames.peek().push(item); + } + + public synchronized T pop(Class expectedType) { + return this.frames.peek().pop(expectedType); + } + + public synchronized CallStackObject peek() { + return this.frames.peek().peek(); + } + + public int size() { + return this.frames.peek().size(); + } + + public boolean empty() { + return this.frames.peek().empty(); } - @Override public void clear() { - super.clear(); - this.variableTypes.clear(); - this.parameterValues.clear(); + this.frames.peek().clear(); } } diff --git a/src/main/java/eu/europa/ted/efx/model/CallStackObjectBase.java b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java similarity index 66% rename from src/main/java/eu/europa/ted/efx/model/CallStackObjectBase.java rename to src/main/java/eu/europa/ted/efx/model/CallStackObject.java index 63126da4..07a3d3e7 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStackObjectBase.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java @@ -1,12 +1,12 @@ package eu.europa.ted.efx.model; /** - * Base class for objects pushed in the Sdk6EfxExpressionTranslator. + * Base class for objects pushed in the EfxExpressionTranslator. * call-stack. * * As the EfxExpressionTranslator translates EFX to a target language, the objects in the call-stack * are typically code snippets in the target language. */ -public abstract class CallStackObjectBase { +public abstract class CallStackObject { } diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java index f57b1950..c2fb4153 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java +++ b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java @@ -1,10 +1,16 @@ package eu.europa.ted.efx.model; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.apache.commons.lang3.tuple.Pair; + import eu.europa.ted.efx.interfaces.MarkupGenerator; public class ContentBlock { @@ -15,6 +21,7 @@ public class ContentBlock { private final Context context; private final Queue children = new LinkedList<>(); private final int number; + private final VariableList variables; private ContentBlock() { this.parent = null; @@ -23,38 +30,40 @@ private ContentBlock() { this.content = new Markup(""); this.context = null; this.number = 0; + this.variables = new VariableList(); } public ContentBlock(final ContentBlock parent, final String id, final int number, - final Markup content, Context contextPath) { + final Markup content, Context contextPath, VariableList variables) { this.parent = parent; this.id = id; this.indentationLevel = parent.indentationLevel + 1; this.content = content; this.context = contextPath; this.number = number; + this.variables = variables; } public static ContentBlock newRootBlock() { return new ContentBlock(); } - public ContentBlock addChild(final int number, final Markup content, final Context context) { + public ContentBlock addChild(final int number, final Markup content, final Context context, final VariableList variables) { // number < 0 means "autogenerate", number == 0 means "no number", number > 0 means "use this number" final int outlineNumber = number >= 0 ? number : children.stream().map(b -> b.number).max(Comparator.naturalOrder()).orElse(0) + 1; String newBlockId = String.format("%s%02d", this.id, this.children.size() + 1); - ContentBlock newBlock = new ContentBlock(this, newBlockId, outlineNumber, content, context); + ContentBlock newBlock = new ContentBlock(this, newBlockId, outlineNumber, content, context, variables); this.children.add(newBlock); return newBlock; } - public ContentBlock addSibling(final int number, final Markup content, final Context context) { + public ContentBlock addSibling(final int number, final Markup content, final Context context, final VariableList variables) { if (this.parent == null) { throw new ParseCancellationException("Cannot add sibling to root block"); } - return this.parent.addChild(number, content, context); + return this.parent.addChild(number, content, context, variables); } public ContentBlock findParentByLevel(final int parentIndentationLevel) { @@ -104,6 +113,29 @@ public Context getParentContext() { return this.parent.getContext(); } + public Set> getOwnVariables() { + Set> variables = new LinkedHashSet<>(); + if (this.context != null && this.context.variable() != null) { + variables.add(this.context.variable()); + } + variables.addAll(this.variables.asList()); + return variables; + } + + public Set> getAllVariables() { + if (this.parent == null) { + return new LinkedHashSet<>(this.getOwnVariables()); + } + final Set> merged = new LinkedHashSet<>(); + merged.addAll(parent.getAllVariables()); + merged.addAll(this.getOwnVariables()); + return merged; + } + + public Set getTemplateParameters() { + return this.getAllVariables().stream().map(v -> v.name).collect(Collectors.toSet()); + } + public Markup renderContent(MarkupGenerator markupGenerator) { StringBuilder sb = new StringBuilder(); sb.append(this.content.script); @@ -115,13 +147,18 @@ public Markup renderContent(MarkupGenerator markupGenerator) { public void renderTemplate(MarkupGenerator markupGenerator, List templates) { templates.add(markupGenerator.composeFragmentDefinition(this.id, this.getOutlineNumber(), - this.renderContent(markupGenerator))); + this.renderContent(markupGenerator), this.getTemplateParameters())); for (ContentBlock child : this.children) { child.renderTemplate(markupGenerator, templates); } } public Markup renderCallTemplate(MarkupGenerator markupGenerator) { - return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath()); + Set> variables = new LinkedHashSet<>(); + if (this.parent != null) { + variables.addAll(parent.getAllVariables().stream().map(v -> Pair.of(v.name, v.referenceExpression.script)).collect(Collectors.toList())); + } + variables.addAll(this.getOwnVariables().stream().map(v -> Pair.of(v.name, v.initializationExpression.script)).collect(Collectors.toList())); + return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath(), variables); } } diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java index f3c4c97a..d1d4e142 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java +++ b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java @@ -8,16 +8,16 @@ public class ContentBlockStack extends Stack { * Adds a new child block to the top of the stack. When the child is later removed, its parent * will return to the top of the stack again. */ - public void pushChild(final int number, final Markup content, final Context context) { - this.push(this.peek().addChild(number, content, context)); + public void pushChild(final int number, final Markup content, final Context context, final VariableList variables) { + this.push(this.peek().addChild(number, content, context, variables)); } /** * Removes the block at the top of the stack and replaces it by a new sibling block. When the last * sibling is later removed, their parent block will return to the top of the stack again. */ - public void pushSibling(final int number, final Markup content, Context context) { - this.push(this.pop().addSibling(number, content, context)); + public void pushSibling(final int number, final Markup content, Context context, final VariableList variables) { + this.push(this.pop().addSibling(number, content, context, variables)); } /** 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 4c1d7f19..7383fa32 100644 --- a/src/main/java/eu/europa/ted/efx/model/Context.java +++ b/src/main/java/eu/europa/ted/efx/model/Context.java @@ -19,11 +19,20 @@ public abstract class Context { */ public static class FieldContext extends Context { + public FieldContext(final String fieldId, final PathExpression absolutePath, + final PathExpression relativePath, final Variable variable) { + super(fieldId, absolutePath, relativePath, variable); + } + public FieldContext(final String fieldId, final PathExpression absolutePath, final PathExpression relativePath) { super(fieldId, absolutePath, relativePath); } + public FieldContext(final String fieldId, final PathExpression absolutePath, final Variable variable) { + super(fieldId, absolutePath, variable); + } + public FieldContext(final String fieldId, final PathExpression absolutePath) { super(fieldId, absolutePath); } @@ -47,14 +56,25 @@ public NodeContext(final String nodeId, final PathExpression absolutePath) { private final String symbol; private final PathExpression absolutePath; private final PathExpression relativePath; + private final Variable variable; protected Context(final String symbol, final PathExpression absolutePath, - final PathExpression relativePath) { + final PathExpression relativePath, final Variable variable) { + this.variable = variable; this.symbol = symbol; this.absolutePath = absolutePath; this.relativePath = relativePath == null ? absolutePath : relativePath; } + protected Context(final String symbol, final PathExpression absolutePath, + final PathExpression relativePath) { + this(symbol, absolutePath, relativePath, null); + } + + protected Context(final String symbol, final PathExpression absolutePath, final Variable variable) { + this(symbol, absolutePath, absolutePath, variable); + } + protected Context(final String symbol, final PathExpression absolutePath) { this(symbol, absolutePath, absolutePath); } @@ -67,6 +87,10 @@ public Boolean isNodeContext() { return this.getClass().equals(NodeContext.class); } + public Variable variable() { + return variable; + } + /** * Returns the [field or node] identifier that was used to create this context. */ diff --git a/src/main/java/eu/europa/ted/efx/model/DateVariable.java b/src/main/java/eu/europa/ted/efx/model/DateVariable.java new file mode 100644 index 00000000..27e47449 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/DateVariable.java @@ -0,0 +1,11 @@ +package eu.europa.ted.efx.model; + +import eu.europa.ted.efx.model.Expression.DateExpression; + +public class DateVariable extends Variable { + + public DateVariable(String variableName, DateExpression initializationExpression, DateExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } + +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/DurationVariable.java b/src/main/java/eu/europa/ted/efx/model/DurationVariable.java new file mode 100644 index 00000000..1c5d124f --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/DurationVariable.java @@ -0,0 +1,11 @@ +package eu.europa.ted.efx.model; + +import eu.europa.ted.efx.model.Expression.DurationExpression; + +public class DurationVariable extends Variable { + + public DurationVariable(String variableName, DurationExpression initializationExpression, DurationExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } + +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java index 701a6df8..edfbce30 100644 --- a/src/main/java/eu/europa/ted/efx/model/Expression.java +++ b/src/main/java/eu/europa/ted/efx/model/Expression.java @@ -16,7 +16,7 @@ * language. It also enables to EFX translator to perform type checking of EFX expressions. * */ -public class Expression extends CallStackObjectBase { +public class Expression extends CallStackObject { /** * eForms types are mapped to Expression types. @@ -177,17 +177,10 @@ public DurationExpression(final String script) { } } - public static class ListExpressionBase extends Expression { - - public ListExpressionBase(final String script) { - super(script); - } - } - /** * Used to represent a list of strings in the target language. */ - public static class ListExpression extends ListExpressionBase { + public static class ListExpression extends Expression { public ListExpression(final String script) { super(script); diff --git a/src/main/java/eu/europa/ted/efx/model/Identifier.java b/src/main/java/eu/europa/ted/efx/model/Identifier.java new file mode 100644 index 00000000..a73e03b9 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/Identifier.java @@ -0,0 +1,11 @@ +package eu.europa.ted.efx.model; + +public class Identifier { + public String name; + public T referenceExpression; + + public Identifier(String name, T referenceExpression) { + this.name = name; + this.referenceExpression = referenceExpression; + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/Markup.java b/src/main/java/eu/europa/ted/efx/model/Markup.java index 2fd9fa6b..ed55e2a2 100644 --- a/src/main/java/eu/europa/ted/efx/model/Markup.java +++ b/src/main/java/eu/europa/ted/efx/model/Markup.java @@ -3,7 +3,7 @@ /** * Represents markup in the target template language. */ -public class Markup extends CallStackObjectBase { +public class Markup extends CallStackObject { /** * Stores the markup script in the target language. diff --git a/src/main/java/eu/europa/ted/efx/model/NumericVariable.java b/src/main/java/eu/europa/ted/efx/model/NumericVariable.java new file mode 100644 index 00000000..8bbe331d --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/NumericVariable.java @@ -0,0 +1,10 @@ +package eu.europa.ted.efx.model; + +import eu.europa.ted.efx.model.Expression.NumericExpression; + +public class NumericVariable extends Variable { + + public NumericVariable(String variableName, NumericExpression initializationExpression, NumericExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/StringVariable.java b/src/main/java/eu/europa/ted/efx/model/StringVariable.java new file mode 100644 index 00000000..1ff3ec08 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/StringVariable.java @@ -0,0 +1,10 @@ +package eu.europa.ted.efx.model; + +import eu.europa.ted.efx.model.Expression.StringExpression; + +public class StringVariable extends Variable { + + public StringVariable(String variableName, StringExpression initializationExpression, StringExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/TimeVariable.java b/src/main/java/eu/europa/ted/efx/model/TimeVariable.java new file mode 100644 index 00000000..1c25b5d6 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/TimeVariable.java @@ -0,0 +1,10 @@ +package eu.europa.ted.efx.model; + +import eu.europa.ted.efx.model.Expression.TimeExpression; + +public class TimeVariable extends Variable { + + public TimeVariable(String variableName, TimeExpression initializationExpression, TimeExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/Variable.java b/src/main/java/eu/europa/ted/efx/model/Variable.java new file mode 100644 index 00000000..e5d1a093 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/Variable.java @@ -0,0 +1,16 @@ +package eu.europa.ted.efx.model; + +public class Variable extends Identifier { + public T initializationExpression; + + public Variable(String variableName, T initializationExpression, T referenceExpression) { + super(variableName, referenceExpression); + this.name = variableName; + this.initializationExpression = initializationExpression; + } + + public Variable(Identifier identifier, T initializationExpression) { + super(identifier.name, identifier.referenceExpression); + this.initializationExpression = initializationExpression; + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/VariableList.java b/src/main/java/eu/europa/ted/efx/model/VariableList.java new file mode 100644 index 00000000..ffc281c3 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/VariableList.java @@ -0,0 +1,29 @@ +package eu.europa.ted.efx.model; + +import java.util.LinkedList; +import java.util.List; + +public class VariableList extends CallStackObject { + + LinkedList> variables; + + public VariableList() { + this.variables = new LinkedList<>(); + } + + public void push(Variable variable) { + this.variables.push(variable); + } + + public synchronized Variable pop() { + return this.variables.pop(); + } + + public boolean isEmpty() { + return this.variables.isEmpty(); + } + + public List> asList() { + return this.variables; + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java index 8afd4f48..9f43d2c0 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java @@ -29,6 +29,7 @@ import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Markup; +import eu.europa.ted.efx.model.VariableList; import eu.europa.ted.efx.sdk0.v6.EfxParser.AssetIdContext; import eu.europa.ted.efx.sdk0.v6.EfxParser.AssetTypeContext; import eu.europa.ted.efx.sdk0.v6.EfxParser.ContextDeclarationBlockContext; @@ -439,13 +440,14 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { @Override public void exitTemplateLine(TemplateLineContext ctx) { + final VariableList variables = new VariableList(); // template variables not supported by EFX prior to 2.0.0 final Context lineContext = this.efxContext.pop(); final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); final Integer outlineNumber = ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; - assert this.stack.isEmpty() : "Stack should be empty at this point."; + assert this.stack.empty() : "Stack should be empty at this point."; if (indentChange > 1) { throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); @@ -453,7 +455,7 @@ public void exitTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext())); + this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { // lower indent level for (int i = indentChange; i < 0; i++) { @@ -462,14 +464,14 @@ public void exitTemplateLine(TemplateLineContext ctx) { this.blockStack.pop(); } assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()))); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } } } diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java index d7cf16d8..c14d38de 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java @@ -19,7 +19,7 @@ import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.CallStack; -import eu.europa.ted.efx.model.CallStackObjectBase; +import eu.europa.ted.efx.model.CallStackObject; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; import eu.europa.ted.efx.model.ContextStack; @@ -546,7 +546,7 @@ private > void exitList(int li @Override public void exitUntypedConditonalExpression(UntypedConditonalExpressionContext ctx) { - Class typeWhenFalse = this.stack.peek().getClass(); + Class typeWhenFalse = this.stack.peek().getClass(); if (typeWhenFalse == BooleanExpression.class) { this.exitConditionalBooleanExpression(); } else if (typeWhenFalse == NumericExpression.class) { @@ -1233,7 +1233,8 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) { @Override public void exitCountFunction(CountFunctionContext ctx) { - this.stack.push(this.script.composeCountOperation(this.stack.pop(ListExpression.class))); + ListExpression expression = this.stack.pop(ListExpression.class); + this.stack.push(this.script.composeCountOperation(expression)); } @Override diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java index f1d8ebb6..b76fd1ac 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java @@ -29,6 +29,7 @@ import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Markup; +import eu.europa.ted.efx.model.VariableList; import eu.europa.ted.efx.sdk0.v7.EfxParser.AssetIdContext; import eu.europa.ted.efx.sdk0.v7.EfxParser.AssetTypeContext; import eu.europa.ted.efx.sdk0.v7.EfxParser.ContextDeclarationBlockContext; @@ -439,13 +440,14 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { @Override public void exitTemplateLine(TemplateLineContext ctx) { + final VariableList variables = new VariableList(); // template variables not supported prior to EFX 2 final Context lineContext = this.efxContext.pop(); final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); final Integer outlineNumber = ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; - assert this.stack.isEmpty() : "Stack should be empty at this point."; + assert this.stack.empty() : "Stack should be empty at this point."; if (indentChange > 1) { throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); @@ -453,7 +455,7 @@ public void exitTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext())); + this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { // lower indent level for (int i = indentChange; i < 0; i++) { @@ -462,14 +464,14 @@ public void exitTemplateLine(TemplateLineContext ctx) { this.blockStack.pop(); } assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()))); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } } } 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 c0ddd161..5c5abbad 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -6,6 +6,7 @@ import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; + import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -15,13 +16,15 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.commons.lang3.StringUtils; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.CallStack; -import eu.europa.ted.efx.model.CallStackObjectBase; +import eu.europa.ted.efx.model.CallStackObject; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; @@ -560,7 +563,7 @@ private > void exitList(int li @Override public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) { - Class typeWhenFalse = this.stack.peek().getClass(); + Class typeWhenFalse = this.stack.peek().getClass(); if (typeWhenFalse == BooleanExpression.class) { this.exitConditionalBooleanExpression(); } else if (typeWhenFalse == NumericExpression.class) { @@ -1109,8 +1112,8 @@ public void exitCodelistReference(CodelistReferenceContext ctx) { @Override public void exitVariableReference(VariableReferenceContext ctx) { - this.stack.pushVariableReference(ctx.Variable().getText(), - this.script.composeVariableReference(ctx.Variable().getText(), Expression.class)); + this.stack.pushVariableReference(this.getVariableName(ctx), + this.script.composeVariableReference(this.getVariableName(ctx), Expression.class)); } /*** Parameter Declarations ***/ @@ -1118,32 +1121,32 @@ public void exitVariableReference(VariableReferenceContext ctx) { @Override public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), StringExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), StringExpression.class); } @Override public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), NumericExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), NumericExpression.class); } @Override public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), BooleanExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), BooleanExpression.class); } @Override public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), DateExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), DateExpression.class); } @Override public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), TimeExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), TimeExpression.class); } @Override public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), DurationExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), DurationExpression.class); } private void exitParameterDeclaration(String parameterName, Class parameterType) { @@ -1160,44 +1163,51 @@ private void exitParameterDeclaration(String parameterNam @Override public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), StringExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, StringExpression.class)); } @Override public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), BooleanExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, BooleanExpression.class)); } @Override public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), NumericExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, NumericExpression.class)); } @Override public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), DateExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, DateExpression.class)); } @Override public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), TimeExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, TimeExpression.class)); } @Override public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), DurationExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, DurationExpression.class)); } @Override public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), ContextExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, ContextExpression.class)); } /*** Boolean functions ***/ @@ -1239,7 +1249,8 @@ public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { @Override public void exitCountFunction(CountFunctionContext ctx) { - this.stack.push(this.script.composeCountOperation(this.stack.pop(ListExpression.class))); + final ListExpression expression = this.stack.pop(ListExpression.class); + this.stack.push(this.script.composeCountOperation(expression)); } @Override @@ -1448,7 +1459,6 @@ public void exitExceptFunction(ExceptFunctionContext ctx) { } } - private > void exitExceptFunction( Class listType) { final L two = this.stack.pop(listType); @@ -1456,4 +1466,63 @@ private > void exitExceptFunct this.stack.push(this.script.composeExceptFunction(one, two, listType)); } + private String getVariableName(String efxVariableIdentifier) { + return StringUtils.stripStart(efxVariableIdentifier, "$"); + } + + private String getVariableName(VariableReferenceContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + protected String getVariableName(ContextVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(StringVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(NumericVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(BooleanVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DateVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(TimeVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DurationVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(StringParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(NumericParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(BooleanParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DateParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(TimeParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DurationParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } } 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 5b4696e4..0be1f12e 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -30,6 +30,7 @@ import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Markup; +import eu.europa.ted.efx.model.VariableList; import eu.europa.ted.efx.sdk1.EfxParser.*; import eu.europa.ted.efx.xpath.XPathAttributeLocator; @@ -284,19 +285,18 @@ private void shorthandIndirectLabelReference(final String fieldId) { : this.script.composeFieldValueReference( symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), PathExpression.class); - final String loopVariableName = "$item"; - + final StringExpression loopVariable = this.script.composeVariableReference("item", StringExpression.class); switch (fieldType) { case "indicator": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))), + List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN), this.script.getStringLiteralFromUnquotedString("-"), - this.script.composeVariableReference(loopVariableName, StringExpression.class), + loopVariable, this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(fieldId))), StringListExpression.class))); @@ -305,7 +305,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { case "internal-code": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))), + List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation(List.of( this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), this.script.getStringLiteralFromUnquotedString("|"), @@ -314,7 +314,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.getStringLiteralFromUnquotedString( this.symbols.getRootCodelistOfField(fieldId)), this.script.getStringLiteralFromUnquotedString("."), - this.script.composeVariableReference(loopVariableName, StringExpression.class))), + loopVariable)), StringListExpression.class))); break; default: @@ -456,13 +456,14 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { @Override public void exitTemplateLine(TemplateLineContext ctx) { + final VariableList variables = new VariableList(); // template variables not supported prior to EFX 2 final Context lineContext = this.efxContext.pop(); final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); final Integer outlineNumber = ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; - assert this.stack.isEmpty() : "Stack should be empty at this point."; + assert this.stack.empty() : "Stack should be empty at this point."; this.stack.clear(); // Variable scope boundary. Clear declared variables if (indentChange > 1) { @@ -471,7 +472,7 @@ public void exitTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext())); + this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { // lower indent level for (int i = indentChange; i < 0; i++) { @@ -480,14 +481,14 @@ public void exitTemplateLine(TemplateLineContext ctx) { this.blockStack.pop(); } assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()))); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } } } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index f5e23fac..480d45b1 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -15,13 +15,15 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.commons.lang3.StringUtils; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.CallStack; -import eu.europa.ted.efx.model.CallStackObjectBase; +import eu.europa.ted.efx.model.CallStackObject; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; @@ -560,7 +562,7 @@ private > void exitList(int li @Override public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) { - Class typeWhenFalse = this.stack.peek().getClass(); + Class typeWhenFalse = this.stack.peek().getClass(); if (typeWhenFalse == BooleanExpression.class) { this.exitConditionalBooleanExpression(); } else if (typeWhenFalse == NumericExpression.class) { @@ -699,7 +701,6 @@ public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx) this.efxContext.declareContextVariable(variable.script, new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); - } else if (ctx.nodeContext() != null) { final String contextNodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx.nodeContext()); @@ -756,34 +757,70 @@ public void exitParenthesizedDurationsFromITeration( this.stack.pop(DurationListExpression.class), DurationListExpression.class)); } + @Override + public void enterStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + @Override public void exitStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { this.exitIterationExpression(StringExpression.class, StringListExpression.class); + this.stack.popStackFrame(); + } + + @Override + public void enterNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); } @Override public void exitNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { this.exitIterationExpression(NumericExpression.class, NumericListExpression.class); + this.stack.popStackFrame(); + } + + @Override + public void enterBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); } @Override public void exitBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class); + this.stack.popStackFrame(); + } + + @Override + public void enterDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); } @Override public void exitDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { this.exitIterationExpression(DateExpression.class, DateListExpression.class); + this.stack.popStackFrame(); + } + + @Override + public void enterTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); } @Override public void exitTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { this.exitIterationExpression(TimeExpression.class, TimeListExpression.class); + this.stack.popStackFrame(); + } + + @Override + public void enterDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); } @Override public void exitDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { this.exitIterationExpression(DurationExpression.class, DurationListExpression.class); + this.stack.popStackFrame(); } public > void exitIteratorExpression( @@ -1109,8 +1146,9 @@ public void exitCodelistReference(CodelistReferenceContext ctx) { @Override public void exitVariableReference(VariableReferenceContext ctx) { - this.stack.pushVariableReference(ctx.Variable().getText(), - this.script.composeVariableReference(ctx.Variable().getText(), Expression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableReference(variableName, + this.script.composeVariableReference(variableName, Expression.class)); } /*** Parameter Declarations ***/ @@ -1118,32 +1156,32 @@ public void exitVariableReference(VariableReferenceContext ctx) { @Override public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), StringExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), StringExpression.class); } @Override public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), NumericExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), NumericExpression.class); } @Override public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), BooleanExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), BooleanExpression.class); } @Override public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), DateExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), DateExpression.class); } @Override public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), TimeExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), TimeExpression.class); } @Override public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) { - this.exitParameterDeclaration(ctx.Variable().getText(), DurationExpression.class); + this.exitParameterDeclaration(this.getVariableName(ctx), DurationExpression.class); } private void exitParameterDeclaration(String parameterName, Class parameterType) { @@ -1160,44 +1198,51 @@ private void exitParameterDeclaration(String parameterNam @Override public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), StringExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, StringExpression.class)); } @Override public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), BooleanExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, BooleanExpression.class)); } @Override public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), NumericExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, NumericExpression.class)); } @Override public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), DateExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, DateExpression.class)); } @Override public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), TimeExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, TimeExpression.class)); } @Override public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), DurationExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, DurationExpression.class)); } @Override public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), ContextExpression.class)); + String variableName = this.getVariableName(ctx); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, ContextExpression.class)); } /*** Boolean functions ***/ @@ -1239,7 +1284,8 @@ public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { @Override public void exitCountFunction(CountFunctionContext ctx) { - this.stack.push(this.script.composeCountOperation(this.stack.pop(ListExpression.class))); + ListExpression expression = this.stack.pop(ListExpression.class); + this.stack.push(this.script.composeCountOperation(expression)); } @Override @@ -1448,7 +1494,6 @@ public void exitExceptFunction(ExceptFunctionContext ctx) { } } - private > void exitExceptFunction( Class listType) { final L two = this.stack.pop(listType); @@ -1456,4 +1501,63 @@ private > void exitExceptFunct this.stack.push(this.script.composeExceptFunction(one, two, listType)); } + protected String getVariableName(String efxVariableIdentifier) { + return StringUtils.stripStart(efxVariableIdentifier, "$"); + } + + private String getVariableName(VariableReferenceContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + protected String getVariableName(ContextVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(StringVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(NumericVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(BooleanVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DateVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(TimeVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DurationVariableDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(StringParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(NumericParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(BooleanParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DateParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(TimeParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DurationParameterDeclarationContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } } 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 c7acb8a0..f60e1472 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -2,9 +2,11 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Constructor; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; + import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; @@ -14,24 +16,40 @@ import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; 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.model.BooleanVariable; import eu.europa.ted.efx.model.ContentBlock; import eu.europa.ted.efx.model.ContentBlockStack; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; +import eu.europa.ted.efx.model.DateVariable; +import eu.europa.ted.efx.model.DurationVariable; import eu.europa.ted.efx.model.Expression; +import eu.europa.ted.efx.model.Expression.BooleanExpression; +import eu.europa.ted.efx.model.Expression.DateExpression; +import eu.europa.ted.efx.model.Expression.DurationExpression; +import eu.europa.ted.efx.model.Expression.NumericExpression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Expression.StringListExpression; +import eu.europa.ted.efx.model.Expression.TimeExpression; import eu.europa.ted.efx.model.Markup; +import eu.europa.ted.efx.model.NumericVariable; +import eu.europa.ted.efx.model.StringVariable; +import eu.europa.ted.efx.model.TimeVariable; +import eu.europa.ted.efx.model.Variable; +import eu.europa.ted.efx.model.VariableList; +import eu.europa.ted.efx.sdk1.EfxExpressionTranslatorV1; import eu.europa.ted.efx.sdk2.EfxParser.*; import eu.europa.ted.efx.xpath.XPathAttributeLocator; +import eu.europa.ted.efx.xpath.XPathContextualizer; /** * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV1} to provide additional @@ -284,19 +302,19 @@ private void shorthandIndirectLabelReference(final String fieldId) { : this.script.composeFieldValueReference( symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), PathExpression.class); - final String loopVariableName = "$item"; - + final StringExpression loopVariable = this.script.composeVariableReference("item", StringExpression.class); switch (fieldType) { case "indicator": + this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))), + List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN), this.script.getStringLiteralFromUnquotedString("-"), - this.script.composeVariableReference(loopVariableName, StringExpression.class), + loopVariable, this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(fieldId))), StringListExpression.class))); @@ -305,7 +323,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { case "internal-code": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))), + List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation(List.of( this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), this.script.getStringLiteralFromUnquotedString("|"), @@ -314,7 +332,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.getStringLiteralFromUnquotedString( this.symbols.getRootCodelistOfField(fieldId)), this.script.getStringLiteralFromUnquotedString("."), - this.script.composeVariableReference(loopVariableName, StringExpression.class))), + loopVariable)), StringListExpression.class))); break; default: @@ -430,64 +448,134 @@ public void exitShorthandFieldValueReferenceFromContextField( */ @Override public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { + if (ctx.templateVariableList() == null) { + this.stack.push(new VariableList()); + } + } + @Override + public void exitContextDeclaration(ContextDeclarationContext ctx) { + final PathExpression contextPath = this.stack.pop(PathExpression.class); + Variable contextVariable = this.getContextVariable(ctx, contextPath); final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); if (filedId != null) { - this.efxContext.push(new FieldContext(filedId, this.stack.pop(PathExpression.class))); + this.efxContext.push(new FieldContext(filedId, contextPath, contextVariable)); + if (contextVariable != null) { + this.stack.declareTemplateVariable(contextVariable.name, + Expression.types.get(this.symbols.getTypeOfField(filedId))); + } } else { final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; - this.efxContext.push(new NodeContext(nodeId, this.stack.pop(PathExpression.class))); + this.efxContext.push(new NodeContext(nodeId, contextPath)); + } + } + + private Variable getContextVariable(ContextDeclarationContext ctx, PathExpression contextPath) { + if (ctx.contextVariableInitializer() == null) { + return null; } + final String variableName = this.getVariableName(ctx.contextVariableInitializer()); + return new Variable<>(variableName, XPathContextualizer.contextualize(contextPath, contextPath), this.script.composeVariableReference(variableName, PathExpression.class)); + } + + @Override + public void enterTemplateVariableList(TemplateVariableListContext ctx) { + this.stack.push(new VariableList()); + } + + @Override + public void exitStringVariableInitializer(StringVariableInitializerContext ctx) { + this.exitVariableInitializer(this.getVariableName(ctx), StringVariable.class, StringExpression.class); + } - // final PathExpression absolutePath = this.stack.pop(PathExpression.class); - // final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - // if (filedId != null) { - // this.efxContext.push(new FieldContext(filedId, absolutePath, this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); - // } else { - // final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); - // assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; - // this.efxContext.push(new NodeContext(nodeId, absolutePath, this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); - // } + @Override + public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) { + this.exitVariableInitializer(this.getVariableName(ctx), BooleanVariable.class, BooleanExpression.class); + } + + @Override + public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) { + this.exitVariableInitializer(this.getVariableName(ctx), NumericVariable.class, NumericExpression.class); + } + + @Override + public void exitDateVariableInitializer(DateVariableInitializerContext ctx) { + this.exitVariableInitializer(this.getVariableName(ctx), DateVariable.class, DateExpression.class); + } + + @Override + public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { + this.exitVariableInitializer(this.getVariableName(ctx), TimeVariable.class, TimeExpression.class); } + @Override + public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { + this.exitVariableInitializer(this.getVariableName(ctx), DurationVariable.class, DurationExpression.class); + } + + private > void exitVariableInitializer(String variableName, Class variableType, Class expressionType) { + T expression = this.stack.pop(expressionType); + VariableList variables = this.stack.pop(VariableList.class); + try { + Constructor constructor = variableType.getConstructor(String.class, expressionType, expressionType); + variables.push(constructor.newInstance(variableName, expression, this.script.composeVariableReference(variableName, expressionType))); + this.stack.push(variables); + this.stack.declareTemplateVariable(variableName, expressionType); + } catch (Exception e) { + throw new ParseCancellationException(e); + } + } /*** Template lines ***/ @Override - public void exitTemplateLine(TemplateLineContext ctx) { - final Context lineContext = this.efxContext.pop(); + public void enterTemplateLine(TemplateLineContext ctx) { final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); - final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); - final Integer outlineNumber = - ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; - assert this.stack.isEmpty() : "Stack should be empty at this point."; - this.stack.clear(); // Variable scope boundary. Clear declared variables - if (indentChange > 1) { throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); } else if (indentChange == 1) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext())); } else if (indentChange < 0) { - // lower indent level for (int i = indentChange; i < 0; i++) { assert !this.blockStack.isEmpty() : UNEXPECTED_INDENTATION; assert this.blockStack.currentIndentationLevel() > indentLevel : UNEXPECTED_INDENTATION; this.blockStack.pop(); } assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + } + } + + @Override + public void exitTemplateLine(TemplateLineContext ctx) { + final Context lineContext = this.efxContext.pop(); + final int indentLevel = this.getIndentLevel(ctx); + final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); + final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); + final VariableList templateVariables = this.stack.pop(VariableList.class); + final Integer outlineNumber = + ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; + assert this.stack.empty() : "Stack should be empty at this point."; + + if (indentChange > 1) { + throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); + } else if (indentChange == 1) { + if (this.blockStack.isEmpty()) { + throw new ParseCancellationException(START_INDENT_AT_ZERO); + } + this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), templateVariables); + } else if (indentChange < 0) { + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), templateVariables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()))); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), templateVariables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext())); + this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), templateVariables); } } } @@ -499,7 +587,7 @@ private Context relativizeContext(Context childContext, Context parentContext) { if (FieldContext.class.isAssignableFrom(childContext.getClass())) { return new FieldContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); + this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath()), childContext.variable()); } assert NodeContext.class.isAssignableFrom( @@ -540,4 +628,32 @@ private int getIndentLevel(TemplateLineContext ctx) { } return 0; } + + private String getVariableName(StringVariableInitializerContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(NumericVariableInitializerContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(BooleanVariableInitializerContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DateVariableInitializerContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(TimeVariableInitializerContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(DurationVariableInitializerContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } + + private String getVariableName(ContextVariableInitializerContext ctx) { + return this.getVariableName(ctx.Variable().getText()); + } } diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 03624b47..de0f0eab 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -1,6 +1,7 @@ package eu.europa.ted.efx.xpath; import static java.util.Map.entry; + import java.lang.reflect.Constructor; import java.util.List; import java.util.Map; @@ -8,7 +9,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; + import org.antlr.v4.runtime.misc.ParseCancellationException; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.ScriptGenerator; @@ -19,14 +22,13 @@ import eu.europa.ted.efx.model.Expression.IteratorExpression; import eu.europa.ted.efx.model.Expression.IteratorListExpression; import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.ListExpressionBase; import eu.europa.ted.efx.model.Expression.NumericExpression; import eu.europa.ted.efx.model.Expression.NumericListExpression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Expression.TimeExpression; -@SdkComponent(versions = {"0.6", "0.7", "1"}, componentType = SdkComponentType.SCRIPT_GENERATOR) +@SdkComponent(versions = {"0.6", "0.7", "1", "2"}, componentType = SdkComponentType.SCRIPT_GENERATOR) public class XPathScriptGenerator implements ScriptGenerator { /** @@ -104,12 +106,12 @@ public T composeFieldAttributeReference(PathExpression fi @Override public T composeVariableReference(String variableName, Class type) { - return Expression.instantiate(variableName, type); + return Expression.instantiate("$" + variableName, type); } @Override public T composeVariableDeclaration(String variableName, Class type) { - return Expression.instantiate(variableName, type); + return Expression.instantiate("$" + variableName, type); } @Override @@ -348,8 +350,8 @@ public BooleanExpression composeComparisonOperation(Expression leftOperand, Stri } @Override - public BooleanExpression composeSequenceEqualFunction(ListExpressionBase one, - ListExpressionBase two) { + public BooleanExpression composeSequenceEqualFunction(ListExpression one, + ListExpression two) { return new BooleanExpression("deep-equal(sort(" + one.script + "), sort(" + two.script + "))"); } @@ -361,7 +363,7 @@ public NumericExpression composeCountOperation(PathExpression nodeSet) { } @Override - public NumericExpression composeCountOperation(ListExpressionBase list) { + public NumericExpression composeCountOperation(ListExpression list) { return new NumericExpression("count(" + list.script + ")"); } diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java index ecf9a344..fe1f9546 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java @@ -8,7 +8,7 @@ * Test for EFX expressions that combine several aspects of the language. */ class EfxExpressionCombinedTest { - final private String SDK_VERSION = "eforms-sdk-1.0"; + final private String SDK_VERSION = "eforms-sdk-2.0"; private String test(final String context, final String expression) { try { diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java index 6cd2ae9d..74db0c5f 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java @@ -7,7 +7,7 @@ import eu.europa.ted.efx.mock.DependencyFactoryMock; class EfxExpressionTranslatorTest { - final private String SDK_VERSION = "eforms-sdk-1.0"; + final private String SDK_VERSION = "eforms-sdk-2.0"; private String test(final String context, final String expression) { return test1(String.format("{%s} ${%s}", context, expression)); diff --git a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java index 9e4d2653..ebe08920 100644 --- a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java @@ -7,7 +7,7 @@ import eu.europa.ted.efx.mock.DependencyFactoryMock; class EfxTemplateTranslatorTest { - final private String SDK_VERSION = "eforms-sdk-1.0"; + final private String SDK_VERSION = "eforms-sdk-2.0"; private String translate(final String template) { try { @@ -26,7 +26,7 @@ private String lines(String... lines) { @Test void testTemplateLineNoIdent() { - assertEquals("declare block01 = { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01)", + assertEquals("let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} foo")); } @@ -35,12 +35,12 @@ void testTemplateLineNoIdent() { */ @Test void testTemplateLineOutline_Autogenerated() { - assertEquals(lines("declare block01 = { outline('1') text('foo')", - "for-each(../..).call(block0101) }", - "declare block0101 = { outline('1.1') text('bar')", - "for-each(PathNode/NumberField).call(block010101) }", - "declare block010101 = { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01)"), // + assertEquals(lines("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') }", + "for-each(/*/PathNode/TextField).call(block01())"), // translate(lines("{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -50,12 +50,12 @@ void testTemplateLineOutline_Autogenerated() { */ @Test void testTemplateLineOutline_Explicit() { - assertEquals(lines("declare block01 = { outline('2') text('foo')", - "for-each(../..).call(block0101) }", - "declare block0101 = { outline('2.3') text('bar')", - "for-each(PathNode/NumberField).call(block010101) }", - "declare block010101 = { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01)"), // + 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())"), // translate(lines("2{BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -66,12 +66,12 @@ void testTemplateLineOutline_Explicit() { */ @Test void testTemplateLineOutline_Mixed() { - assertEquals(lines("declare block01 = { outline('2') text('foo')", - "for-each(../..).call(block0101) }", - "declare block0101 = { outline('2.1') text('bar')", - "for-each(PathNode/NumberField).call(block010101) }", - "declare block010101 = { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01)"), // + 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())"), // translate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -81,12 +81,12 @@ void testTemplateLineOutline_Mixed() { */ @Test void testTemplateLineOutline_Suppressed() { - assertEquals(lines("declare block01 = { outline('2') text('foo')", - "for-each(../..).call(block0101) }", - "declare block0101 = { text('bar')", - "for-each(PathNode/NumberField).call(block010101) }", - "declare block010101 = { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01)"), // + 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())"), // translate(lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -97,12 +97,12 @@ void testTemplateLineOutline_Suppressed() { @Test void testTemplateLineOutline_SuppressedAtParent() { // Outline is ignored if the line has no children - assertEquals(lines("declare block01 = { text('foo')", - "for-each(../..).call(block0101) }", - "declare block0101 = { outline('1') text('bar')", - "for-each(PathNode/NumberField).call(block010101) }", - "declare block010101 = { text('foo') }", - "for-each(/*/PathNode/TextField).call(block01)"), // + 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())"), // translate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -114,18 +114,18 @@ void testTemplateLineFirstIndented() { @Test void testTemplateLineIdentTab() { assertEquals( - lines("declare block01 = { outline('1') text('foo')", "for-each(.).call(block0101) }", // - "declare block0101 = { text('bar') }", // - "for-each(/*/PathNode/TextField).call(block01)"),// + lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // + "let block0101() -> { text('bar') }", // + "for-each(/*/PathNode/TextField).call(block01())"),// translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); } @Test void testTemplateLineIdentSpaces() { assertEquals( - lines("declare block01 = { outline('1') text('foo')", "for-each(.).call(block0101) }", // - "declare block0101 = { text('bar') }", // - "for-each(/*/PathNode/TextField).call(block01)"),// + lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // + "let block0101() -> { text('bar') }", // + "for-each(/*/PathNode/TextField).call(block01())"),// translate(lines("{BT-00-Text} foo", " {BT-00-Text} bar"))); } @@ -144,11 +144,11 @@ void testTemplateLineIdentMixedSpaceThenTab() { @Test void testTemplateLineIdentLower() { assertEquals( - lines("declare block01 = { outline('1') text('foo')", "for-each(.).call(block0101) }", - "declare block0101 = { text('bar') }", - "declare block02 = { text('code') }", - "for-each(/*/PathNode/TextField).call(block01)", - "for-each(/*/PathNode/CodeField).call(block02)"), + 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())"), translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar", "{BT-00-Code} code"))); } @@ -158,45 +158,72 @@ void testTemplateLineIdentUnexpected() { () -> translate(lines("{BT-00-Text} foo", "\t\t{BT-00-Text} bar"))); } + @Test + void testTemplateLineJoining() { + 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())"), + translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar \\ \n joined \\\n\\\nmore", "{BT-00-Code} code"))); + } + @Test void testTemplateLine_VariableScope() { assertEquals( - lines("declare block01 = { outline('1') eval(for $x in . return $x)", // - "for-each(.).call(block0101) }", // - "declare block0101 = { eval(for $x in . return $x) }", // - "for-each(/*/PathNode/TextField).call(block01)"),// + lines("let block01() -> { #1: eval(for $x in . return $x)", // + "for-each(.).call(block0101()) }", // + "let block0101() -> { eval(for $x in . return $x) }", // + "for-each(/*/PathNode/TextField).call(block01())"),// translate(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("let block01(t, ctx) -> { #1: eval(for $x in . return concat($x, $t))", // + "for-each(.).call(block0101(ctx:$ctx, t:$t, t2:'test')) }", // + "let block0101(t, ctx, t2) -> { #1.1: eval(for $y in . return concat($y, $t, $t2))", // + "for-each(.).call(block010101(ctx:$ctx, t:$t, t2:$t2)) }", // + "let block010101(t, ctx, t2) -> { eval(for $z in . return concat($z, $t, $ctx)) }", // + "for-each(/*/PathNode/TextField).call(block01(ctx:., t:./normalize-space(text())))"), // + translate(lines( + "{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)}", + " {BT-00-Text} ${for text:$z in BT-00-Text return concat($z, $t, $ctx)}"))); + + } + /*** Labels ***/ @Test void testStandardLabelReference() { assertEquals( - "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{field|name|BT-00-Text}")); } @Test void testStandardLabelReference_UsingLabelTypeAsAssetId() { assertEquals( - "declare block01 = { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{auxiliary|text|value}")); } @Test void testShorthandBtLabelReference() { assertEquals( - "declare block01 = { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{name|BT-00}")); } @Test void testShorthandFieldLabelReference() { assertEquals( - "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{name|BT-00-Text}")); } @@ -208,42 +235,42 @@ void testShorthandBtLabelReference_MissingLabelType() { @Test void testShorthandIndirectLabelReferenceForIndicator() { assertEquals( - "declare block01 = { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{BT-00-Indicator}")); } @Test void testShorthandIndirectLabelReferenceForCode() { assertEquals( - "declare block01 = { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{BT-00-Code}")); } @Test void testShorthandIndirectLabelReferenceForInternalCode() { assertEquals( - "declare block01 = { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{BT-00-Internal-Code}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute() { assertEquals( - "declare block01 = { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameAttributeInContext() { assertEquals( - "declare block01 = { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01)", + "let block01() -> { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())", translate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() { assertEquals( - "declare block01 = { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01)", + "let block01() -> { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", translate("{BT-00-Code} #{BT-00-CodeAttribute}")); } @@ -260,14 +287,14 @@ void testShorthandIndirectLabelReferenceForAttribute() { @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndIndicatorField() { assertEquals( - "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/IndicatorField).call(block01)", + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/IndicatorField).call(block01())", translate("{BT-00-Indicator} #{name}")); } @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndCodeField() { assertEquals( - "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nfor-each(/*/PathNode/CodeField).call(block01)", + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nfor-each(/*/PathNode/CodeField).call(block01())", translate("{BT-00-Code} #{name}")); } @@ -279,7 +306,7 @@ void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndTextField() { @Test void testShorthandLabelReferenceFromContext_WithOtherLabelType() { assertEquals( - "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{name}")); } @@ -291,14 +318,14 @@ void testShorthandLabelReferenceFromContext_WithUnknownLabelType() { @Test void testShorthandLabelReferenceFromContext_WithNodeContext() { assertEquals( - "declare block01 = { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01)", + "let block01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01())", translate("{ND-Root} #{name}")); } @Test void testShorthandIndirectLabelReferenceFromContextField() { assertEquals( - "declare block01 = { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01)", + "let block01() -> { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", translate("{BT-00-Code} #value")); } @@ -312,14 +339,14 @@ void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() { @Test void testShorthandFieldValueReferenceFromContextField() { - assertEquals("declare block01 = { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01)", + assertEquals("let block01() -> { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01())", translate("{BT-00-Code} $value")); } @Test void testShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( - "declare block01 = { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01)", + "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", translate("{BT-00-Code} blah #value blah $value blah")); } @@ -334,14 +361,14 @@ void testShorthandFieldValueReferenceFromContextField_WithNodeContext() { @Test void testNestedExpression() { assertEquals( - "declare block01 = { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\nfor-each(/*/PathNode/TextField).call(block01)", + "let block01() -> { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} #{field|name|${BT-00-Text}}")); } @Test void testEndOfLineComments() { assertEquals( - "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01)", + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01())", translate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); } } 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 c79a3fc5..9b56446a 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -1,8 +1,13 @@ package eu.europa.ted.efx.mock; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; + import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + import eu.europa.ted.efx.interfaces.MarkupGenerator; import eu.europa.ted.efx.model.Expression; import eu.europa.ted.efx.model.Expression.PathExpression; @@ -33,15 +38,29 @@ public Markup renderFreeText(String freeText) { @Override public Markup composeFragmentDefinition(String name, String number, Markup content) { + return this.composeFragmentDefinition(name, number, content, new LinkedHashSet<>()); + } + + @Override + public Markup composeFragmentDefinition(String name, String number, Markup content, Set parameters) { if (StringUtils.isBlank(number)) { - return new Markup(String.format("declare %s = { %s }", name, content.script)); + return new Markup(String.format("let %s(%s) -> { %s }", name, + parameters.stream().collect(Collectors.joining(", ")), content.script)); } - return new Markup(String.format("declare %s = { outline('%s') %s }", name, number, content.script)); + return new Markup(String.format("let %s(%s) -> { #%s: %s }", name, + parameters.stream().collect(Collectors.joining(", ")), number, content.script)); } @Override public Markup renderFragmentInvocation(String name, PathExpression context) { - return new Markup(String.format("for-each(%s).call(%s)", context.script, name)); + return this.renderFragmentInvocation(name, context, new LinkedHashSet<>()); + } + + @Override + public Markup renderFragmentInvocation(String name, PathExpression context, + Set> variables) { + return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name, variables.stream() + .map(v -> String.format("%s:%s", v.getLeft(), v.getRight())).collect(Collectors.joining(", ")))); } @Override From 51d3b2ccb3836880b850015921bc2b0be578bbec Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Fri, 24 Mar 2023 01:48:54 +0100 Subject: [PATCH 03/57] Updated eforms-core-library version in pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c361ff6c..931dbc62 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ ${project.build.directory}/eforms-sdk/antlr4 - 1.0.1-SNAPSHOT + 1.0.1 4.9.3 From 54575bbe2b180404ab8a0ffc0a43bc168b512e9e Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Fri, 24 Mar 2023 02:06:39 +0100 Subject: [PATCH 04/57] Fixed small typo in EFX grammar --- .../eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 480d45b1..34a70d02 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -751,8 +751,8 @@ public void exitParenthesizedTimesFromIteration(ParenthesizedTimesFromIterationC } @Override - public void exitParenthesizedDurationsFromITeration( - ParenthesizedDurationsFromITerationContext ctx) { + public void exitParenthesizedDurationsFromIteration( + ParenthesizedDurationsFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(DurationListExpression.class), DurationListExpression.class)); } From c2cf0a167cf26473217bfb90ca90f996a2ae0f39 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Fri, 7 Apr 2023 18:26:50 +0200 Subject: [PATCH 05/57] Added string-join function (TEDEFO-1374) --- .../eu/europa/ted/efx/interfaces/ScriptGenerator.java | 3 +++ .../europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java | 7 +++++++ .../eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 7 +++++++ .../eu/europa/ted/efx/EfxExpressionTranslatorTest.java | 10 ++++++++++ 4 files changed, 27 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index b3020021..d1411a39 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -26,6 +26,7 @@ import eu.europa.ted.efx.model.Expression.NumericListExpression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; +import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Expression.TimeExpression; /** @@ -267,6 +268,8 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public StringExpression composeStringConcatenation(List list); + public StringExpression composeStringJoin(StringListExpression list, StringExpression separator); + public BooleanExpression composeEndsWithCondition(StringExpression text, StringExpression endsWith); diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 34a70d02..753ace65 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1338,6 +1338,13 @@ public void exitConcatFunction(ConcatFunctionContext ctx) { this.stack.push(this.script.composeStringConcatenation(list)); } + @Override + public void exitStringJoinFunction(StringJoinFunctionContext ctx) { + final StringExpression separator = this.stack.pop(StringExpression.class); + final StringListExpression list = this.stack.pop(StringListExpression.class); + this.stack.push(this.script.composeStringJoin(list, separator)); + } + @Override public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { final StringExpression format = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index de0f0eab..17c00c05 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -26,6 +26,7 @@ import eu.europa.ted.efx.model.Expression.NumericListExpression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; +import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Expression.TimeExpression; @SdkComponent(versions = {"0.6", "0.7", "1", "2"}, componentType = SdkComponentType.SCRIPT_GENERATOR) @@ -421,6 +422,12 @@ public StringExpression composeStringConcatenation(List list) "concat(" + list.stream().map(i -> i.script).collect(Collectors.joining(", ")) + ")"); } + @Override + public StringExpression composeStringJoin(StringListExpression list, StringExpression separator) { + return new StringExpression( + "string-join(" + list.script + ", " + separator.script + ")"); + } + @Override public StringExpression composeNumberFormatting(NumericExpression number, StringExpression format) { diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java index 74db0c5f..8ec20cc0 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java @@ -1155,6 +1155,16 @@ void testConcatFunction() { assertEquals("concat('abc', 'def')", test("ND-Root", "concat('abc', 'def')")); }; + @Test + void testStringJoinFunction_withLiterals() { + assertEquals("string-join(('abc','def'), ',')", test("ND-Root", "string-join(('abc', 'def'), ',')")); + } + + @Test + void testStringJoinFunction_withFieldReference() { + assertEquals("string-join(PathNode/TextField, ',')", test("ND-Root", "string-join(BT-00-Text, ',')")); + } + @Test void testFormatNumberFunction() { assertEquals("format-number(PathNode/NumberField/number(), '#,##0.00')", From 7a7227f12f542e07f9bcb6036504f6b5b0c09db1 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sun, 9 Apr 2023 13:25:32 +0200 Subject: [PATCH 06/57] First attempt to introduce Field and Node aliases (TEDEFO-2082) --- .../efx/sdk2/EfxExpressionTranslatorV2.java | 29 +++- .../ted/efx/sdk2/entity/SdkFieldV2.java | 13 +- .../europa/ted/efx/sdk2/entity/SdkNodeV2.java | 9 +- .../ted/efx/EfxExpressionTranslatorTest.java | 18 +-- .../ted/efx/mock/SymbolResolverMock.java | 126 +++++++++--------- 5 files changed, 117 insertions(+), 78 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 753ace65..566b3669 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -67,8 +67,11 @@ public class EfxExpressionTranslatorV2 extends EfxBaseListener implements EfxExpressionTranslator { - private static final String NOT_MODIFIER = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); + private static final String NOT_MODIFIER = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); + + private static final String VARIABLE_PREFIX = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.VariablePrefix).replaceAll("^'|'$", ""); + private static final String ATTRIBUTE_PREFIX = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.AttributePrefix).replaceAll("^'|'$", ""); + private static final String CODELIST_PREFIX = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.CodelistPrefix).replaceAll("^'|'$", ""); private static final String BEGIN_EXPRESSION_BLOCK = "{"; private static final String END_EXPRESSION_BLOCK = "}"; @@ -1044,7 +1047,7 @@ public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx @Override public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - ctx.attributeReference().Identifier().getText(), StringExpression.class)); + this.getAttributeName(ctx), StringExpression.class)); } /*** References with context override ***/ @@ -1139,7 +1142,7 @@ public void exitFieldReferenceWithVariableContextOverride( @Override public void exitCodelistReference(CodelistReferenceContext ctx) { - this.stack.push(this.script.composeList(this.symbols.expandCodelist(ctx.codeListId.getText()) + this.stack.push(this.script.composeList(this.symbols.expandCodelist(this.getCodelistName(ctx)) .stream().map(s -> this.script.getStringLiteralFromUnquotedString(s)) .collect(Collectors.toList()), StringListExpression.class)); } @@ -1508,8 +1511,24 @@ private > void exitExceptFunct this.stack.push(this.script.composeExceptFunction(one, two, listType)); } + protected String getCodelistName(String efxCodelistIdentifier) { + return StringUtils.substringAfter(efxCodelistIdentifier, CODELIST_PREFIX); + } + + private String getCodelistName(CodelistReferenceContext ctx) { + return this.getCodelistName(ctx.CodelistId().getText()); + } + + protected String getAttributeName(String efxAttributeIdentifier) { + return StringUtils.substringAfter(efxAttributeIdentifier, ATTRIBUTE_PREFIX); + } + + private String getAttributeName(ScalarFromAttributeReferenceContext ctx) { + return this.getAttributeName(ctx.attributeReference().Attribute().getText()); + } + protected String getVariableName(String efxVariableIdentifier) { - return StringUtils.stripStart(efxVariableIdentifier, "$"); + return StringUtils.substringAfter(efxVariableIdentifier, VARIABLE_PREFIX); } private String getVariableName(VariableReferenceContext ctx) { diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java index aeacec6a..0187efae 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java @@ -7,13 +7,20 @@ @SdkComponent(versions = {"2"}, componentType = SdkComponentType.FIELD) public class SdkFieldV2 extends SdkFieldV1 { + private final String alias; public SdkFieldV2(String id, String type, String parentNodeId, String xpathAbsolute, - String xpathRelative, String rootCodelistId) { + String xpathRelative, String rootCodelistId, String alias) { super(id, type, parentNodeId, xpathAbsolute, xpathRelative, rootCodelistId); + this.alias = alias; } - public SdkFieldV2(JsonNode field) { - super(field); + public SdkFieldV2(JsonNode fieldNode) { + super(fieldNode); + this.alias = fieldNode.has("alias") ? fieldNode.get("alias").asText(null) : null; + } + + public String getAlias() { + return alias; } } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java index 48a6cae8..9bac4149 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java @@ -10,13 +10,20 @@ */ @SdkComponent(versions = {"2"}, componentType = SdkComponentType.NODE) public class SdkNodeV2 extends SdkNodeV1 { + private final String alias; public SdkNodeV2(String id, String parentId, String xpathAbsolute, String xpathRelative, - boolean repeatable) { + boolean repeatable, String alias) { super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + this.alias = alias; } public SdkNodeV2(JsonNode node) { super(node); + this.alias = node.has("alias") ? node.get("alias").asText(null) : null; + } + + public String getAlias() { + return alias; } } diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java index 8ec20cc0..ec9b219e 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java @@ -94,13 +94,13 @@ void testLikePatternCondition_WithNot() { void testFieldValueComparison_UsingTextFields() { assertEquals( "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", - test("ND-Root", "BT-00-Text == BT-00-Text-Multilingual")); + test("Root", "text == textMultilingual")); } @Test void testFieldValueComparison_UsingNumericFields() { assertEquals("PathNode/NumberField/number() <= PathNode/IntegerField/number()", - test("ND-Root", "BT-00-Number <= BT-00-Integer")); + test("ND-Root", "BT-00-Number <= integer")); } @Test @@ -137,7 +137,7 @@ void testFieldValueComparison_WithStringLiteral() { @Test void testFieldValueComparison_WithNumericLiteral() { assertEquals("PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", - test("ND-Root", "BT-00-Integer - BT-00-Number > 0")); + test("ND-Root", "integer - BT-00-Number > 0")); } @Test @@ -174,7 +174,7 @@ void testNumericComparison() { assertEquals( "2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", test("BT-00-Text", - "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > BT-00-Integer")); + "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer")); } @Test @@ -978,7 +978,7 @@ void testDurationList_UsingDurationField() { @Test void testCodeList() { - assertEquals("'a' = ('code1','code2','code3')", test("BT-00-Text", "'a' in (accessibility)")); + assertEquals("'a' = ('code1','code2','code3')", test("BT-00-Text", "'a' in codelist:accessibility")); } @@ -1034,19 +1034,19 @@ void testFieldReferenceWithFieldContextOverride() { @Test void testFieldReferenceWithFieldContextOverride_WithIntegerField() { assertEquals("../IntegerField/number()", - test("BT-00-Code", "BT-01-SubLevel-Text::BT-00-Integer")); + test("BT-00-Code", "BT-01-SubLevel-Text::integer")); } @Test void testFieldReferenceWithNodeContextOverride() { assertEquals("../../PathNode/IntegerField/number()", - test("BT-00-Text", "ND-Root::BT-00-Integer")); + test("BT-00-Text", "ND-Root::integer")); } @Test void testFieldReferenceWithNodeContextOverride_WithPredicate() { assertEquals("../../PathNode/IntegerField/number()", - test("BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::BT-00-Integer")); + test("BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::integer")); } @Test @@ -1069,7 +1069,7 @@ void testFieldReference_ForDurationFields() { @Test void testFieldReference_WithAxis() { assertEquals("./preceding::PathNode/IntegerField/number()", - test("ND-Root", "ND-Root::preceding::BT-00-Integer")); + test("ND-Root", "ND-Root::preceding::integer")); } /*** Boolean functions ***/ diff --git a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java index 3e3a4df2..f97acc38 100644 --- a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; import java.util.Optional; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.slf4j.Logger; @@ -19,7 +20,7 @@ import eu.europa.ted.eforms.sdk.entity.SdkNode; import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.sdk1.entity.*; +import eu.europa.ted.efx.sdk2.entity.*; import eu.europa.ted.efx.xpath.XPathContextualizer; public class SymbolResolverMock implements SymbolResolver { @@ -32,9 +33,11 @@ public static SymbolResolverMock getInstance(final String sdkVersion) { return instances.computeIfAbsent(sdkVersion, k -> new SymbolResolverMock()); } - protected Map fieldById; - protected Map nodeById; - protected Map codelistById; + protected Map fieldById; + protected Map fieldByAlias; + protected Map nodeById; + protected Map nodeByAlias; + protected Map codelistById; protected SymbolResolverMock() { try { @@ -52,57 +55,60 @@ private static JsonNode fromString(final String jsonString) public void loadMapData() throws JsonMappingException, JsonProcessingException { this.fieldById = Map.ofEntries(// - entry("BT-00-Text", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField\",\"xpathRelative\":\"PathNode/TextField\"}"))), - entry("BT-00-Attribute", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Attribute\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField/@Attribute\",\"xpathRelative\":\"PathNode/TextField/@Attribute\"}"))), - entry("BT-00-Indicator", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-indicator\",\"type\":\"indicator\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IndicatorField\",\"xpathRelative\":\"PathNode/IndicatorField\"}"))), - entry("BT-00-Code", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Code\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-Internal-Code", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Internal-Code\",\"type\":\"internal-code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/InternalCodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-CodeAttribute", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-CodeAttribute\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField/@attribute\",\"xpathRelative\":\"PathNode/CodeField/@attribute\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-Text-Multilingual", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Text-Multilingual\",\"type\":\"text-multilingual\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextMultilingualField\",\"xpathRelative\":\"PathNode/TextMultilingualField\"}}"))), - entry("BT-00-StartDate", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-StartDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartDateField\",\"xpathRelative\":\"PathNode/StartDateField\"}}"))), - entry("BT-00-EndDate", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-EndDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndDateField\",\"xpathRelative\":\"PathNode/EndDateField\"}}"))), - entry("BT-00-StartTime", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-StartTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartTimeField\",\"xpathRelative\":\"PathNode/StartTimeField\"}}"))), - entry("BT-00-EndTime", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-EndTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndTimeField\",\"xpathRelative\":\"PathNode/EndTimeField\"}}"))), - entry("BT-00-Measure", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Measure\",\"type\":\"measure\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/MeasureField\",\"xpathRelative\":\"PathNode/MeasureField\"}}"))), - entry("BT-00-Integer", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Integer\",\"type\":\"integer\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IntegerField\",\"xpathRelative\":\"PathNode/IntegerField\"}}"))), - entry("BT-00-Amount", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Amount\",\"type\":\"amount\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/AmountField\",\"xpathRelative\":\"PathNode/AmountField\"}}"))), - entry("BT-00-Url", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Url\",\"type\":\"url\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/UrlField\",\"xpathRelative\":\"PathNode/UrlField\"}}"))), - entry("BT-00-Zoned-Date", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Zoned-Date\",\"type\":\"zoned-date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedDateField\",\"xpathRelative\":\"PathNode/ZonedDateField\"}}"))), - entry("BT-00-Zoned-Time", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Zoned-Time\",\"type\":\"zoned-time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedTimeField\",\"xpathRelative\":\"PathNode/ZonedTimeField\"}}"))), - entry("BT-00-Id-Ref", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Id-Ref\",\"type\":\"id-ref\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IdRefField\",\"xpathRelative\":\"PathNode/IdRefField\"}}"))), - entry("BT-00-Number", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Number\",\"type\":\"number\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/NumberField\",\"xpathRelative\":\"PathNode/NumberField\"}}"))), - entry("BT-00-Phone", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Phone\",\"type\":\"phone\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/PhoneField\",\"xpathRelative\":\"PathNode/PhoneField\"}}"))), - entry("BT-00-Email", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Email\",\"type\":\"email\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EmailField\",\"xpathRelative\":\"PathNode/EmailField\"}}"))), - entry("BT-01-SubLevel-Text", new SdkFieldV1(fromString( - "{\"id\":\"BT-01-SubLevel-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ChildNode/SubLevelTextField\",\"xpathRelative\":\"PathNode/ChildNode/SubLevelTextField\"}}"))), - entry("BT-01-SubNode-Text", new SdkFieldV1(fromString( - "{\"id\":\"BT-01-SubNode-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-SubNode\",\"xpathAbsolute\":\"/*/SubNode/SubTextField\",\"xpathRelative\":\"SubTextField\"}}")))); + entry("BT-00-Text", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Text\",\"alias\":\"text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField\",\"xpathRelative\":\"PathNode/TextField\"}"))), + entry("BT-00-Attribute", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Attribute\",\"alias\":\"attribute\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField/@Attribute\",\"xpathRelative\":\"PathNode/TextField/@Attribute\"}"))), + entry("BT-00-Indicator", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-indicator\",\"alias\":\"indicator\",\"type\":\"indicator\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IndicatorField\",\"xpathRelative\":\"PathNode/IndicatorField\"}"))), + entry("BT-00-Code", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Code\",\"alias\":\"code\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), + entry("BT-00-Internal-Code", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Internal-Code\",\"alias\":\"internalCode\",\"type\":\"internal-code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/InternalCodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), + entry("BT-00-CodeAttribute", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-CodeAttribute\",\"alias\":\"codeAttribute\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField/@attribute\",\"xpathRelative\":\"PathNode/CodeField/@attribute\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), + entry("BT-00-Text-Multilingual", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Text-Multilingual\",\"alias\":\"textMultilingual\",\"type\":\"text-multilingual\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextMultilingualField\",\"xpathRelative\":\"PathNode/TextMultilingualField\"}}"))), + entry("BT-00-StartDate", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-StartDate\",\"alias\":\"startDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartDateField\",\"xpathRelative\":\"PathNode/StartDateField\"}}"))), + entry("BT-00-EndDate", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-EndDate\",\"alias\":\"endDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndDateField\",\"xpathRelative\":\"PathNode/EndDateField\"}}"))), + entry("BT-00-StartTime", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-StartTime\",\"alias\":\"startTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartTimeField\",\"xpathRelative\":\"PathNode/StartTimeField\"}}"))), + entry("BT-00-EndTime", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-EndTime\",\"alias\":\"endTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndTimeField\",\"xpathRelative\":\"PathNode/EndTimeField\"}}"))), + entry("BT-00-Measure", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Measure\",\"alias\":\"measure\",\"type\":\"measure\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/MeasureField\",\"xpathRelative\":\"PathNode/MeasureField\"}}"))), + entry("BT-00-Integer", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Integer\",\"alias\":\"integer\",\"type\":\"integer\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IntegerField\",\"xpathRelative\":\"PathNode/IntegerField\"}}"))), + entry("BT-00-Amount", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Amount\",\"alias\":\"amount\",\"type\":\"amount\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/AmountField\",\"xpathRelative\":\"PathNode/AmountField\"}}"))), + entry("BT-00-Url", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Url\",\"alias\":\"url\",\"type\":\"url\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/UrlField\",\"xpathRelative\":\"PathNode/UrlField\"}}"))), + entry("BT-00-Zoned-Date", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Zoned-Date\",\"alias\":\"zonedDate\",\"type\":\"zoned-date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedDateField\",\"xpathRelative\":\"PathNode/ZonedDateField\"}}"))), + entry("BT-00-Zoned-Time", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Zoned-Time\",\"alias\":\"zonedTime\",\"type\":\"zoned-time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedTimeField\",\"xpathRelative\":\"PathNode/ZonedTimeField\"}}"))), + entry("BT-00-Id-Ref", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Id-Ref\",\"alias\":\"idRef\",\"type\":\"id-ref\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IdRefField\",\"xpathRelative\":\"PathNode/IdRefField\"}}"))), + entry("BT-00-Number", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Number\",\"alias\":\"number\",\"type\":\"number\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/NumberField\",\"xpathRelative\":\"PathNode/NumberField\"}}"))), + entry("BT-00-Phone", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Phone\",\"alias\":\"phone\",\"type\":\"phone\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/PhoneField\",\"xpathRelative\":\"PathNode/PhoneField\"}}"))), + entry("BT-00-Email", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-Email\",\"alias\":\"email\",\"type\":\"email\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EmailField\",\"xpathRelative\":\"PathNode/EmailField\"}}"))), + entry("BT-01-SubLevel-Text", new SdkFieldV2(fromString( + "{\"id\":\"BT-01-SubLevel-Text\",\"alias\":\"subLevel_text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ChildNode/SubLevelTextField\",\"xpathRelative\":\"PathNode/ChildNode/SubLevelTextField\"}}"))), + entry("BT-01-SubNode-Text", new SdkFieldV2(fromString( + "{\"id\":\"BT-01-SubNode-Text\",\"alias\":\"subNode_text\",\"type\":\"text\",\"parentNodeId\":\"ND-SubNode\",\"xpathAbsolute\":\"/*/SubNode/SubTextField\",\"xpathRelative\":\"SubTextField\"}}")))); + + this.fieldByAlias = this.fieldById.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); this.nodeById = Map.ofEntries(// - entry("ND-Root", new SdkNodeV1("ND-Root", null, "/*", "/*", false)), entry("ND-SubNode", - new SdkNodeV1("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false))); + entry("ND-Root", new SdkNodeV2("ND-Root", null, "/*", "/*", false, "Root")), entry("ND-SubNode", + new SdkNodeV2("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false, "SubNode"))); + this.nodeByAlias = this.nodeById.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); this.codelistById = new HashMap<>(Map.ofEntries( buildCodelistMock("accessibility", Optional.empty()), @@ -111,14 +117,14 @@ public void loadMapData() throws JsonMappingException, JsonProcessingException { )); } - private static Entry buildCodelistMock(final String codelistId, + private static Entry buildCodelistMock(final String codelistId, final Optional parentId) { - return entry(codelistId, new SdkCodelistV1(codelistId, "0.0.1", + return entry(codelistId, new SdkCodelistV2(codelistId, "0.0.1", Arrays.asList("code1", "code2", "code3"), parentId)); } public SdkField getFieldById(String fieldId) { - return this.fieldById.get(fieldId); + return this.fieldById.containsKey(fieldId) ? this.fieldById.get(fieldId) : this.fieldByAlias.get(fieldId); } @Override @@ -138,7 +144,7 @@ public PathExpression getRelativePath(PathExpression absolutePath, PathExpressio @Override public String getTypeOfField(String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); + final SdkField sdkField = getFieldById(fieldId); if (sdkField == null) { throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); } @@ -147,7 +153,7 @@ public String getTypeOfField(String fieldId) { @Override public String getRootCodelistOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); + final SdkField sdkField = getFieldById(fieldId); if (sdkField == null) { throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); } @@ -181,7 +187,7 @@ public List expandCodelist(String codelistId) { */ @Override public String getParentNodeOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); + final SdkField sdkField = getFieldById(fieldId); if (sdkField != null) { return sdkField.getParentNodeId(); } @@ -194,7 +200,7 @@ public String getParentNodeOfField(final String fieldId) { */ @Override public PathExpression getAbsolutePathOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); + final SdkField sdkField = getFieldById(fieldId); if (sdkField == null) { throw new ParseCancellationException( String.format("Unknown field identifier '%s'.", fieldId)); @@ -208,7 +214,7 @@ public PathExpression getAbsolutePathOfField(final String fieldId) { */ @Override public PathExpression getAbsolutePathOfNode(final String nodeId) { - final SdkNode sdkNode = nodeById.get(nodeId); + final SdkNode sdkNode = nodeById.containsKey(nodeId) ? nodeById.get(nodeId) : nodeByAlias.get(nodeId); if (sdkNode == null) { throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); } From 8bdbbe80454d25dc0660ed880e52aef70657f602 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Mon, 10 Apr 2023 00:45:49 +0200 Subject: [PATCH 07/57] Revisited template variables implementation to add comments and fix bugs (TEDEFO-1376). --- .../eu/europa/ted/efx/model/CallStack.java | 177 +++++++++++++++--- .../efx/sdk2/EfxExpressionTranslatorV2.java | 30 +-- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 8 + .../ted/efx/EfxTemplateTranslatorTest.java | 12 +- 4 files changed, 186 insertions(+), 41 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java index 6ae9333d..2a7f03a3 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -7,43 +7,87 @@ import org.antlr.v4.runtime.misc.ParseCancellationException; +/** + * The call stack is a stack of stack frames. Each stack frame represents a + * scope. The top of the stack is the current scope. The bottom of the stack is + * the global scope. + */ public class CallStack { + private static final String TYPE_MISMATCH = "Type mismatch. Expected %s instead of %s."; + private static final String UNDECLARED_IDENTIFIER = "Identifier not declared: "; + private static final String IDENTIFIER_ALREADY_DECLARED = "Identifier already declared: "; + private static final String STACK_UNDERFLOW = "Stack underflow. Return values were available in the dropped frame, but no stack frame is left to consume them."; + + /** + * Stack frames are means of controlling the scope of variables and parameters. + * Certain sub-expressions are scoped, meaning that variables and parameters are + * only available within the scope of the sub-expression. + */ class StackFrame extends Stack { + /** + * Keeps a list of all identifiers declared in the current scope as well as + * their type. + */ Map> typeRegister = new HashMap>(); + /** + * Keeps a list of all parameter values declared in the current scope. + */ Map valueRegister = new HashMap(); - public void pushParameterDeclaration(String parameterName, Expression parameterDeclarationExpression, + /** + * Registers a parameter identifier and pushes a parameter declaration on the + * current stack frame. Also stores the parameter value. + */ + void pushParameterDeclaration(String parameterName, Expression parameterDeclarationExpression, Expression parameterValue) { this.declareIdentifier(parameterName, parameterDeclarationExpression.getClass()); this.storeValue(parameterName, parameterValue); this.push(parameterDeclarationExpression); } - public void pushVariableDeclaration(String variableName, Expression variableDeclarationExpression) { + /** + * Registers a variable identifier and pushes a variable declaration on the + * current stack frame. + */ + void pushVariableDeclaration(String variableName, Expression variableDeclarationExpression) { this.declareIdentifier(variableName, variableDeclarationExpression.getClass()); this.push(variableDeclarationExpression); } - public void declareIdentifier(String identifier, Class type) { + /** + * Registers an identifier in the current scope. + * This registration is later used to check if an identifier is declared in the + * current scope. + */ + void declareIdentifier(String identifier, Class type) { this.typeRegister.put(identifier, type); } - private void storeValue(String identifier, Expression value) { + /** + * Used to store parameter values. + */ + void storeValue(String identifier, Expression value) { this.valueRegister.put(identifier, value); } - public synchronized T pop(Class expectedType) { + /** + * Returns the object at the top of the stack and removes it from the stack. + * The object must be of the expected type. + */ + synchronized T pop(Class expectedType) { Class actualType = peek().getClass(); if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) { - throw new ParseCancellationException("Type mismatch. Expected " + expectedType.getSimpleName() - + " instead of " + this.peek().getClass().getSimpleName()); + throw new ParseCancellationException(String.format(TYPE_MISMATCH, expectedType.getSimpleName(), this.peek().getClass().getSimpleName())); } return expectedType.cast(this.pop()); } + /** + * Clears the stack frame and all its registers. + */ @Override public void clear() { super.clear(); @@ -52,101 +96,182 @@ public void clear() { } } + /** + * The stack of stack frames. + */ Stack frames; + /** + * Default and only constructor. + * Adds a global scope to the stack. + */ public CallStack() { this.frames = new Stack<>(); - this.frames.push(new StackFrame()); + this.frames.push(new StackFrame()); // The global scope } + /** + * Creates a new stack frame and pushes it on top of the call stack. + * + * This method is called at the begin boundary of scoped sub-expression to + * allow for the declaration of local variables. + */ public void pushStackFrame() { this.frames.push(new StackFrame()); } + /** + * Drops the current stack frame and passes the return values to the previous + * stack frame. + * + * This method is called at the end boundary of scoped sub-expressions. + * Variables local to the sub-expression must go out of scope and the return + * values are passed to the parent expression. + */ public void popStackFrame() { StackFrame droppedFrame = this.frames.pop(); - this.frames.peek().addAll(droppedFrame); // pass return values to the current stack frame + + // If the dropped frame is not empty, then it contains return values that should + // be passed to the next frame on the stack. + if (droppedFrame.size() > 0) { + if (this.frames.empty()) { + throw new ParseCancellationException(STACK_UNDERFLOW); + } + this.frames.peek().addAll(droppedFrame); + } } + /** + * Pushes a parameter declaration on the current stack frame. + * Checks if another identifier with the same name is already declared in the + * current scope. + */ public void pushParameterDeclaration(String parameterName, Expression parameterDeclaration, Expression parameterValue) { if (this.inScope(parameterName)) { - throw new ParseCancellationException("Identifier " + parameterDeclaration.script + " already declared."); - } else if (parameterDeclaration.getClass() == Expression.class) { - throw new ParseCancellationException(); - } else { - this.frames.peek().pushParameterDeclaration(parameterName, parameterDeclaration, parameterValue); + throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + parameterDeclaration.script); } + this.frames.peek().pushParameterDeclaration(parameterName, parameterDeclaration, parameterValue); } + /** + * Pushes a variable declaration on the current stack frame. + * Checks if another identifier with the same name is already declared in the + * current scope. + */ public void pushVariableDeclaration(String variableName, Expression variableDeclaration) { if (this.inScope(variableName)) { - throw new ParseCancellationException("Identifier " + variableDeclaration.script + " already declared."); - } else if (variableDeclaration.getClass() == Expression.class) { - throw new ParseCancellationException(); - } else { - this.frames.peek().pushVariableDeclaration(variableName, variableDeclaration); + throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + variableDeclaration.script); } + this.frames.peek().pushVariableDeclaration(variableName, variableDeclaration); } + /** + * Declares a template variable. Template variables are tracked to ensure proper + * scoping. However, their declaration is not pushed on the stack as they are + * declared at the template level (in Markup) and not at the expression level + * (not in the target language script). + */ public void declareTemplateVariable(String variableName, Class variableType) { if (this.inScope(variableName)) { - throw new ParseCancellationException("Identifier " + variableName + " already declared."); - } else if (variableType == Expression.class) { - throw new ParseCancellationException(); - } else { - this.frames.peek().declareIdentifier(variableName, variableType); + throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + variableName); } + this.frames.peek().declareIdentifier(variableName, variableType); } + /** + * Checks if an identifier is declared in the current scope. + */ boolean inScope(String identifier) { return this.frames.stream().anyMatch(f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier) ); } + /** + * Returns the stack frame containing the given identifier. + */ StackFrame findFrameContaining(String identifier) { return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)).findFirst().orElse(null); } + /** + * Gets the value of a parameter. + */ Optional getParameter(String identifier) { return this.frames.stream().filter(f -> f.valueRegister.containsKey(identifier)).findFirst().map(x -> x.valueRegister.get(identifier)); } + /** + * Gets the type of a variable. + */ Optional> getVariable(String identifier) { return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier)).findFirst().map(x -> x.typeRegister.get(identifier)); } + /** + * Pushes a variable reference on the current stack frame. + * Makes sure there is no name collision with other identifiers already in + * scope. + */ public void pushVariableReference(String variableName, Expression variableReference) { getParameter(variableName).ifPresentOrElse(parameterValue -> this.push(parameterValue), () -> getVariable(variableName).ifPresentOrElse( variableType -> this.pushVariableReference(variableReference, variableType), () -> { - throw new ParseCancellationException("Identifier " + variableName + " not declared."); + throw new ParseCancellationException(UNDECLARED_IDENTIFIER + variableName); })); } + /** + * Pushes a variable reference on the current stack frame. + * This method is private because it is only used for to improve the readability + * of its public counterpart. + */ private void pushVariableReference(Expression variableReference, Class variableType) { this.frames.peek().push(Expression.instantiate(variableReference.script, variableType)); } + /** + * Pushes an object on the current stack frame. + * No checks, no questions asked. + */ public void push(CallStackObject item) { this.frames.peek().push(item); } + /** + * Returns the object at the top of the current stack frame and removes it from + * the stack. + * + * @param expectedType The that the returned object is expected to have. + */ public synchronized T pop(Class expectedType) { return this.frames.peek().pop(expectedType); } + /** + * Returns the object at the top of the current stack frame without removing it + * from the stack. + */ public synchronized CallStackObject peek() { return this.frames.peek().peek(); } + /** + * Returns the number of elements in the current stack frame. + */ public int size() { return this.frames.peek().size(); } - + + /** + * Returns true if the current stack frame is empty. + */ public boolean empty() { return this.frames.peek().empty(); } + /** + * Clears the current stack frame. + */ public void clear() { this.frames.peek().clear(); } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 566b3669..20a94a9f 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -432,6 +432,11 @@ private > void exitInListCondi /*** Quantified expressions ***/ + @Override + public void enterQuantifiedExpression(QuantifiedExpressionContext ctx) { + this.stack.pushStackFrame(); // Quantified expressions need their own scope because they introduce new variables. + } + @Override public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { BooleanExpression booleanExpression = this.stack.pop(BooleanExpression.class); @@ -442,6 +447,7 @@ public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { this.stack.push(this.script.composeAnySatisfies(this.stack.pop(IteratorListExpression.class), booleanExpression)); } + this.stack.popStackFrame(); // Variables declared in the quantified expression go out of scope here. } /*** Numeric expressions ***/ @@ -762,68 +768,68 @@ public void exitParenthesizedDurationsFromIteration( @Override public void enterStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { - this.stack.pushStackFrame(); + this.stack.pushStackFrame(); // Iteration variables are local to the iteration } @Override public void exitStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { this.exitIterationExpression(StringExpression.class, StringListExpression.class); - this.stack.popStackFrame(); + this.stack.popStackFrame(); // Iteration variables are local to the iteration } @Override public void enterNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { - this.stack.pushStackFrame(); + this.stack.pushStackFrame(); // Iteration variables are local to the iteration } @Override public void exitNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { this.exitIterationExpression(NumericExpression.class, NumericListExpression.class); - this.stack.popStackFrame(); + this.stack.popStackFrame(); // Iteration variables are local to the iteration } @Override public void enterBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { - this.stack.pushStackFrame(); + this.stack.pushStackFrame(); // Iteration variables are local to the iteration } @Override public void exitBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class); - this.stack.popStackFrame(); + this.stack.popStackFrame(); // Iteration variables are local to the iteration } @Override public void enterDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { - this.stack.pushStackFrame(); + this.stack.pushStackFrame(); // Iteration variables are local to the iteration } @Override public void exitDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { this.exitIterationExpression(DateExpression.class, DateListExpression.class); - this.stack.popStackFrame(); + this.stack.popStackFrame(); // Iteration variables are local to the iteration } @Override public void enterTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { - this.stack.pushStackFrame(); + this.stack.pushStackFrame(); // Iteration variables are local to the iteration } @Override public void exitTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { this.exitIterationExpression(TimeExpression.class, TimeListExpression.class); - this.stack.popStackFrame(); + this.stack.popStackFrame(); // Iteration variables are local to the iteration } @Override public void enterDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { - this.stack.pushStackFrame(); + this.stack.pushStackFrame(); // Iteration variables are local to the iteration } @Override public void exitDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { this.exitIterationExpression(DurationExpression.class, DurationListExpression.class); - this.stack.popStackFrame(); + this.stack.popStackFrame(); // Iteration variables are local to the iteration } public > void exitIteratorExpression( 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 f60e1472..e8db600b 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -539,14 +539,22 @@ public void enterTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } + this.stack.pushStackFrame(); // Create a stack frame for the new template line. } else if (indentChange < 0) { for (int i = indentChange; i < 0; i++) { assert !this.blockStack.isEmpty() : UNEXPECTED_INDENTATION; assert this.blockStack.currentIndentationLevel() > indentLevel : UNEXPECTED_INDENTATION; this.blockStack.pop(); + this.stack.popStackFrame(); // Each skipped indentation level must go out of scope. } + this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation level). + this.stack.pushStackFrame(); // Create a stack frame for the new template line. assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; } + else if (indentChange == 0) { + this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation level). + this.stack.pushStackFrame(); // Create a stack frame for the new template line. + } } @Override diff --git a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java index ebe08920..3eb43fc0 100644 --- a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java @@ -184,15 +184,21 @@ void testTemplateLine_VariableScope() { void testTemplateLine_ContextVariable() { assertEquals( lines("let block01(t, ctx) -> { #1: eval(for $x in . return concat($x, $t))", // - "for-each(.).call(block0101(ctx:$ctx, t:$t, t2:'test')) }", // + "for-each(.).call(block0101(ctx:$ctx, t:$t, t2:'test'))", // + "for-each(.).call(block0102(ctx:$ctx, t:$t, t2:'test3')) }", // "let block0101(t, ctx, t2) -> { #1.1: eval(for $y in . return concat($y, $t, $t2))", // - "for-each(.).call(block010101(ctx:$ctx, t:$t, t2:$t2)) }", // + "for-each(.).call(block010101(ctx:$ctx, t:$t, t2:$t2))", // + "for-each(.).call(block010102(ctx:$ctx, t:$t, t2:$t2)) }", // "let block010101(t, ctx, t2) -> { eval(for $z in . return concat($z, $t, $ctx)) }", // + "let block010102(t, ctx, t2) -> { eval(for $z in . return concat($z, $t, $ctx)) }", // + "let block0102(t, ctx, t2) -> { eval(for $z in . return concat($z, $t2, $ctx)) }", // "for-each(/*/PathNode/TextField).call(block01(ctx:., t:./normalize-space(text())))"), // translate(lines( "{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)}", - " {BT-00-Text} ${for text:$z in BT-00-Text return concat($z, $t, $ctx)}"))); + " {BT-00-Text} ${for text:$z in BT-00-Text return concat($z, $t, $ctx)}", + " {BT-00-Text} ${for text:$z in BT-00-Text return concat($z, $t, $ctx)}", + " {BT-00-Text, text:$t2 = 'test3'} ${for text:$z in BT-00-Text return concat($z, $t2, $ctx)}"))); } From 02d1f92f78fe9fd33de876e762c5353d33aa3564 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sat, 15 Apr 2023 14:35:52 +0200 Subject: [PATCH 08/57] Restored some SDK2 changes after merge form SDK1 develop --- .../europa/ted/efx/interfaces/MarkupGenerator.java | 14 +++++++++++++- .../java/eu/europa/ted/efx/model/ContentBlock.java | 4 ++-- .../europa/ted/efx/mock/MarkupGeneratorMock.java | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) 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 f1f4b5ab..033a5098 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -72,10 +72,22 @@ public interface MarkupGenerator { @Deprecated(since = "2.0.0", forRemoval = true) Markup composeFragmentDefinition(final String name, String number, Markup content); + /** + * Given a fragment name (identifier) and some pre-rendered content, this method + * returns the code + * that encapsulates it in the target template. + */ + Markup composeFragmentDefinition(final String name, String number, Markup content, Set parameters); + + /** + * @deprecated Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead. + */ + @Deprecated(since = "2.0.0", forRemoval = true) + Markup renderFragmentInvocation(final String name, final PathExpression context); /** * Given a fragment name (identifier), and an evaluation context, this method returns the code * that invokes (uses) the fragment. */ - Markup renderFragmentInvocation(final String name, final PathExpression context); + Markup renderFragmentInvocation(final String name, final PathExpression context, final Set> variables); } diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java index 26817626..c2fb4153 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java +++ b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java @@ -147,7 +147,7 @@ public Markup renderContent(MarkupGenerator markupGenerator) { public void renderTemplate(MarkupGenerator markupGenerator, List templates) { templates.add(markupGenerator.composeFragmentDefinition(this.id, this.getOutlineNumber(), - this.renderContent(markupGenerator))); + this.renderContent(markupGenerator), this.getTemplateParameters())); for (ContentBlock child : this.children) { child.renderTemplate(markupGenerator, templates); } @@ -159,6 +159,6 @@ public Markup renderCallTemplate(MarkupGenerator markupGenerator) { variables.addAll(parent.getAllVariables().stream().map(v -> Pair.of(v.name, v.referenceExpression.script)).collect(Collectors.toList())); } variables.addAll(this.getOwnVariables().stream().map(v -> Pair.of(v.name, v.initializationExpression.script)).collect(Collectors.toList())); - return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath()); + return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath(), variables); } } 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 830e906e..9b56446a 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -41,6 +41,7 @@ public Markup composeFragmentDefinition(String name, String number, Markup conte return this.composeFragmentDefinition(name, number, content, new LinkedHashSet<>()); } + @Override public Markup composeFragmentDefinition(String name, String number, Markup content, Set parameters) { if (StringUtils.isBlank(number)) { return new Markup(String.format("let %s(%s) -> { %s }", name, @@ -55,6 +56,7 @@ public Markup renderFragmentInvocation(String name, PathExpression context) { return this.renderFragmentInvocation(name, context, new LinkedHashSet<>()); } + @Override public Markup renderFragmentInvocation(String name, PathExpression context, Set> variables) { return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name, variables.stream() From de55e37a8286f1042a6b43813233aceb579abbe4 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Mon, 17 Apr 2023 12:56:23 +0200 Subject: [PATCH 09/57] Added sequence indexers (TEDEFO-2105) --- .../ted/efx/interfaces/ScriptGenerator.java | 3 + .../efx/sdk2/EfxExpressionTranslatorV2.java | 38 ++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 9 +- .../ted/efx/EfxExpressionTranslatorTest.java | 90 +++++++++++++++---- 4 files changed, 121 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index d1411a39..893a13a4 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -354,4 +354,7 @@ public > L composeIntersectFun public > L composeExceptFunction(L listOne, L listTwo, Class listType); + + public > T composeIndexer(L list, + NumericExpression index, Class type); } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 20a94a9f..446aadd7 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1160,6 +1160,44 @@ public void exitVariableReference(VariableReferenceContext ctx) { this.script.composeVariableReference(variableName, Expression.class)); } + /*** Indexers ***/ + + @Override + public void exitStringAtSequenceIndex(StringAtSequenceIndexContext ctx) { + this.exitSequenceAtIndex(StringExpression.class, StringListExpression.class); + } + + @Override + public void exitNumericAtSequenceIndex(NumericAtSequenceIndexContext ctx) { + this.exitSequenceAtIndex(NumericExpression.class, NumericListExpression.class); + } + + @Override + public void exitBooleanAtSequenceIndex(BooleanAtSequenceIndexContext ctx) { + this.exitSequenceAtIndex(BooleanExpression.class, BooleanListExpression.class); + } + + @Override + public void exitDateAtSequenceIndex(DateAtSequenceIndexContext ctx) { + this.exitSequenceAtIndex(DateExpression.class, DateListExpression.class); + } + + @Override + public void exitTimeAtSequenceIndex(TimeAtSequenceIndexContext ctx) { + this.exitSequenceAtIndex(TimeExpression.class, TimeListExpression.class); + } + + @Override + public void exitDurationAtSequenceIndex(DurationAtSequenceIndexContext ctx) { + this.exitSequenceAtIndex(DurationExpression.class, DurationListExpression.class); + } + + private > void exitSequenceAtIndex(Class itemType, Class listType) { + NumericExpression index = this.stack.pop(NumericExpression.class); + L list = this.stack.pop(listType); + this.stack.push(this.script.composeIndexer(list, index, itemType)); + } + /*** Parameter Declarations ***/ diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 17c00c05..53184819 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -284,6 +284,14 @@ public PathExpression joinPaths(final PathExpression first, final PathExpression return XPathContextualizer.join(first, second); } + /** Indexers ***/ + + @Override + public > T composeIndexer(L list, NumericExpression index, + Class type) { + return Expression.instantiate(String.format("%s[%s]", list.script, index.script), type); + } + /*** BooleanExpressions ***/ @@ -536,5 +544,4 @@ private int getWeeksFromDurationLiteral(final String literal) { Matcher weeksMatcher = Pattern.compile("(?<=[^0-9])[0-9]+(?=W)").matcher(literal); return weeksMatcher.find() ? Integer.parseInt(weeksMatcher.group()) : 0; } - } diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java index ec9b219e..85b873b8 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java @@ -22,7 +22,7 @@ private String test1(final String expression, final String... params) { } } - /*** Boolean expressions ***/ + // #region: Boolean expressions --------------------------------------------- @Test void testParenthesizedBooleanExpression() { @@ -289,7 +289,9 @@ void testBooleanLiteralExpression_Never() { assertEquals("false()", test("BT-00-Text", "NEVER")); } - /*** Quantified expressions ***/ + // #endregion: Boolean expressions + + // #region: Quantified expressions ------------------------------------------ @Test void testStringQuantifiedExpression_UsingLiterals() { @@ -375,7 +377,9 @@ void testDurationQuantifiedExpression_UsingFieldReference() { test("ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D")); } - /*** Conditional expressions ***/ + // #endregion: Quantified expressions + + // #region: Conditional expressions ----------------------------------------- @Test void testConditionalExpression() { @@ -452,7 +456,9 @@ void testConditionalDurationExpression() { test("ND-Root", "if P1D > BT-00-Measure then P1D else P2D")); } - /*** Iteration expressions ***/ + // #endregion: Conditional expressions + + // #region: Iteration expressions ------------------------------------------- // Strings from iteration --------------------------------------------------- @@ -894,7 +900,9 @@ void testDurationsFromDurationIteration_UsingFieldReference() { test("ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)")); } - /*** Numeric expressions ***/ + // #endregion: Iteration expressions + + // #region: Numeric expressions --------------------------------------------- @Test void testMultiplicationExpression() { @@ -916,7 +924,9 @@ void testNumericLiteralExpression() { assertEquals("3.1415", test("BT-00-Text", "3.1415")); } - /*** List ***/ + // #endregion: Numeric expressions + + // #region: Lists ----------------------------------------------------------- @Test void testStringList() { @@ -981,8 +991,9 @@ void testCodeList() { assertEquals("'a' = ('code1','code2','code3')", test("BT-00-Text", "'a' in codelist:accessibility")); } - - /*** References ***/ + // #endregion: Lists + + // #region: References ------------------------------------------------------ @Test void testFieldAttributeValueReference() { @@ -1072,7 +1083,9 @@ void testFieldReference_WithAxis() { test("ND-Root", "ND-Root::preceding::integer")); } - /*** Boolean functions ***/ + // #endregion: References + + // #region: Boolean functions ----------------------------------------------- @Test void testNotFunction() { @@ -1099,7 +1112,9 @@ void testEndsWithFunction() { test("ND-Root", "ends-with(BT-00-Text, 'abc')")); } - /*** Numeric functions ***/ + // #endregion: Boolean functions + + // #region: Numeric functions ----------------------------------------------- @Test void testCountFunction_UsingFieldReference() { @@ -1135,7 +1150,9 @@ void testStringLengthFunction() { test("ND-Root", "string-length(BT-00-Text)")); } - /*** String functions ***/ + // #endregion: Numeric functions + + // #region: String functions ------------------------------------------------ @Test void testSubstringFunction() { @@ -1171,8 +1188,9 @@ void testFormatNumberFunction() { test("ND-Root", "format-number(BT-00-Number, '#,##0.00')")); } + // #endregion: String functions - /*** Date functions ***/ + // #region: Date functions -------------------------------------------------- @Test void testDateFromStringFunction() { @@ -1180,7 +1198,9 @@ void testDateFromStringFunction() { test("ND-Root", "date(BT-00-Text)")); } - /*** Time functions ***/ + // #endregion: Date functions + + // #region: Time functions -------------------------------------------------- @Test void testTimeFromStringFunction() { @@ -1188,7 +1208,9 @@ void testTimeFromStringFunction() { test("ND-Root", "time(BT-00-Text)")); } - /*** Sequence Functions ***/ + // #endregion: Time functions + + // #region: Sequence Functions ---------------------------------------------- @Test void testDistinctValuesFunction_WithStringSequences() { @@ -1226,7 +1248,7 @@ void testDistinctValuesFunction_WithFieldReferences() { test("ND-Root", "distinct-values(BT-00-Text)")); } - /* Union */ + // #region: Union @Test void testUnionFunction_WithStringSequences() { @@ -1270,7 +1292,9 @@ void testUnionFunction_WithTypeMismatch() { () -> test("ND-Root", "value-union(BT-00-Text, BT-00-Number)")); } - /* Intersect */ + // #endregion: Union + + // #region: Intersect @Test void testIntersectFunction_WithStringSequences() { @@ -1314,7 +1338,9 @@ void testIntersectFunction_WithTypeMismatch() { () -> test("ND-Root", "value-intersect(BT-00-Text, BT-00-Number)")); } - /* Except */ + // #endregion: Intersect + + // #region: Except @Test void testExceptFunction_WithStringSequences() { @@ -1358,7 +1384,9 @@ void testExceptFunction_WithTypeMismatch() { () -> test("ND-Root", "value-except(BT-00-Text, BT-00-Number)")); } - /* Compare sequences */ + // #endregion: Except + + // #region: Compare sequences @Test void testSequenceEqualFunction_WithStringSequences() { @@ -1443,4 +1471,30 @@ void testParametrizedExpression_WithDurationParameter() { assertEquals("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", test1("{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y")); } + + // #endregion: Compare sequences + + // #endregion Sequence Functions + + // #region: Indexers -------------------------------------------------------- + + @Test + void testIndexer_WithFieldReference() { + assertEquals("PathNode/TextField[1]", + test("ND-Root", "text:BT-00-Text[1]")); + } + + @Test + void testIndexer_WithFieldReferenceAndPredicate() { + assertEquals("PathNode/TextField[./normalize-space(text()) = 'hello'][1]", + test("ND-Root", "text:BT-00-Text[BT-00-Text == 'hello'][1]")); + } + + @Test + void testIndexer_WithTextSequence() { + assertEquals("('a','b','c')[1]", + test("ND-Root", "('a', 'b','c')[1]")); + } + + // #endregion: Indexers } From a696754c8b721b438c5b66c842d8515ec5f304de Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Mon, 17 Apr 2023 16:37:29 +0200 Subject: [PATCH 10/57] [formatting]: Formatted Java code and organised imports. --- .../ted/eforms/sdk/SdkSymbolResolver.java | 2 - .../java/eu/europa/ted/efx/EfxTranslator.java | 14 +- .../ted/efx/interfaces/MarkupGenerator.java | 11 +- .../ted/efx/interfaces/ScriptGenerator.java | 15 +- .../ted/efx/interfaces/SymbolResolver.java | 13 +- .../TranslatorDependencyFactory.java | 12 +- .../europa/ted/efx/model/BooleanVariable.java | 9 +- .../eu/europa/ted/efx/model/CallStack.java | 131 +++++++++--------- .../europa/ted/efx/model/CallStackObject.java | 3 +- .../eu/europa/ted/efx/model/ContentBlock.java | 23 +-- .../ted/efx/model/ContentBlockStack.java | 6 +- .../java/eu/europa/ted/efx/model/Context.java | 10 +- .../eu/europa/ted/efx/model/DateVariable.java | 9 +- .../ted/efx/model/DurationVariable.java | 9 +- .../eu/europa/ted/efx/model/Expression.java | 6 +- .../eu/europa/ted/efx/model/Identifier.java | 14 +- .../europa/ted/efx/model/NumericVariable.java | 9 +- .../europa/ted/efx/model/StringVariable.java | 7 +- .../eu/europa/ted/efx/model/TimeVariable.java | 9 +- .../eu/europa/ted/efx/model/Variable.java | 22 +-- .../eu/europa/ted/efx/model/VariableList.java | 34 ++--- .../sdk0/v6/EfxExpressionTranslator06.java | 4 +- .../efx/sdk0/v6/EfxTemplateTranslator06.java | 15 +- .../ted/efx/sdk0/v6/entity/SdkCodelist06.java | 7 +- .../ted/efx/sdk0/v6/entity/SdkNode06.java | 14 +- .../sdk0/v7/EfxExpressionTranslator07.java | 4 +- .../efx/sdk0/v7/EfxTemplateTranslator07.java | 15 +- .../ted/efx/sdk0/v7/entity/SdkCodelist07.java | 7 +- .../ted/efx/sdk0/v7/entity/SdkNode07.java | 14 +- .../efx/sdk1/EfxExpressionTranslatorV1.java | 59 ++++---- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 65 ++++++--- .../ted/efx/sdk1/entity/SdkCodelistV1.java | 3 +- .../ted/efx/sdk1/entity/SdkFieldV1.java | 3 +- .../europa/ted/efx/sdk1/entity/SdkNodeV1.java | 14 +- .../efx/sdk2/EfxExpressionTranslatorV2.java | 81 ++++++----- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 108 ++++++++++----- .../ted/efx/sdk2/entity/SdkCodelistV2.java | 9 +- .../ted/efx/sdk2/entity/SdkFieldV2.java | 2 +- .../europa/ted/efx/sdk2/entity/SdkNodeV2.java | 28 ++-- .../ted/efx/xpath/XPathContextualizer.java | 7 +- .../ted/efx/xpath/XPathScriptGenerator.java | 75 +++++----- .../ted/efx/EfxExpressionTranslatorTest.java | 108 +++++++++------ .../ted/efx/EfxTemplateTranslatorTest.java | 118 ++++++++-------- .../ted/efx/XPathContextualizerTest.java | 1 - .../ted/efx/mock/MarkupGeneratorMock.java | 11 +- .../ted/efx/mock/SymbolResolverMock.java | 28 ++-- 46 files changed, 669 insertions(+), 499 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java index 02265477..db4c427b 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -3,9 +3,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; - import org.antlr.v4.runtime.misc.ParseCancellationException; - import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.eforms.sdk.entity.SdkCodelist; diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslator.java b/src/main/java/eu/europa/ted/efx/EfxTranslator.java index 4c09c53f..e69c1df0 100644 --- a/src/main/java/eu/europa/ted/efx/EfxTranslator.java +++ b/src/main/java/eu/europa/ted/efx/EfxTranslator.java @@ -24,7 +24,7 @@ * an EFX translator to translate EFX expressions and templates. */ public class EfxTranslator { - + /** * Instantiates an EFX expression translator and translates a given expression. * @@ -38,7 +38,8 @@ public class EfxTranslator { * {@link TranslatorDependencyFactory}. * @throws InstantiationException */ - public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, + final String sdkVersion, final String expression, final String... expressionParameters) throws InstantiationException { return EfxTranslatorFactory.getEfxExpressionTranslator(sdkVersion, dependencyFactory) .translateExpression(expression, expressionParameters); @@ -58,7 +59,8 @@ public static String translateExpression(final TranslatorDependencyFactory depen * @throws IOException * @throws InstantiationException */ - public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, + final String sdkVersion, final Path pathname) throws IOException, InstantiationException { return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory) @@ -77,7 +79,8 @@ public static String translateTemplate(final TranslatorDependencyFactory depende * {@link TranslatorDependencyFactory}. * @throws InstantiationException */ - public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, + final String sdkVersion, final String template) throws InstantiationException { return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory) @@ -98,7 +101,8 @@ public static String translateTemplate(final TranslatorDependencyFactory depende * @throws IOException * @throws InstantiationException */ - public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, + final String sdkVersion, final InputStream stream) throws IOException, InstantiationException { return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory) 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 033a5098..9b9a2c0a 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -15,9 +15,7 @@ import java.util.List; import java.util.Set; - import org.apache.commons.lang3.tuple.Pair; - import eu.europa.ted.efx.model.Expression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; @@ -73,11 +71,11 @@ public interface MarkupGenerator { Markup composeFragmentDefinition(final String name, String number, Markup content); /** - * Given a fragment name (identifier) and some pre-rendered content, this method - * returns the code + * Given a fragment name (identifier) and some pre-rendered content, this method returns the code * that encapsulates it in the target template. */ - Markup composeFragmentDefinition(final String name, String number, Markup content, Set parameters); + Markup composeFragmentDefinition(final String name, String number, Markup content, + Set parameters); /** * @deprecated Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead. @@ -89,5 +87,6 @@ public interface MarkupGenerator { * Given a fragment name (identifier), and an evaluation context, this method returns the code * that invokes (uses) the fragment. */ - Markup renderFragmentInvocation(final String name, final PathExpression context, final Set> variables); + Markup renderFragmentInvocation(final String name, final PathExpression context, + final Set> variables); } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 893a13a4..1738f96b 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -14,7 +14,6 @@ package eu.europa.ted.efx.interfaces; import java.util.List; - import eu.europa.ted.efx.model.Expression; import eu.europa.ted.efx.model.Expression.BooleanExpression; import eu.europa.ted.efx.model.Expression.DateExpression; @@ -171,10 +170,10 @@ public > IteratorExpression co String variableName, L sourceList); public IteratorExpression composeIteratorExpression( - String variableName, PathExpression pathExpression); - + String variableName, PathExpression pathExpression); + public IteratorListExpression composeIteratorList(List iterators); - + /** * When we need data from an external source, we need some script that gets that data. Getting the * data is a two-step process: a) we need to access the data source, b) we need to get the actual @@ -293,9 +292,11 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public BooleanExpression composeExistsCondition(PathExpression reference); - public BooleanExpression composeUniqueValueCondition(PathExpression needle, PathExpression haystack); + public BooleanExpression composeUniqueValueCondition(PathExpression needle, + PathExpression haystack); - public BooleanExpression composeSequenceEqualFunction(ListExpression one, ListExpression two); + public BooleanExpression composeSequenceEqualFunction(ListExpression one, + ListExpression two); /* * Date Functions @@ -344,7 +345,7 @@ public DurationExpression composeSubtraction(final DurationExpression left, */ public > L composeDistinctValuesFunction( - L list, Class listType); + L list, Class listType); public > L composeUnionFunction(L listOne, L listTwo, Class listType); diff --git a/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java b/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java index 8b263a96..c4310e04 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java @@ -41,11 +41,12 @@ public interface SymbolResolver { * Gets the path that can be used to locate the given field in the data source, relative to * another given path. * - * The "path" points to a location in the data source. The path will be eventually used to retrieve - * the data from the data source. Typically, the data source is an XML file, in which case the - * path should be an XPath. If the data source is a JSON file, then the path should be a JsonPath. - * If you intend to use a function call to retrieve the data from the data source then that is what - * you should return as path. In general keep in mind that the path is used as target language script. + * The "path" points to a location in the data source. The path will be eventually used to + * retrieve the data from the data source. Typically, the data source is an XML file, in which + * case the path should be an XPath. If the data source is a JSON file, then the path should be a + * JsonPath. If you intend to use a function call to retrieve the data from the data source then + * that is what you should return as path. In general keep in mind that the path is used as target + * language script. * * @param fieldId The identifier of the field to look for. * @param contextPath The path relative to which we expect to find the return value. @@ -59,7 +60,7 @@ public PathExpression getRelativePathOfField(final String fieldId, * given path. * * See {@link getRelativePathOfField} for a description of the concept of "path". - * + * * @param nodeId The identifier of the node to look for. * @param contextPath The path relative to which we expect to find the return value. * @return The path to the given node relative to the given context path. diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java index e06dcd00..f7b27407 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java @@ -18,12 +18,12 @@ /** * Instantiates the dependencies needed by an EFX translator. * - * An EFX expression translator needs the following dependencies to be instantiated: - * - a SymbolResolver to resolve symbols (references of fields etc.). - * - a ScriptGenerator to provide target script language syntax for specific computations. - * - an error listener to handle any errors encountered during translation - * An EFX template translator needs the all three dependencies listed above, plus one more: - * - a MarkupGenerator to provide target markup language syntax for specific output constructs. + * An EFX expression translator needs the following dependencies to be instantiated: - a + * SymbolResolver to resolve symbols (references of fields etc.). - a ScriptGenerator to provide + * target script language syntax for specific computations. - an error listener to handle any errors + * encountered during translation An EFX template translator needs the all three dependencies listed + * above, plus one more: - a MarkupGenerator to provide target markup language syntax for specific + * output constructs. */ public interface TranslatorDependencyFactory { diff --git a/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java b/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java index 59810ac0..22c64247 100644 --- a/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java +++ b/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java @@ -4,7 +4,8 @@ public class BooleanVariable extends Variable { - public BooleanVariable(String variableName, BooleanExpression initializationExpression, BooleanExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } -} \ No newline at end of file + public BooleanVariable(String variableName, BooleanExpression initializationExpression, + BooleanExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java index 2a7f03a3..7f19bf28 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -4,33 +4,32 @@ import java.util.Map; import java.util.Optional; import java.util.Stack; - import org.antlr.v4.runtime.misc.ParseCancellationException; /** - * The call stack is a stack of stack frames. Each stack frame represents a - * scope. The top of the stack is the current scope. The bottom of the stack is - * the global scope. + * The call stack is a stack of stack frames. Each stack frame represents a scope. The top of the + * stack is the current scope. The bottom of the stack is the global scope. */ -public class CallStack { +public class CallStack { private static final String TYPE_MISMATCH = "Type mismatch. Expected %s instead of %s."; private static final String UNDECLARED_IDENTIFIER = "Identifier not declared: "; private static final String IDENTIFIER_ALREADY_DECLARED = "Identifier already declared: "; - private static final String STACK_UNDERFLOW = "Stack underflow. Return values were available in the dropped frame, but no stack frame is left to consume them."; + private static final String STACK_UNDERFLOW = + "Stack underflow. Return values were available in the dropped frame, but no stack frame is left to consume them."; /** - * Stack frames are means of controlling the scope of variables and parameters. - * Certain sub-expressions are scoped, meaning that variables and parameters are - * only available within the scope of the sub-expression. + * Stack frames are means of controlling the scope of variables and parameters. Certain + * sub-expressions are scoped, meaning that variables and parameters are only available within the + * scope of the sub-expression. */ class StackFrame extends Stack { /** - * Keeps a list of all identifiers declared in the current scope as well as - * their type. + * Keeps a list of all identifiers declared in the current scope as well as their type. */ - Map> typeRegister = new HashMap>(); + Map> typeRegister = + new HashMap>(); /** * Keeps a list of all parameter values declared in the current scope. @@ -38,8 +37,8 @@ class StackFrame extends Stack { Map valueRegister = new HashMap(); /** - * Registers a parameter identifier and pushes a parameter declaration on the - * current stack frame. Also stores the parameter value. + * Registers a parameter identifier and pushes a parameter declaration on the current stack + * frame. Also stores the parameter value. */ void pushParameterDeclaration(String parameterName, Expression parameterDeclarationExpression, Expression parameterValue) { @@ -49,8 +48,7 @@ void pushParameterDeclaration(String parameterName, Expression parameterDeclarat } /** - * Registers a variable identifier and pushes a variable declaration on the - * current stack frame. + * Registers a variable identifier and pushes a variable declaration on the current stack frame. */ void pushVariableDeclaration(String variableName, Expression variableDeclarationExpression) { this.declareIdentifier(variableName, variableDeclarationExpression.getClass()); @@ -58,9 +56,8 @@ void pushVariableDeclaration(String variableName, Expression variableDeclaration } /** - * Registers an identifier in the current scope. - * This registration is later used to check if an identifier is declared in the - * current scope. + * Registers an identifier in the current scope. This registration is later used to check if an + * identifier is declared in the current scope. */ void declareIdentifier(String identifier, Class type) { this.typeRegister.put(identifier, type); @@ -74,13 +71,14 @@ void storeValue(String identifier, Expression value) { } /** - * Returns the object at the top of the stack and removes it from the stack. - * The object must be of the expected type. + * Returns the object at the top of the stack and removes it from the stack. The object must be + * of the expected type. */ synchronized T pop(Class expectedType) { Class actualType = peek().getClass(); if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) { - throw new ParseCancellationException(String.format(TYPE_MISMATCH, expectedType.getSimpleName(), this.peek().getClass().getSimpleName())); + throw new ParseCancellationException(String.format(TYPE_MISMATCH, + expectedType.getSimpleName(), this.peek().getClass().getSimpleName())); } return expectedType.cast(this.pop()); } @@ -90,9 +88,9 @@ synchronized T pop(Class expectedType) { */ @Override public void clear() { - super.clear(); - this.typeRegister.clear(); - this.valueRegister.clear(); + super.clear(); + this.typeRegister.clear(); + this.valueRegister.clear(); } } @@ -102,8 +100,7 @@ public void clear() { Stack frames; /** - * Default and only constructor. - * Adds a global scope to the stack. + * Default and only constructor. Adds a global scope to the stack. */ public CallStack() { this.frames = new Stack<>(); @@ -113,20 +110,18 @@ public CallStack() { /** * Creates a new stack frame and pushes it on top of the call stack. * - * This method is called at the begin boundary of scoped sub-expression to - * allow for the declaration of local variables. + * This method is called at the begin boundary of scoped sub-expression to allow for the + * declaration of local variables. */ public void pushStackFrame() { this.frames.push(new StackFrame()); } /** - * Drops the current stack frame and passes the return values to the previous - * stack frame. + * Drops the current stack frame and passes the return values to the previous stack frame. * - * This method is called at the end boundary of scoped sub-expressions. - * Variables local to the sub-expression must go out of scope and the return - * values are passed to the parent expression. + * This method is called at the end boundary of scoped sub-expressions. Variables local to the + * sub-expression must go out of scope and the return values are passed to the parent expression. */ public void popStackFrame() { StackFrame droppedFrame = this.frames.pop(); @@ -142,36 +137,38 @@ public void popStackFrame() { } /** - * Pushes a parameter declaration on the current stack frame. - * Checks if another identifier with the same name is already declared in the - * current scope. + * Pushes a parameter declaration on the current stack frame. Checks if another identifier with + * the same name is already declared in the current scope. */ - public void pushParameterDeclaration(String parameterName, Expression parameterDeclaration, Expression parameterValue) { + public void pushParameterDeclaration(String parameterName, Expression parameterDeclaration, + Expression parameterValue) { if (this.inScope(parameterName)) { - throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + parameterDeclaration.script); + throw new ParseCancellationException( + IDENTIFIER_ALREADY_DECLARED + parameterDeclaration.script); } - this.frames.peek().pushParameterDeclaration(parameterName, parameterDeclaration, parameterValue); + this.frames.peek().pushParameterDeclaration(parameterName, parameterDeclaration, + parameterValue); } /** - * Pushes a variable declaration on the current stack frame. - * Checks if another identifier with the same name is already declared in the - * current scope. + * Pushes a variable declaration on the current stack frame. Checks if another identifier with the + * same name is already declared in the current scope. */ public void pushVariableDeclaration(String variableName, Expression variableDeclaration) { if (this.inScope(variableName)) { - throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + variableDeclaration.script); + throw new ParseCancellationException( + IDENTIFIER_ALREADY_DECLARED + variableDeclaration.script); } this.frames.peek().pushVariableDeclaration(variableName, variableDeclaration); } /** - * Declares a template variable. Template variables are tracked to ensure proper - * scoping. However, their declaration is not pushed on the stack as they are - * declared at the template level (in Markup) and not at the expression level - * (not in the target language script). + * Declares a template variable. Template variables are tracked to ensure proper scoping. However, + * their declaration is not pushed on the stack as they are declared at the template level (in + * Markup) and not at the expression level (not in the target language script). */ - public void declareTemplateVariable(String variableName, Class variableType) { + public void declareTemplateVariable(String variableName, + Class variableType) { if (this.inScope(variableName)) { throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + variableName); } @@ -182,34 +179,39 @@ public void declareTemplateVariable(String variableName, Class f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier) ); + return this.frames.stream().anyMatch( + f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)); } /** * Returns the stack frame containing the given identifier. */ StackFrame findFrameContaining(String identifier) { - return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)).findFirst().orElse(null); + return this.frames.stream() + .filter( + f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)) + .findFirst().orElse(null); } /** * Gets the value of a parameter. */ Optional getParameter(String identifier) { - return this.frames.stream().filter(f -> f.valueRegister.containsKey(identifier)).findFirst().map(x -> x.valueRegister.get(identifier)); + return this.frames.stream().filter(f -> f.valueRegister.containsKey(identifier)).findFirst() + .map(x -> x.valueRegister.get(identifier)); } /** * Gets the type of a variable. */ Optional> getVariable(String identifier) { - return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier)).findFirst().map(x -> x.typeRegister.get(identifier)); + return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier)).findFirst() + .map(x -> x.typeRegister.get(identifier)); } /** - * Pushes a variable reference on the current stack frame. - * Makes sure there is no name collision with other identifiers already in - * scope. + * Pushes a variable reference on the current stack frame. Makes sure there is no name collision + * with other identifiers already in scope. */ public void pushVariableReference(String variableName, Expression variableReference) { getParameter(variableName).ifPresentOrElse(parameterValue -> this.push(parameterValue), @@ -221,25 +223,23 @@ public void pushVariableReference(String variableName, Expression variableRefere } /** - * Pushes a variable reference on the current stack frame. - * This method is private because it is only used for to improve the readability - * of its public counterpart. + * Pushes a variable reference on the current stack frame. This method is private because it is + * only used for to improve the readability of its public counterpart. */ - private void pushVariableReference(Expression variableReference, Class variableType) { + private void pushVariableReference(Expression variableReference, + Class variableType) { this.frames.peek().push(Expression.instantiate(variableReference.script, variableType)); } /** - * Pushes an object on the current stack frame. - * No checks, no questions asked. + * Pushes an object on the current stack frame. No checks, no questions asked. */ public void push(CallStackObject item) { this.frames.peek().push(item); } /** - * Returns the object at the top of the current stack frame and removes it from - * the stack. + * Returns the object at the top of the current stack frame and removes it from the stack. * * @param expectedType The that the returned object is expected to have. */ @@ -248,8 +248,7 @@ public synchronized T pop(Class expectedType) { } /** - * Returns the object at the top of the current stack frame without removing it - * from the stack. + * Returns the object at the top of the current stack frame without removing it from the stack. */ public synchronized CallStackObject peek() { return this.frames.peek().peek(); diff --git a/src/main/java/eu/europa/ted/efx/model/CallStackObject.java b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java index 07a3d3e7..6223f519 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStackObject.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java @@ -1,8 +1,7 @@ package eu.europa.ted.efx.model; /** - * Base class for objects pushed in the EfxExpressionTranslator. - * call-stack. + * Base class for objects pushed in the EfxExpressionTranslator. call-stack. * * As the EfxExpressionTranslator translates EFX to a target language, the objects in the call-stack * are typically code snippets in the target language. diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java index c2fb4153..40d1224b 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java +++ b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java @@ -7,10 +7,8 @@ import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; - import org.antlr.v4.runtime.misc.ParseCancellationException; import org.apache.commons.lang3.tuple.Pair; - import eu.europa.ted.efx.interfaces.MarkupGenerator; public class ContentBlock { @@ -48,18 +46,22 @@ public static ContentBlock newRootBlock() { return new ContentBlock(); } - public ContentBlock addChild(final int number, final Markup content, final Context context, final VariableList variables) { - // number < 0 means "autogenerate", number == 0 means "no number", number > 0 means "use this number" + public ContentBlock addChild(final int number, final Markup content, final Context context, + final VariableList variables) { + // number < 0 means "autogenerate", number == 0 means "no number", number > 0 means "use this + // number" final int outlineNumber = number >= 0 ? number : children.stream().map(b -> b.number).max(Comparator.naturalOrder()).orElse(0) + 1; String newBlockId = String.format("%s%02d", this.id, this.children.size() + 1); - ContentBlock newBlock = new ContentBlock(this, newBlockId, outlineNumber, content, context, variables); + ContentBlock newBlock = + new ContentBlock(this, newBlockId, outlineNumber, content, context, variables); this.children.add(newBlock); return newBlock; } - public ContentBlock addSibling(final int number, final Markup content, final Context context, final VariableList variables) { + public ContentBlock addSibling(final int number, final Markup content, final Context context, + final VariableList variables) { if (this.parent == null) { throw new ParseCancellationException("Cannot add sibling to root block"); } @@ -156,9 +158,12 @@ public void renderTemplate(MarkupGenerator markupGenerator, List templat public Markup renderCallTemplate(MarkupGenerator markupGenerator) { Set> variables = new LinkedHashSet<>(); if (this.parent != null) { - variables.addAll(parent.getAllVariables().stream().map(v -> Pair.of(v.name, v.referenceExpression.script)).collect(Collectors.toList())); + variables.addAll(parent.getAllVariables().stream() + .map(v -> Pair.of(v.name, v.referenceExpression.script)).collect(Collectors.toList())); } - variables.addAll(this.getOwnVariables().stream().map(v -> Pair.of(v.name, v.initializationExpression.script)).collect(Collectors.toList())); - return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath(), variables); + variables.addAll(this.getOwnVariables().stream() + .map(v -> Pair.of(v.name, v.initializationExpression.script)).collect(Collectors.toList())); + return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath(), + variables); } } diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java index d1d4e142..d22da077 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java +++ b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java @@ -8,7 +8,8 @@ public class ContentBlockStack extends Stack { * Adds a new child block to the top of the stack. When the child is later removed, its parent * will return to the top of the stack again. */ - public void pushChild(final int number, final Markup content, final Context context, final VariableList variables) { + public void pushChild(final int number, final Markup content, final Context context, + final VariableList variables) { this.push(this.peek().addChild(number, content, context, variables)); } @@ -16,7 +17,8 @@ public void pushChild(final int number, final Markup content, final Context cont * Removes the block at the top of the stack and replaces it by a new sibling block. When the last * sibling is later removed, their parent block will return to the top of the stack again. */ - public void pushSibling(final int number, final Markup content, Context context, final VariableList variables) { + public void pushSibling(final int number, final Markup content, Context context, + final VariableList variables) { this.push(this.pop().addSibling(number, content, context, variables)); } 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 7383fa32..ccd372f2 100644 --- a/src/main/java/eu/europa/ted/efx/model/Context.java +++ b/src/main/java/eu/europa/ted/efx/model/Context.java @@ -29,7 +29,8 @@ public FieldContext(final String fieldId, final PathExpression absolutePath, super(fieldId, absolutePath, relativePath); } - public FieldContext(final String fieldId, final PathExpression absolutePath, final Variable variable) { + public FieldContext(final String fieldId, final PathExpression absolutePath, + final Variable variable) { super(fieldId, absolutePath, variable); } @@ -68,13 +69,14 @@ protected Context(final String symbol, final PathExpression absolutePath, protected Context(final String symbol, final PathExpression absolutePath, final PathExpression relativePath) { - this(symbol, absolutePath, relativePath, null); + this(symbol, absolutePath, relativePath, null); } - protected Context(final String symbol, final PathExpression absolutePath, final Variable variable) { + protected Context(final String symbol, final PathExpression absolutePath, + final Variable variable) { this(symbol, absolutePath, absolutePath, variable); } - + protected Context(final String symbol, final PathExpression absolutePath) { this(symbol, absolutePath, absolutePath); } diff --git a/src/main/java/eu/europa/ted/efx/model/DateVariable.java b/src/main/java/eu/europa/ted/efx/model/DateVariable.java index 27e47449..ab92916d 100644 --- a/src/main/java/eu/europa/ted/efx/model/DateVariable.java +++ b/src/main/java/eu/europa/ted/efx/model/DateVariable.java @@ -4,8 +4,9 @@ public class DateVariable extends Variable { - public DateVariable(String variableName, DateExpression initializationExpression, DateExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } + public DateVariable(String variableName, DateExpression initializationExpression, + DateExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } -} \ No newline at end of file +} diff --git a/src/main/java/eu/europa/ted/efx/model/DurationVariable.java b/src/main/java/eu/europa/ted/efx/model/DurationVariable.java index 1c5d124f..7e76637f 100644 --- a/src/main/java/eu/europa/ted/efx/model/DurationVariable.java +++ b/src/main/java/eu/europa/ted/efx/model/DurationVariable.java @@ -4,8 +4,9 @@ public class DurationVariable extends Variable { - public DurationVariable(String variableName, DurationExpression initializationExpression, DurationExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } + public DurationVariable(String variableName, DurationExpression initializationExpression, + DurationExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } -} \ No newline at end of file +} diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java index edfbce30..20782a0d 100644 --- a/src/main/java/eu/europa/ted/efx/model/Expression.java +++ b/src/main/java/eu/europa/ted/efx/model/Expression.java @@ -80,10 +80,10 @@ public static T instantiate(String script, Class type) } } - public static T empty(Class type) { + public static T empty(Class type) { return instantiate("", type); } - + @Override public boolean equals(Object obj) { if (obj == null) { @@ -249,7 +249,7 @@ public BooleanListExpression(final String script) { } /** - * Used to represent iterators (for traversing a list using a variable) + * Used to represent iterators (for traversing a list using a variable) */ public static class IteratorExpression extends Expression { diff --git a/src/main/java/eu/europa/ted/efx/model/Identifier.java b/src/main/java/eu/europa/ted/efx/model/Identifier.java index a73e03b9..413e5008 100644 --- a/src/main/java/eu/europa/ted/efx/model/Identifier.java +++ b/src/main/java/eu/europa/ted/efx/model/Identifier.java @@ -1,11 +1,11 @@ package eu.europa.ted.efx.model; public class Identifier { - public String name; - public T referenceExpression; + public String name; + public T referenceExpression; - public Identifier(String name, T referenceExpression) { - this.name = name; - this.referenceExpression = referenceExpression; - } -} \ No newline at end of file + public Identifier(String name, T referenceExpression) { + this.name = name; + this.referenceExpression = referenceExpression; + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/NumericVariable.java b/src/main/java/eu/europa/ted/efx/model/NumericVariable.java index 8bbe331d..653915ee 100644 --- a/src/main/java/eu/europa/ted/efx/model/NumericVariable.java +++ b/src/main/java/eu/europa/ted/efx/model/NumericVariable.java @@ -4,7 +4,8 @@ public class NumericVariable extends Variable { - public NumericVariable(String variableName, NumericExpression initializationExpression, NumericExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } -} \ No newline at end of file + public NumericVariable(String variableName, NumericExpression initializationExpression, + NumericExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/StringVariable.java b/src/main/java/eu/europa/ted/efx/model/StringVariable.java index 1ff3ec08..8d3704ee 100644 --- a/src/main/java/eu/europa/ted/efx/model/StringVariable.java +++ b/src/main/java/eu/europa/ted/efx/model/StringVariable.java @@ -4,7 +4,8 @@ public class StringVariable extends Variable { - public StringVariable(String variableName, StringExpression initializationExpression, StringExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } + public StringVariable(String variableName, StringExpression initializationExpression, + StringExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } } diff --git a/src/main/java/eu/europa/ted/efx/model/TimeVariable.java b/src/main/java/eu/europa/ted/efx/model/TimeVariable.java index 1c25b5d6..7a62e0b7 100644 --- a/src/main/java/eu/europa/ted/efx/model/TimeVariable.java +++ b/src/main/java/eu/europa/ted/efx/model/TimeVariable.java @@ -4,7 +4,8 @@ public class TimeVariable extends Variable { - public TimeVariable(String variableName, TimeExpression initializationExpression, TimeExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } -} \ No newline at end of file + public TimeVariable(String variableName, TimeExpression initializationExpression, + TimeExpression referenceExpression) { + super(variableName, initializationExpression, referenceExpression); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/Variable.java b/src/main/java/eu/europa/ted/efx/model/Variable.java index e5d1a093..12468548 100644 --- a/src/main/java/eu/europa/ted/efx/model/Variable.java +++ b/src/main/java/eu/europa/ted/efx/model/Variable.java @@ -1,16 +1,16 @@ package eu.europa.ted.efx.model; public class Variable extends Identifier { - public T initializationExpression; + public T initializationExpression; - public Variable(String variableName, T initializationExpression, T referenceExpression) { - super(variableName, referenceExpression); - this.name = variableName; - this.initializationExpression = initializationExpression; - } + public Variable(String variableName, T initializationExpression, T referenceExpression) { + super(variableName, referenceExpression); + this.name = variableName; + this.initializationExpression = initializationExpression; + } - public Variable(Identifier identifier, T initializationExpression) { - super(identifier.name, identifier.referenceExpression); - this.initializationExpression = initializationExpression; - } -} \ No newline at end of file + public Variable(Identifier identifier, T initializationExpression) { + super(identifier.name, identifier.referenceExpression); + this.initializationExpression = initializationExpression; + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/VariableList.java b/src/main/java/eu/europa/ted/efx/model/VariableList.java index ffc281c3..8bda31b4 100644 --- a/src/main/java/eu/europa/ted/efx/model/VariableList.java +++ b/src/main/java/eu/europa/ted/efx/model/VariableList.java @@ -5,25 +5,25 @@ public class VariableList extends CallStackObject { - LinkedList> variables; + LinkedList> variables; - public VariableList() { - this.variables = new LinkedList<>(); - } + public VariableList() { + this.variables = new LinkedList<>(); + } - public void push(Variable variable) { - this.variables.push(variable); - } + public void push(Variable variable) { + this.variables.push(variable); + } - public synchronized Variable pop() { - return this.variables.pop(); - } + public synchronized Variable pop() { + return this.variables.pop(); + } - public boolean isEmpty() { - return this.variables.isEmpty(); - } + public boolean isEmpty() { + return this.variables.isEmpty(); + } - public List> asList() { - return this.variables; - } -} \ No newline at end of file + public List> asList() { + return this.variables; + } +} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java index 65f3ea17..afc15a9e 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java @@ -95,8 +95,8 @@ * Apart from writing expressions that can be translated and evaluated in a target scripting * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The - * {@link EfxExpressionTranslator06} only focuses on EFX expressions. To translate EFX templates - * you need to use the {@link EfxTemplateTranslator06} which derives from this class. + * {@link EfxExpressionTranslator06} only focuses on EFX expressions. To translate EFX templates you + * need to use the {@link EfxTemplateTranslator06} which derives from this class. */ @SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.EFX_EXPRESSION_TRANSLATOR) public class EfxExpressionTranslator06 extends EfxBaseListener diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java index 9f43d2c0..2da591f1 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java @@ -440,7 +440,8 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { @Override public void exitTemplateLine(TemplateLineContext ctx) { - final VariableList variables = new VariableList(); // template variables not supported by EFX prior to 2.0.0 + final VariableList variables = new VariableList(); // template variables not supported by EFX + // prior to 2.0.0 final Context lineContext = this.efxContext.pop(); final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); @@ -455,7 +456,8 @@ public void exitTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); + this.blockStack.pushChild(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { // lower indent level for (int i = indentChange; i < 0; i++) { @@ -464,14 +466,17 @@ public void exitTemplateLine(TemplateLineContext ctx) { this.blockStack.pop(); } assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, + this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } } } diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java index 22afd920..9b09183f 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java @@ -14,7 +14,8 @@ @SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.CODELIST) public class SdkCodelist06 extends SdkCodelist { - public SdkCodelist06(String codelistId, String codelistVersion, List codes, Optional parentId) { - super(codelistId, codelistVersion, codes, parentId); - } + public SdkCodelist06(String codelistId, String codelistVersion, List codes, + Optional parentId) { + super(codelistId, codelistVersion, codes, parentId); + } } diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java index e393e4ab..988eef90 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java @@ -11,12 +11,12 @@ @SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.NODE) public class SdkNode06 extends SdkNode { - public SdkNode06(String id, String parentId, String xpathAbsolute, String xpathRelative, - boolean repeatable) { - super(id, parentId, xpathAbsolute, xpathRelative, repeatable); - } + public SdkNode06(String id, String parentId, String xpathAbsolute, String xpathRelative, + boolean repeatable) { + super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + } - public SdkNode06(JsonNode node) { - super(node); - } + public SdkNode06(JsonNode node) { + super(node); + } } diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java index c14d38de..9c4b12be 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java @@ -52,8 +52,8 @@ * Apart from writing expressions that can be translated and evaluated in a target scripting * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The - * {@link EfxExpressionTranslator07} only focuses on EFX expressions. To translate EFX templates - * you need to use the {@link EfxTemplateTranslator07} which derives from this class. + * {@link EfxExpressionTranslator07} only focuses on EFX expressions. To translate EFX templates you + * need to use the {@link EfxTemplateTranslator07} which derives from this class. */ @SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.EFX_EXPRESSION_TRANSLATOR) public class EfxExpressionTranslator07 extends EfxBaseListener diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java index b76fd1ac..9c7690e6 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java @@ -440,7 +440,8 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { @Override public void exitTemplateLine(TemplateLineContext ctx) { - final VariableList variables = new VariableList(); // template variables not supported prior to EFX 2 + final VariableList variables = new VariableList(); // template variables not supported prior to + // EFX 2 final Context lineContext = this.efxContext.pop(); final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); @@ -455,7 +456,8 @@ public void exitTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); + this.blockStack.pushChild(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { // lower indent level for (int i = indentChange; i < 0; i++) { @@ -464,14 +466,17 @@ public void exitTemplateLine(TemplateLineContext ctx) { this.blockStack.pop(); } assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, + this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } } } diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java index c063bfc0..bcb1f5e3 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java @@ -14,7 +14,8 @@ @SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.CODELIST) public class SdkCodelist07 extends SdkCodelist { - public SdkCodelist07(String codelistId, String codelistVersion, List codes, Optional parentId) { - super(codelistId, codelistVersion, codes, parentId); - } + public SdkCodelist07(String codelistId, String codelistVersion, List codes, + Optional parentId) { + super(codelistId, codelistVersion, codes, parentId); + } } diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java index 63639f2e..9b16f16b 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java @@ -11,12 +11,12 @@ @SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.NODE) public class SdkNode07 extends SdkNode { - public SdkNode07(String id, String parentId, String xpathAbsolute, String xpathRelative, - boolean repeatable) { - super(id, parentId, xpathAbsolute, xpathRelative, repeatable); - } + public SdkNode07(String id, String parentId, String xpathAbsolute, String xpathRelative, + boolean repeatable) { + super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + } - public SdkNode07(JsonNode node) { - super(node); - } + public SdkNode07(JsonNode node) { + super(node); + } } 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 5c5abbad..a6efb28b 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -6,7 +6,6 @@ import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; - import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -17,7 +16,6 @@ import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; import org.apache.commons.lang3.StringUtils; - import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; @@ -61,8 +59,8 @@ * Apart from writing expressions that can be translated and evaluated in a target scripting * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The - * {@link EfxExpressionTranslatorV1} only focuses on EFX expressions. To translate EFX templates - * you need to use the {@link EfxTemplateTranslatorV1} which derives from this class. + * {@link EfxExpressionTranslatorV1} only focuses on EFX expressions. To translate EFX templates you + * need to use the {@link EfxTemplateTranslatorV1} which derives from this class. */ @SdkComponent(versions = {"1"}, componentType = SdkComponentType.EFX_EXPRESSION_TRANSLATOR) public class EfxExpressionTranslatorV1 extends EfxBaseListener @@ -105,7 +103,7 @@ public class EfxExpressionTranslatorV1 extends EfxBaseListener protected ScriptGenerator script; private LinkedList expressionParameters = new LinkedList<>(); - + protected EfxExpressionTranslatorV1() {} public EfxExpressionTranslatorV1(final SymbolResolver symbolResolver, @@ -141,12 +139,15 @@ public String translateExpression(final String expression, final String... param return getTranslatedScript(); } - private T translateParameter(final String parameterValue, final Class parameterType) { - final EfxExpressionTranslatorV1 translator = new EfxExpressionTranslatorV1(this.symbols, this.script, - this.errorListener); + private T translateParameter(final String parameterValue, + final Class parameterType) { + final EfxExpressionTranslatorV1 translator = + new EfxExpressionTranslatorV1(this.symbols, this.script, + this.errorListener); final EfxLexer lexer = - new EfxLexer(CharStreams.fromString(BEGIN_EXPRESSION_BLOCK + parameterValue + END_EXPRESSION_BLOCK)); + new EfxLexer( + CharStreams.fromString(BEGIN_EXPRESSION_BLOCK + parameterValue + END_EXPRESSION_BLOCK)); final CommonTokenStream tokens = new CommonTokenStream(lexer); final EfxParser parser = new EfxParser(tokens); @@ -157,12 +158,12 @@ private T translateParameter(final String parameterValue, parser.addErrorListener(errorListener); } - final ParseTree tree = parser.parameterValue(); - final ParseTreeWalker walker = new ParseTreeWalker(); + final ParseTree tree = parser.parameterValue(); + final ParseTreeWalker walker = new ParseTreeWalker(); - walker.walk(translator, tree); + walker.walk(translator, tree); - return Expression.instantiate(translator.getTranslatedScript(), parameterType); + return Expression.instantiate(translator.getTranslatedScript(), parameterType); } /** @@ -189,18 +190,20 @@ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRul } if (ctx instanceof AbsoluteFieldReferenceContext) { - return ((AbsoluteFieldReferenceContext) ctx).reference.reference.simpleFieldReference().FieldId() + return ((AbsoluteFieldReferenceContext) ctx).reference.reference.simpleFieldReference() + .FieldId() .getText(); } if (ctx instanceof FieldReferenceWithFieldContextOverrideContext) { - return ((FieldReferenceWithFieldContextOverrideContext) ctx).reference.reference.simpleFieldReference() + return ((FieldReferenceWithFieldContextOverrideContext) ctx).reference.reference + .simpleFieldReference() .FieldId().getText(); } if (ctx instanceof FieldReferenceWithNodeContextOverrideContext) { - return ((FieldReferenceWithNodeContextOverrideContext) ctx).reference.reference - .reference.simpleFieldReference().FieldId().getText(); + return ((FieldReferenceWithNodeContextOverrideContext) ctx).reference.reference.reference + .simpleFieldReference().FieldId().getText(); } SimpleFieldReferenceContext fieldReferenceContext = @@ -685,7 +688,7 @@ public void exitDateIteratorExpression(DateIteratorExpressionContext ctx) { public void exitTimeIteratorExpression(TimeIteratorExpressionContext ctx) { this.exitIteratorExpression(TimeExpression.class, TimeListExpression.class); } - + @Override public void exitDurationIteratorExpression(DurationIteratorExpressionContext ctx) { this.exitIteratorExpression(DurationExpression.class, DurationListExpression.class); @@ -719,7 +722,7 @@ public void exitIteratorList(IteratorListContext ctx) { iterators.add(0, this.stack.pop(IteratorExpression.class)); } this.stack.push(this.script.composeIteratorList(iterators)); - } + } @Override public void exitParenthesizedStringsFromIteration(ParenthesizedStringsFromIterationContext ctx) { @@ -796,7 +799,8 @@ public > void exitIteratorExpr this.stack.push(this.script.composeIteratorExpression(variable.script, list)); } - public > void exitIterationExpression(Class expressionType, + public > void exitIterationExpression( + Class expressionType, Class targetListType) { T expression = this.stack.pop(expressionType); IteratorListExpression iterators = this.stack.pop(IteratorListExpression.class); @@ -959,9 +963,9 @@ public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNotic PathExpression field = this.stack.pop(PathExpression.class); PathExpression notice = this.stack.pop(PathExpression.class); this.stack.push(this.script.composeFieldInExternalReference(notice, field)); - - // Finally, pop the null context we pushed during enterFieldReferenceInOtherNotice - this.efxContext.pop(); + + // Finally, pop the null context we pushed during enterFieldReferenceInOtherNotice + this.efxContext.pop(); } } @@ -1149,7 +1153,8 @@ public void exitDurationParameterDeclaration(DurationParameterDeclarationContext this.exitParameterDeclaration(this.getVariableName(ctx), DurationExpression.class); } - private void exitParameterDeclaration(String parameterName, Class parameterType) { + private void exitParameterDeclaration(String parameterName, + Class parameterType) { if (this.expressionParameters.isEmpty()) { throw new ParseCancellationException("No parameter passed for " + parameterName); } @@ -1164,8 +1169,8 @@ private void exitParameterDeclaration(String parameterNam @Override public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, StringExpression.class)); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, StringExpression.class)); } @Override @@ -1478,7 +1483,7 @@ protected String getVariableName(ContextVariableDeclarationContext ctx) { return this.getVariableName(ctx.Variable().getText()); } - private String getVariableName(StringVariableDeclarationContext ctx) { + private String getVariableName(StringVariableDeclarationContext ctx) { return this.getVariableName(ctx.Variable().getText()); } 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 0be1f12e..b056341c 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -31,7 +31,23 @@ import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Markup; import eu.europa.ted.efx.model.VariableList; -import eu.europa.ted.efx.sdk1.EfxParser.*; +import eu.europa.ted.efx.sdk1.EfxParser.AssetIdContext; +import eu.europa.ted.efx.sdk1.EfxParser.AssetTypeContext; +import eu.europa.ted.efx.sdk1.EfxParser.ContextDeclarationBlockContext; +import eu.europa.ted.efx.sdk1.EfxParser.ExpressionTemplateContext; +import eu.europa.ted.efx.sdk1.EfxParser.LabelTemplateContext; +import eu.europa.ted.efx.sdk1.EfxParser.LabelTypeContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandBtLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldValueReferenceFromContextFieldContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceFromContextFieldContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandLabelReferenceFromContextContext; +import eu.europa.ted.efx.sdk1.EfxParser.StandardExpressionBlockContext; +import eu.europa.ted.efx.sdk1.EfxParser.StandardLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.TemplateFileContext; +import eu.europa.ted.efx.sdk1.EfxParser.TemplateLineContext; +import eu.europa.ted.efx.sdk1.EfxParser.TextTemplateContext; import eu.europa.ted.efx.xpath.XPathAttributeLocator; /** @@ -277,7 +293,8 @@ public void exitShorthandIndirectLabelReference(ShorthandIndirectLabelReferenceC private void shorthandIndirectLabelReference(final String fieldId) { final Context currentContext = this.efxContext.peek(); final String fieldType = this.symbols.getTypeOfField(fieldId); - final XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(symbols.getAbsolutePathOfField(fieldId)); + final XPathAttributeLocator parsedPath = + XPathAttributeLocator.findAttribute(symbols.getAbsolutePathOfField(fieldId)); final PathExpression valueReference = parsedPath.hasAttribute() ? this.script.composeFieldAttributeReference( symbols.getRelativePath(parsedPath.getPath(), currentContext.absolutePath()), @@ -285,12 +302,14 @@ private void shorthandIndirectLabelReference(final String fieldId) { : this.script.composeFieldValueReference( symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), PathExpression.class); - final StringExpression loopVariable = this.script.composeVariableReference("item", StringExpression.class); + final StringExpression loopVariable = + this.script.composeVariableReference("item", StringExpression.class); switch (fieldType) { case "indicator": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), + List.of( + this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), this.script.getStringLiteralFromUnquotedString("|"), @@ -305,7 +324,8 @@ private void shorthandIndirectLabelReference(final String fieldId) { case "internal-code": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), + List.of( + this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation(List.of( this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), this.script.getStringLiteralFromUnquotedString("|"), @@ -333,7 +353,8 @@ private void shorthandIndirectLabelReference(final String fieldId) { * context. */ @Override - public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromContextContext ctx) { + public void exitShorthandLabelReferenceFromContext( + ShorthandLabelReferenceFromContextContext ctx) { final String labelType = ctx.LabelType().getText(); if (this.efxContext.isFieldContext()) { if (labelType.equals(SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE)) { @@ -396,8 +417,8 @@ public void exitAssetId(AssetIdContext ctx) { /** * Handles a standard expression block in a template line. Most of the work is done by the base - * class EfxExpressionTranslator. After the expression is translated, the result is passed - * through the renderer. + * class EfxExpressionTranslator. After the expression is translated, the result is passed through + * the renderer. */ @Override public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { @@ -443,11 +464,14 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { // final PathExpression absolutePath = this.stack.pop(PathExpression.class); // final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); // if (filedId != null) { - // this.efxContext.push(new FieldContext(filedId, absolutePath, this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); + // this.efxContext.push(new FieldContext(filedId, absolutePath, + // this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); // } else { - // final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); - // assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; - // this.efxContext.push(new NodeContext(nodeId, absolutePath, this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); + // final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); + // assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as + // context."; + // this.efxContext.push(new NodeContext(nodeId, absolutePath, + // this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); // } } @@ -456,7 +480,8 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { @Override public void exitTemplateLine(TemplateLineContext ctx) { - final VariableList variables = new VariableList(); // template variables not supported prior to EFX 2 + final VariableList variables = new VariableList(); // template variables not supported prior to + // EFX 2 final Context lineContext = this.efxContext.pop(); final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); @@ -464,7 +489,7 @@ public void exitTemplateLine(TemplateLineContext ctx) { final Integer outlineNumber = ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; assert this.stack.empty() : "Stack should be empty at this point."; - this.stack.clear(); // Variable scope boundary. Clear declared variables + this.stack.clear(); // Variable scope boundary. Clear declared variables if (indentChange > 1) { throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); @@ -472,7 +497,8 @@ public void exitTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); + this.blockStack.pushChild(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { // lower indent level for (int i = indentChange; i < 0; i++) { @@ -481,14 +507,17 @@ public void exitTemplateLine(TemplateLineContext ctx) { this.blockStack.pop(); } assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, + this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } } } diff --git a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkCodelistV1.java b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkCodelistV1.java index ac18150e..c8ba0ec7 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkCodelistV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkCodelistV1.java @@ -14,7 +14,8 @@ @SdkComponent(versions = {"1"}, componentType = SdkComponentType.CODELIST) public class SdkCodelistV1 extends SdkCodelist { - public SdkCodelistV1(final String codelistId, final String codelistVersion, final List codes, final Optional parentId) { + public SdkCodelistV1(final String codelistId, final String codelistVersion, + final List codes, final Optional parentId) { super(codelistId, codelistVersion, codes, parentId); } } diff --git a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java index ee485946..469a9265 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java @@ -8,7 +8,8 @@ @SdkComponent(versions = {"1"}, componentType = SdkComponentType.FIELD) public class SdkFieldV1 extends SdkField { - public SdkFieldV1(final String id, final String type, final String parentNodeId, final String xpathAbsolute, + public SdkFieldV1(final String id, final String type, final String parentNodeId, + final String xpathAbsolute, final String xpathRelative, final String codelistId) { super(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId); } diff --git a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNodeV1.java b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNodeV1.java index 28041051..502ea999 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNodeV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNodeV1.java @@ -11,12 +11,12 @@ @SdkComponent(versions = {"1"}, componentType = SdkComponentType.NODE) public class SdkNodeV1 extends SdkNode { - public SdkNodeV1(String id, String parentId, String xpathAbsolute, String xpathRelative, - boolean repeatable) { - super(id, parentId, xpathAbsolute, xpathRelative, repeatable); - } + public SdkNodeV1(String id, String parentId, String xpathAbsolute, String xpathRelative, + boolean repeatable) { + super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + } - public SdkNodeV1(JsonNode node) { - super(node); - } + public SdkNodeV1(JsonNode node) { + super(node); + } } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 446aadd7..5d6ce1b8 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -16,7 +16,6 @@ import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; import org.apache.commons.lang3.StringUtils; - import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; @@ -46,6 +45,8 @@ import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Expression.TimeExpression; import eu.europa.ted.efx.model.Expression.TimeListExpression; +import eu.europa.ted.efx.sdk1.EfxExpressionTranslatorV1; +import eu.europa.ted.efx.sdk1.EfxTemplateTranslatorV1; import eu.europa.ted.efx.sdk2.EfxParser.*; import eu.europa.ted.efx.xpath.XPathAttributeLocator; @@ -60,18 +61,22 @@ * Apart from writing expressions that can be translated and evaluated in a target scripting * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The - * {@link EfxExpressionTranslatorV1} only focuses on EFX expressions. To translate EFX templates - * you need to use the {@link EfxTemplateTranslatorV1} which derives from this class. + * {@link EfxExpressionTranslatorV1} only focuses on EFX expressions. To translate EFX templates you + * need to use the {@link EfxTemplateTranslatorV1} which derives from this class. */ @SdkComponent(versions = {"2"}, componentType = SdkComponentType.EFX_EXPRESSION_TRANSLATOR) public class EfxExpressionTranslatorV2 extends EfxBaseListener implements EfxExpressionTranslator { - private static final String NOT_MODIFIER = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); + private static final String NOT_MODIFIER = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); - private static final String VARIABLE_PREFIX = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.VariablePrefix).replaceAll("^'|'$", ""); - private static final String ATTRIBUTE_PREFIX = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.AttributePrefix).replaceAll("^'|'$", ""); - private static final String CODELIST_PREFIX = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.CodelistPrefix).replaceAll("^'|'$", ""); + private static final String VARIABLE_PREFIX = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.VariablePrefix).replaceAll("^'|'$", ""); + private static final String ATTRIBUTE_PREFIX = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.AttributePrefix).replaceAll("^'|'$", ""); + private static final String CODELIST_PREFIX = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.CodelistPrefix).replaceAll("^'|'$", ""); private static final String BEGIN_EXPRESSION_BLOCK = "{"; private static final String END_EXPRESSION_BLOCK = "}"; @@ -107,7 +112,7 @@ public class EfxExpressionTranslatorV2 extends EfxBaseListener protected ScriptGenerator script; private LinkedList expressionParameters = new LinkedList<>(); - + protected EfxExpressionTranslatorV2() {} public EfxExpressionTranslatorV2(final SymbolResolver symbolResolver, @@ -143,12 +148,15 @@ public String translateExpression(final String expression, final String... param return getTranslatedScript(); } - private T translateParameter(final String parameterValue, final Class parameterType) { - final EfxExpressionTranslatorV2 translator = new EfxExpressionTranslatorV2(this.symbols, this.script, - this.errorListener); + private T translateParameter(final String parameterValue, + final Class parameterType) { + final EfxExpressionTranslatorV2 translator = + new EfxExpressionTranslatorV2(this.symbols, this.script, + this.errorListener); final EfxLexer lexer = - new EfxLexer(CharStreams.fromString(BEGIN_EXPRESSION_BLOCK + parameterValue + END_EXPRESSION_BLOCK)); + new EfxLexer( + CharStreams.fromString(BEGIN_EXPRESSION_BLOCK + parameterValue + END_EXPRESSION_BLOCK)); final CommonTokenStream tokens = new CommonTokenStream(lexer); final EfxParser parser = new EfxParser(tokens); @@ -159,12 +167,12 @@ private T translateParameter(final String parameterValue, parser.addErrorListener(errorListener); } - final ParseTree tree = parser.parameterValue(); - final ParseTreeWalker walker = new ParseTreeWalker(); + final ParseTree tree = parser.parameterValue(); + final ParseTreeWalker walker = new ParseTreeWalker(); - walker.walk(translator, tree); + walker.walk(translator, tree); - return Expression.instantiate(translator.getTranslatedScript(), parameterType); + return Expression.instantiate(translator.getTranslatedScript(), parameterType); } /** @@ -191,18 +199,20 @@ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRul } if (ctx instanceof AbsoluteFieldReferenceContext) { - return ((AbsoluteFieldReferenceContext) ctx).reference.reference.simpleFieldReference().FieldId() + return ((AbsoluteFieldReferenceContext) ctx).reference.reference.simpleFieldReference() + .FieldId() .getText(); } if (ctx instanceof FieldReferenceWithFieldContextOverrideContext) { - return ((FieldReferenceWithFieldContextOverrideContext) ctx).reference.reference.simpleFieldReference() + return ((FieldReferenceWithFieldContextOverrideContext) ctx).reference.reference + .simpleFieldReference() .FieldId().getText(); } if (ctx instanceof FieldReferenceWithNodeContextOverrideContext) { - return ((FieldReferenceWithNodeContextOverrideContext) ctx).reference.reference - .reference.simpleFieldReference().FieldId().getText(); + return ((FieldReferenceWithNodeContextOverrideContext) ctx).reference.reference.reference + .simpleFieldReference().FieldId().getText(); } SimpleFieldReferenceContext fieldReferenceContext = @@ -434,7 +444,8 @@ private > void exitInListCondi @Override public void enterQuantifiedExpression(QuantifiedExpressionContext ctx) { - this.stack.pushStackFrame(); // Quantified expressions need their own scope because they introduce new variables. + this.stack.pushStackFrame(); // Quantified expressions need their own scope because they + // introduce new variables. } @Override @@ -447,7 +458,8 @@ public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { this.stack.push(this.script.composeAnySatisfies(this.stack.pop(IteratorListExpression.class), booleanExpression)); } - this.stack.popStackFrame(); // Variables declared in the quantified expression go out of scope here. + this.stack.popStackFrame(); // Variables declared in the quantified expression go out of scope + // here. } /*** Numeric expressions ***/ @@ -693,7 +705,7 @@ public void exitDateIteratorExpression(DateIteratorExpressionContext ctx) { public void exitTimeIteratorExpression(TimeIteratorExpressionContext ctx) { this.exitIteratorExpression(TimeExpression.class, TimeListExpression.class); } - + @Override public void exitDurationIteratorExpression(DurationIteratorExpressionContext ctx) { this.exitIteratorExpression(DurationExpression.class, DurationListExpression.class); @@ -726,7 +738,7 @@ public void exitIteratorList(IteratorListContext ctx) { iterators.add(0, this.stack.pop(IteratorExpression.class)); } this.stack.push(this.script.composeIteratorList(iterators)); - } + } @Override public void exitParenthesizedStringsFromIteration(ParenthesizedStringsFromIterationContext ctx) { @@ -839,7 +851,8 @@ public > void exitIteratorExpr this.stack.push(this.script.composeIteratorExpression(variable.script, list)); } - public > void exitIterationExpression(Class expressionType, + public > void exitIterationExpression( + Class expressionType, Class targetListType) { T expression = this.stack.pop(expressionType); IteratorListExpression iterators = this.stack.pop(IteratorListExpression.class); @@ -1002,9 +1015,9 @@ public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNotic PathExpression field = this.stack.pop(PathExpression.class); PathExpression notice = this.stack.pop(PathExpression.class); this.stack.push(this.script.composeFieldInExternalReference(notice, field)); - - // Finally, pop the null context we pushed during enterFieldReferenceInOtherNotice - this.efxContext.pop(); + + // Finally, pop the null context we pushed during enterFieldReferenceInOtherNotice + this.efxContext.pop(); } } @@ -1192,7 +1205,8 @@ public void exitDurationAtSequenceIndex(DurationAtSequenceIndexContext ctx) { this.exitSequenceAtIndex(DurationExpression.class, DurationListExpression.class); } - private > void exitSequenceAtIndex(Class itemType, Class listType) { + private > void exitSequenceAtIndex( + Class itemType, Class listType) { NumericExpression index = this.stack.pop(NumericExpression.class); L list = this.stack.pop(listType); this.stack.push(this.script.composeIndexer(list, index, itemType)); @@ -1231,7 +1245,8 @@ public void exitDurationParameterDeclaration(DurationParameterDeclarationContext this.exitParameterDeclaration(this.getVariableName(ctx), DurationExpression.class); } - private void exitParameterDeclaration(String parameterName, Class parameterType) { + private void exitParameterDeclaration(String parameterName, + Class parameterType) { if (this.expressionParameters.isEmpty()) { throw new ParseCancellationException("No parameter passed for " + parameterName); } @@ -1246,8 +1261,8 @@ private void exitParameterDeclaration(String parameterNam @Override public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, StringExpression.class)); + this.stack.pushVariableDeclaration(variableName, + this.script.composeVariableDeclaration(variableName, StringExpression.class)); } @Override @@ -1583,7 +1598,7 @@ protected String getVariableName(ContextVariableDeclarationContext ctx) { return this.getVariableName(ctx.Variable().getText()); } - private String getVariableName(StringVariableDeclarationContext ctx) { + private String getVariableName(StringVariableDeclarationContext ctx) { return this.getVariableName(ctx.Variable().getText()); } 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 e8db600b..fb86daab 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -6,7 +6,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; - import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; @@ -16,7 +15,6 @@ import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxTemplateTranslator; @@ -47,7 +45,32 @@ import eu.europa.ted.efx.model.Variable; import eu.europa.ted.efx.model.VariableList; import eu.europa.ted.efx.sdk1.EfxExpressionTranslatorV1; -import eu.europa.ted.efx.sdk2.EfxParser.*; +import eu.europa.ted.efx.sdk2.EfxParser.AssetIdContext; +import eu.europa.ted.efx.sdk2.EfxParser.AssetTypeContext; +import eu.europa.ted.efx.sdk2.EfxParser.BooleanVariableInitializerContext; +import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationBlockContext; +import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.ContextVariableInitializerContext; +import eu.europa.ted.efx.sdk2.EfxParser.DateVariableInitializerContext; +import eu.europa.ted.efx.sdk2.EfxParser.DurationVariableInitializerContext; +import eu.europa.ted.efx.sdk2.EfxParser.ExpressionTemplateContext; +import eu.europa.ted.efx.sdk2.EfxParser.LabelTemplateContext; +import eu.europa.ted.efx.sdk2.EfxParser.LabelTypeContext; +import eu.europa.ted.efx.sdk2.EfxParser.NumericVariableInitializerContext; +import eu.europa.ted.efx.sdk2.EfxParser.ShorthandBtLabelReferenceContext; +import eu.europa.ted.efx.sdk2.EfxParser.ShorthandFieldLabelReferenceContext; +import eu.europa.ted.efx.sdk2.EfxParser.ShorthandFieldValueReferenceFromContextFieldContext; +import eu.europa.ted.efx.sdk2.EfxParser.ShorthandIndirectLabelReferenceContext; +import eu.europa.ted.efx.sdk2.EfxParser.ShorthandIndirectLabelReferenceFromContextFieldContext; +import eu.europa.ted.efx.sdk2.EfxParser.ShorthandLabelReferenceFromContextContext; +import eu.europa.ted.efx.sdk2.EfxParser.StandardExpressionBlockContext; +import eu.europa.ted.efx.sdk2.EfxParser.StandardLabelReferenceContext; +import eu.europa.ted.efx.sdk2.EfxParser.StringVariableInitializerContext; +import eu.europa.ted.efx.sdk2.EfxParser.TemplateFileContext; +import eu.europa.ted.efx.sdk2.EfxParser.TemplateLineContext; +import eu.europa.ted.efx.sdk2.EfxParser.TemplateVariableListContext; +import eu.europa.ted.efx.sdk2.EfxParser.TextTemplateContext; +import eu.europa.ted.efx.sdk2.EfxParser.TimeVariableInitializerContext; import eu.europa.ted.efx.xpath.XPathAttributeLocator; import eu.europa.ted.efx.xpath.XPathContextualizer; @@ -294,7 +317,8 @@ public void exitShorthandIndirectLabelReference(ShorthandIndirectLabelReferenceC private void shorthandIndirectLabelReference(final String fieldId) { final Context currentContext = this.efxContext.peek(); final String fieldType = this.symbols.getTypeOfField(fieldId); - final XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(symbols.getAbsolutePathOfField(fieldId)); + final XPathAttributeLocator parsedPath = + XPathAttributeLocator.findAttribute(symbols.getAbsolutePathOfField(fieldId)); final PathExpression valueReference = parsedPath.hasAttribute() ? this.script.composeFieldAttributeReference( symbols.getRelativePath(parsedPath.getPath(), currentContext.absolutePath()), @@ -302,13 +326,15 @@ private void shorthandIndirectLabelReference(final String fieldId) { : this.script.composeFieldValueReference( symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), PathExpression.class); - final StringExpression loopVariable = this.script.composeVariableReference("item", StringExpression.class); + final StringExpression loopVariable = + this.script.composeVariableReference("item", StringExpression.class); switch (fieldType) { case "indicator": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), + List.of( + this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), this.script.getStringLiteralFromUnquotedString("|"), @@ -323,7 +349,8 @@ private void shorthandIndirectLabelReference(final String fieldId) { case "internal-code": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( - List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))), + List.of( + this.script.composeIteratorExpression(loopVariable.script, valueReference))), this.script.composeStringConcatenation(List.of( this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), this.script.getStringLiteralFromUnquotedString("|"), @@ -351,7 +378,8 @@ private void shorthandIndirectLabelReference(final String fieldId) { * context. */ @Override - public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromContextContext ctx) { + public void exitShorthandLabelReferenceFromContext( + ShorthandLabelReferenceFromContextContext ctx) { final String labelType = ctx.LabelType().getText(); if (this.efxContext.isFieldContext()) { if (labelType.equals(SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE)) { @@ -414,8 +442,8 @@ public void exitAssetId(AssetIdContext ctx) { /** * Handles a standard expression block in a template line. Most of the work is done by the base - * class EfxExpressionTranslator. After the expression is translated, the result is passed - * through the renderer. + * class EfxExpressionTranslator. After the expression is translated, the result is passed through + * the renderer. */ @Override public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { @@ -471,12 +499,14 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) { } } - private Variable getContextVariable(ContextDeclarationContext ctx, PathExpression contextPath) { + private Variable getContextVariable(ContextDeclarationContext ctx, + PathExpression contextPath) { if (ctx.contextVariableInitializer() == null) { return null; } final String variableName = this.getVariableName(ctx.contextVariableInitializer()); - return new Variable<>(variableName, XPathContextualizer.contextualize(contextPath, contextPath), this.script.composeVariableReference(variableName, PathExpression.class)); + return new Variable<>(variableName, XPathContextualizer.contextualize(contextPath, contextPath), + this.script.composeVariableReference(variableName, PathExpression.class)); } @Override @@ -486,40 +516,49 @@ public void enterTemplateVariableList(TemplateVariableListContext ctx) { @Override public void exitStringVariableInitializer(StringVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), StringVariable.class, StringExpression.class); + this.exitVariableInitializer(this.getVariableName(ctx), StringVariable.class, + StringExpression.class); } @Override public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), BooleanVariable.class, BooleanExpression.class); + this.exitVariableInitializer(this.getVariableName(ctx), BooleanVariable.class, + BooleanExpression.class); } @Override public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), NumericVariable.class, NumericExpression.class); + this.exitVariableInitializer(this.getVariableName(ctx), NumericVariable.class, + NumericExpression.class); } @Override public void exitDateVariableInitializer(DateVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), DateVariable.class, DateExpression.class); + this.exitVariableInitializer(this.getVariableName(ctx), DateVariable.class, + DateExpression.class); } @Override public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), TimeVariable.class, TimeExpression.class); + this.exitVariableInitializer(this.getVariableName(ctx), TimeVariable.class, + TimeExpression.class); } @Override public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), DurationVariable.class, DurationExpression.class); + this.exitVariableInitializer(this.getVariableName(ctx), DurationVariable.class, + DurationExpression.class); } - private > void exitVariableInitializer(String variableName, Class variableType, Class expressionType) { + private > void exitVariableInitializer( + String variableName, Class variableType, Class expressionType) { T expression = this.stack.pop(expressionType); VariableList variables = this.stack.pop(VariableList.class); try { - Constructor constructor = variableType.getConstructor(String.class, expressionType, expressionType); - variables.push(constructor.newInstance(variableName, expression, this.script.composeVariableReference(variableName, expressionType))); + Constructor constructor = + variableType.getConstructor(String.class, expressionType, expressionType); + variables.push(constructor.newInstance(variableName, expression, + this.script.composeVariableReference(variableName, expressionType))); this.stack.push(variables); this.stack.declareTemplateVariable(variableName, expressionType); } catch (Exception e) { @@ -547,13 +586,14 @@ public void enterTemplateLine(TemplateLineContext ctx) { this.blockStack.pop(); this.stack.popStackFrame(); // Each skipped indentation level must go out of scope. } - this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation level). + this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation + // level). this.stack.pushStackFrame(); // Create a stack frame for the new template line. assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - } - else if (indentChange == 0) { - this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation level). - this.stack.pushStackFrame(); // Create a stack frame for the new template line. + } else if (indentChange == 0) { + this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation + // level). + this.stack.pushStackFrame(); // Create a stack frame for the new template line. } } @@ -574,16 +614,21 @@ public void exitTemplateLine(TemplateLineContext ctx) { if (this.blockStack.isEmpty()) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } - this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), templateVariables); + this.blockStack.pushChild(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.currentContext()), templateVariables); } else if (indentChange < 0) { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), templateVariables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), templateVariables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), templateVariables)); + this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, + this.relativizeContext(lineContext, this.rootBlock.getContext()), templateVariables)); } else { - this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), templateVariables); + this.blockStack.pushSibling(outlineNumber, content, + this.relativizeContext(lineContext, this.blockStack.parentContext()), + templateVariables); } } } @@ -595,7 +640,8 @@ private Context relativizeContext(Context childContext, Context parentContext) { if (FieldContext.class.isAssignableFrom(childContext.getClass())) { return new FieldContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath()), childContext.variable()); + this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath()), + childContext.variable()); } assert NodeContext.class.isAssignableFrom( diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java index 2a260704..1794738d 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java @@ -2,9 +2,9 @@ import java.util.List; import java.util.Optional; -import eu.europa.ted.efx.sdk1.entity.SdkCodelistV1; import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.efx.sdk1.entity.SdkCodelistV1; /** * Representation of an SdkCodelist for usage in the symbols map. @@ -14,7 +14,8 @@ @SdkComponent(versions = {"2"}, componentType = SdkComponentType.CODELIST) public class SdkCodelistV2 extends SdkCodelistV1 { - public SdkCodelistV2(final String codelistId, final String codelistVersion, final List codes, final Optional parentId) { - super(codelistId, codelistVersion, codes, parentId); - } + public SdkCodelistV2(final String codelistId, final String codelistVersion, + final List codes, final Optional parentId) { + super(codelistId, codelistVersion, codes, parentId); + } } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java index 0187efae..c9860847 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java @@ -1,9 +1,9 @@ package eu.europa.ted.efx.sdk2.entity; import com.fasterxml.jackson.databind.JsonNode; -import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; @SdkComponent(versions = {"2"}, componentType = SdkComponentType.FIELD) public class SdkFieldV2 extends SdkFieldV1 { diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java index 9bac4149..9865a639 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java @@ -1,29 +1,29 @@ package eu.europa.ted.efx.sdk2.entity; import com.fasterxml.jackson.databind.JsonNode; -import eu.europa.ted.efx.sdk1.entity.SdkNodeV1; import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.efx.sdk1.entity.SdkNodeV1; /** * A node is something like a section. Nodes can be parents of other nodes or parents of fields. */ @SdkComponent(versions = {"2"}, componentType = SdkComponentType.NODE) public class SdkNodeV2 extends SdkNodeV1 { - private final String alias; + private final String alias; - public SdkNodeV2(String id, String parentId, String xpathAbsolute, String xpathRelative, - boolean repeatable, String alias) { - super(id, parentId, xpathAbsolute, xpathRelative, repeatable); - this.alias = alias; - } + public SdkNodeV2(String id, String parentId, String xpathAbsolute, String xpathRelative, + boolean repeatable, String alias) { + super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + this.alias = alias; + } - public SdkNodeV2(JsonNode node) { - super(node); - this.alias = node.has("alias") ? node.get("alias").asText(null) : null; - } + public SdkNodeV2(JsonNode node) { + super(node); + this.alias = node.has("alias") ? node.get("alias").asText(null) : null; + } - public String getAlias() { - return alias; - } + public String getAlias() { + return alias; + } } diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java index f1413ec2..0cd6c8f6 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -83,7 +83,8 @@ public static PathExpression addAxis(String axis, PathExpression path) { steps.removeFirst(); } - return new PathExpression(axis + "::" + steps.stream().map(s -> s.stepText).collect(Collectors.joining("/"))); + return new PathExpression( + axis + "::" + steps.stream().map(s -> s.stepText).collect(Collectors.joining("/"))); } private static PathExpression getContextualizedXpath(Queue contextQueue, @@ -119,8 +120,8 @@ private static PathExpression getContextualizedXpath(Queue contextQueu // For each step remaining in the contextQueue we prepend a back-step (..) in // the resulting relativeXpath. while (!contextQueue.isEmpty()) { - contextQueue.poll(); // consume the step - relativeXpath = "../" + relativeXpath; // prepend a back-step + contextQueue.poll(); // consume the step + relativeXpath = "../" + relativeXpath; // prepend a back-step } // We remove any trailing forward slashes from the resulting xPath. diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 53184819..ea11a30c 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -1,7 +1,6 @@ package eu.europa.ted.efx.xpath; import static java.util.Map.entry; - import java.lang.reflect.Constructor; import java.util.List; import java.util.Map; @@ -9,9 +8,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; - import org.antlr.v4.runtime.misc.ParseCancellationException; - import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.ScriptGenerator; @@ -29,7 +26,8 @@ import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Expression.TimeExpression; -@SdkComponent(versions = {"0.6", "0.7", "1", "2"}, componentType = SdkComponentType.SCRIPT_GENERATOR) +@SdkComponent(versions = {"0.6", "0.7", "1", "2"}, + componentType = SdkComponentType.SCRIPT_GENERATOR) public class XPathScriptGenerator implements ScriptGenerator { /** @@ -63,7 +61,7 @@ public T composeFieldReferenceWithPredicate(PathExpressio @Override public T composeFieldReferenceWithAxis(final PathExpression fieldReference, final String axis, Class type) { - return Expression.instantiate(XPathContextualizer.addAxis(axis, fieldReference).script, type); + return Expression.instantiate(XPathContextualizer.addAxis(axis, fieldReference).script, type); } @Override @@ -83,17 +81,18 @@ public T composeFieldValueReference(PathExpression fieldR return Expression.instantiate(fieldReference.script + "/xs:time(text())", type); } if (DurationExpression.class.isAssignableFrom(type)) { - return Expression.instantiate("(for $F in " + fieldReference.script + " return (if ($F/@unitCode='WEEK')" + // - " then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D'))" + // - " else if ($F/@unitCode='DAY')" + // - " then xs:dayTimeDuration(concat('P', $F/number(), 'D'))" + // - " else if ($F/@unitCode='YEAR')" + // - " then xs:yearMonthDuration(concat('P', $F/number(), 'Y'))" + // - " else if ($F/@unitCode='MONTH')" + // - " then xs:yearMonthDuration(concat('P', $F/number(), 'M'))" + // - // " else if (" + fieldReference.script + ")" + // - // " then fn:error('Invalid @unitCode')" + // - " else ()))", type); + return Expression + .instantiate("(for $F in " + fieldReference.script + " return (if ($F/@unitCode='WEEK')" + // + " then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D'))" + // + " else if ($F/@unitCode='DAY')" + // + " then xs:dayTimeDuration(concat('P', $F/number(), 'D'))" + // + " else if ($F/@unitCode='YEAR')" + // + " then xs:yearMonthDuration(concat('P', $F/number(), 'Y'))" + // + " else if ($F/@unitCode='MONTH')" + // + " then xs:yearMonthDuration(concat('P', $F/number(), 'M'))" + // + // " else if (" + fieldReference.script + ")" + // + // " then fn:error('Invalid @unitCode')" + // + " else ()))", type); } return Expression.instantiate(fieldReference.script, type); @@ -102,7 +101,9 @@ public T composeFieldValueReference(PathExpression fieldR @Override public T composeFieldAttributeReference(PathExpression fieldReference, String attribute, Class type) { - return Expression.instantiate(fieldReference.script + (fieldReference.script.isEmpty() ? "" : "/") + "@" + attribute, type); + return Expression.instantiate( + fieldReference.script + (fieldReference.script.isEmpty() ? "" : "/") + "@" + attribute, + type); } @Override @@ -117,8 +118,8 @@ public T composeVariableDeclaration(String variableName, @Override public T composeParameterDeclaration(String parameterName, - Class type) { - return Expression.empty(type); + Class type) { + return Expression.empty(type); } @Override @@ -189,9 +190,9 @@ public BooleanExpression composePatternMatchCondition(StringExpression expressio @Override public BooleanExpression composeAllSatisfy(ListExpression list, String variableName, BooleanExpression booleanExpression) { - return new BooleanExpression( - "every " + variableName + " in " + list.script + " satisfies " + booleanExpression.script); - } + return new BooleanExpression( + "every " + variableName + " in " + list.script + " satisfies " + booleanExpression.script); + } @Override public BooleanExpression composeAllSatisfy( @@ -203,9 +204,9 @@ public BooleanExpression composeAllSatisfy( @Override public BooleanExpression composeAnySatisfies(ListExpression list, String variableName, BooleanExpression booleanExpression) { - return new BooleanExpression( - "some " + variableName + " in " + list.script + " satisfies " + booleanExpression.script); - } + return new BooleanExpression( + "some " + variableName + " in " + list.script + " satisfies " + booleanExpression.script); + } @Override public BooleanExpression composeAnySatisfies( @@ -254,7 +255,7 @@ public IteratorListExpression composeIteratorList(List itera return new IteratorListExpression( iterators.stream().map(i -> i.script).collect(Collectors.joining(", ", "", ""))); } - + @Override public T composeParenthesizedExpression(T expression, Class type) { try { @@ -287,7 +288,8 @@ public PathExpression joinPaths(final PathExpression first, final PathExpression /** Indexers ***/ @Override - public > T composeIndexer(L list, NumericExpression index, + public > T composeIndexer(L list, + NumericExpression index, Class type) { return Expression.instantiate(String.format("%s[%s]", list.script, index.script), type); } @@ -320,8 +322,10 @@ public BooleanExpression composeExistsCondition(PathExpression reference) { } @Override - public BooleanExpression composeUniqueValueCondition(PathExpression needle, PathExpression haystack) { - return new BooleanExpression("count(for $x in " + needle.script + ", $y in " + haystack.script + "[. = $x] return $y) = 1"); + public BooleanExpression composeUniqueValueCondition(PathExpression needle, + PathExpression haystack) { + return new BooleanExpression("count(for $x in " + needle.script + ", $y in " + haystack.script + + "[. = $x] return $y) = 1"); } /*** Boolean functions ***/ @@ -360,7 +364,7 @@ public BooleanExpression composeComparisonOperation(Expression leftOperand, Stri @Override public BooleanExpression composeSequenceEqualFunction(ListExpression one, - ListExpression two) { + ListExpression two) { return new BooleanExpression("deep-equal(sort(" + one.script + "), sort(" + two.script + "))"); } @@ -511,25 +515,28 @@ public DurationExpression composeSubtraction(DurationExpression left, DurationEx @Override public > L composeDistinctValuesFunction( L list, Class listType) { - return Expression.instantiate("distinct-values(" + list.script + ")", listType); + return Expression.instantiate("distinct-values(" + list.script + ")", listType); } @Override public > L composeUnionFunction(L listOne, L listTwo, Class listType) { - return Expression.instantiate("distinct-values((" + listOne.script + ", " + listTwo.script + "))", listType); + return Expression + .instantiate("distinct-values((" + listOne.script + ", " + listTwo.script + "))", listType); } @Override public > L composeIntersectFunction(L listOne, L listTwo, Class listType) { - return Expression.instantiate("distinct-values(" + listOne.script + "[.= " + listTwo.script + "])", listType); + return Expression.instantiate( + "distinct-values(" + listOne.script + "[.= " + listTwo.script + "])", listType); } @Override public > L composeExceptFunction(L listOne, L listTwo, Class listType) { - return Expression.instantiate("distinct-values(" + listOne.script + "[not(. = " + listTwo.script + ")])", listType); + return Expression.instantiate( + "distinct-values(" + listOne.script + "[not(. = " + listTwo.script + ")])", listType); } diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java index 85b873b8..94ebb350 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java @@ -15,7 +15,7 @@ private String test(final String context, final String expression) { private String test1(final String expression, final String... params) { try { - return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, SDK_VERSION, + return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, SDK_VERSION, expression, params); } catch (InstantiationException e) { throw new RuntimeException(e); @@ -69,12 +69,16 @@ void testPresenceCondition_WithNot() { @Test void testUniqueValueCondition() { - assertEquals("count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", test("ND-Root", "BT-00-Text is unique in /BT-00-Text")); + assertEquals( + "count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", + test("ND-Root", "BT-00-Text is unique in /BT-00-Text")); } @Test void testUniqueValueCondition_WithNot() { - assertEquals("not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", test("ND-Root", "BT-00-Text is not unique in /BT-00-Text")); + assertEquals( + "not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", + test("ND-Root", "BT-00-Text is not unique in /BT-00-Text")); } @@ -345,8 +349,10 @@ void testDateQuantifiedExpression_UsingFieldReference() { @Test void testDateQuantifiedExpression_UsingMultipleIterators() { - assertEquals("every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", - test("ND-Root", "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z")); + assertEquals( + "every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", + test("ND-Root", + "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z")); } @Test @@ -457,7 +463,7 @@ void testConditionalDurationExpression() { } // #endregion: Conditional expressions - + // #region: Iteration expressions ------------------------------------------- // Strings from iteration --------------------------------------------------- @@ -470,14 +476,18 @@ void testStringsFromStringIteration_UsingLiterals() { @Test void testStringsSequenceFromIteration_UsingMultipleIterators() { - assertEquals("'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", - test("ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))")); + assertEquals( + "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", + test("ND-Root", + "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))")); } - + @Test void testStringsSequenceFromIteration_UsingObjectVariable() { - assertEquals("for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", - test("ND-Root", "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'")); + assertEquals( + "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", + test("ND-Root", + "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'")); } @Test @@ -522,7 +532,8 @@ void testStringsFromNumericIteration_UsingFieldReference() { void testStringsFromDateIteration_UsingLiterals() { assertEquals( "'a' = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 'y')", - test("ND-Root", "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')")); + test("ND-Root", + "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')")); } @Test @@ -602,7 +613,8 @@ void testNumbersFromNumericIteration_UsingFieldReference() { void testNumbersFromDateIteration_UsingLiterals() { assertEquals( "123 = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 0)", - test("ND-Root", "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)")); + test("ND-Root", + "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)")); } @Test @@ -862,7 +874,8 @@ void testDurationsFromNumericIteration_UsingFieldReference() { void testDurationsFromDateIteration_UsingLiterals() { assertEquals( "xs:dayTimeDuration('P1D') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)")); + test("ND-Root", + "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)")); } @Test @@ -900,8 +913,8 @@ void testDurationsFromDurationIteration_UsingFieldReference() { test("ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)")); } - // #endregion: Iteration expressions - + // #endregion: Iteration expressions + // #region: Numeric expressions --------------------------------------------- @Test @@ -925,7 +938,7 @@ void testNumericLiteralExpression() { } // #endregion: Numeric expressions - + // #region: Lists ----------------------------------------------------------- @Test @@ -988,11 +1001,12 @@ void testDurationList_UsingDurationField() { @Test void testCodeList() { - assertEquals("'a' = ('code1','code2','code3')", test("BT-00-Text", "'a' in codelist:accessibility")); + assertEquals("'a' = ('code1','code2','code3')", + test("BT-00-Text", "'a' in codelist:accessibility")); } // #endregion: Lists - + // #region: References ------------------------------------------------------ @Test @@ -1011,7 +1025,7 @@ void testFieldAttributeValueReference_SameElementContext() { void testScalarFromAttributeReference() { assertEquals("PathNode/CodeField/@listName", test("ND-Root", "BT-00-Code/@listName")); } - + @Test void testScalarFromAttributeReference_SameElementContext() { assertEquals("./@listName", test("BT-00-Code", "BT-00-Code/@listName")); @@ -1151,7 +1165,7 @@ void testStringLengthFunction() { } // #endregion: Numeric functions - + // #region: String functions ------------------------------------------------ @Test @@ -1174,12 +1188,14 @@ void testConcatFunction() { @Test void testStringJoinFunction_withLiterals() { - assertEquals("string-join(('abc','def'), ',')", test("ND-Root", "string-join(('abc', 'def'), ',')")); + assertEquals("string-join(('abc','def'), ',')", + test("ND-Root", "string-join(('abc', 'def'), ',')")); } @Test void testStringJoinFunction_withFieldReference() { - assertEquals("string-join(PathNode/TextField, ',')", test("ND-Root", "string-join(BT-00-Text, ',')")); + assertEquals("string-join(PathNode/TextField, ',')", + test("ND-Root", "string-join(BT-00-Text, ',')")); } @Test @@ -1226,13 +1242,15 @@ void testDistinctValuesFunction_WithNumberSequences() { @Test void testDistinctValuesFunction_WithDateSequences() { - assertEquals("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", + assertEquals( + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", test("ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))")); } @Test void testDistinctValuesFunction_WithTimeSequences() { - assertEquals("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", + assertEquals( + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", test("ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))")); } @@ -1241,7 +1259,7 @@ void testDistinctValuesFunction_WithBooleanSequences() { assertEquals("distinct-values((true(),false(),false(),false()))", test("ND-Root", "distinct-values((TRUE, FALSE, FALSE, NEVER))")); } - + @Test void testDistinctValuesFunction_WithFieldReferences() { assertEquals("distinct-values(PathNode/TextField)", @@ -1264,13 +1282,15 @@ void testUnionFunction_WithNumberSequences() { @Test void testUnionFunction_WithDateSequences() { - assertEquals("distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", + assertEquals( + "distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", test("ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); } @Test void testUnionFunction_WithTimeSequences() { - assertEquals("distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", + assertEquals( + "distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", test("ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); } @@ -1279,7 +1299,7 @@ void testUnionFunction_WithBooleanSequences() { assertEquals("distinct-values(((true(),false()), (false(),false())))", test("ND-Root", "value-union((TRUE, FALSE), (FALSE, NEVER))")); } - + @Test void testUnionFunction_WithFieldReferences() { assertEquals("distinct-values((PathNode/TextField, PathNode/TextField))", @@ -1310,13 +1330,15 @@ void testIntersectFunction_WithNumberSequences() { @Test void testIntersectFunction_WithDateSequences() { - assertEquals("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", + assertEquals( + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", test("ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); } @Test void testIntersectFunction_WithTimeSequences() { - assertEquals("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", + assertEquals( + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", test("ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); } @@ -1325,7 +1347,7 @@ void testIntersectFunction_WithBooleanSequences() { assertEquals("distinct-values((true(),false())[.= (false(),false())])", test("ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))")); } - + @Test void testIntersectFunction_WithFieldReferences() { assertEquals("distinct-values(PathNode/TextField[.= PathNode/TextField])", @@ -1356,13 +1378,15 @@ void testExceptFunction_WithNumberSequences() { @Test void testExceptFunction_WithDateSequences() { - assertEquals("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", + assertEquals( + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", test("ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); } @Test void testExceptFunction_WithTimeSequences() { - assertEquals("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", + assertEquals( + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", test("ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); } @@ -1371,7 +1395,7 @@ void testExceptFunction_WithBooleanSequences() { assertEquals("distinct-values((true(),false())[not(. = (false(),false()))])", test("ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))")); } - + @Test void testExceptFunction_WithFieldReferences() { assertEquals("distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", @@ -1402,13 +1426,15 @@ void testSequenceEqualFunction_WithNumberSequences() { @Test void testSequenceEqualFunction_WithDateSequences() { - assertEquals("deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", + assertEquals( + "deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", test("ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); } @Test void testSequenceEqualFunction_WithTimeSequences() { - assertEquals("deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", + assertEquals( + "deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", test("ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); } @@ -1420,7 +1446,8 @@ void testSequenceEqualFunction_WithBooleanSequences() { @Test void testSequenceEqualFunction_WithDurationSequences() { - assertEquals("deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", + assertEquals( + "deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", test("ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))")); } @@ -1468,12 +1495,13 @@ void testParametrizedExpression_WithBooleanParameter() { @Test void testParametrizedExpression_WithDurationParameter() { - assertEquals("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", + assertEquals( + "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", test1("{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y")); } // #endregion: Compare sequences - + // #endregion Sequence Functions // #region: Indexers -------------------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java index 3eb43fc0..c57b32d2 100644 --- a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java @@ -26,7 +26,8 @@ private String lines(String... lines) { @Test void testTemplateLineNoIdent() { - assertEquals("let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())", + assertEquals( + "let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())", translate("{BT-00-Text} foo")); } @@ -35,58 +36,56 @@ void testTemplateLineNoIdent() { */ @Test void testTemplateLineOutline_Autogenerated() { - assertEquals(lines("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') }", - "for-each(/*/PathNode/TextField).call(block01())"), // + assertEquals(lines("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') }", + "for-each(/*/PathNode/TextField).call(block01())"), // translate(lines("{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } /** - * The autogenerated number for a node can be overridden. - * Leaf nodes don't get an outline number. + * The autogenerated number for a node can be overridden. Leaf nodes don't get an outline number. */ @Test void testTemplateLineOutline_Explicit() { - 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("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())"), // translate(lines("2{BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } /** - * The autogenerated number for some nodes can be overridden. - * The other nodes get an auto generated outline number. - * Leaf nodes don't get an outline number. + * The autogenerated number for some nodes can be overridden. The other nodes get an auto + * generated outline number. Leaf nodes don't get an outline number. */ @Test void testTemplateLineOutline_Mixed() { - 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("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())"), // translate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } /** - * The outline number can be suppressed for a line if overridden with the value zero. - * Leaf nodes don't get an outline number. + * The outline number can be suppressed for a line if overridden with the value zero. Leaf nodes + * don't get an outline number. */ @Test void testTemplateLineOutline_Suppressed() { - 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("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())"), // translate(lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -97,12 +96,12 @@ void testTemplateLineOutline_Suppressed() { @Test void testTemplateLineOutline_SuppressedAtParent() { // 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("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())"), // translate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @@ -115,8 +114,8 @@ void testTemplateLineFirstIndented() { void testTemplateLineIdentTab() { assertEquals( lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // - "let block0101() -> { text('bar') }", // - "for-each(/*/PathNode/TextField).call(block01())"),// + "let block0101() -> { text('bar') }", // + "for-each(/*/PathNode/TextField).call(block01())"), // translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); } @@ -124,8 +123,8 @@ void testTemplateLineIdentTab() { void testTemplateLineIdentSpaces() { assertEquals( lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // - "let block0101() -> { text('bar') }", // - "for-each(/*/PathNode/TextField).call(block01())"),// + "let block0101() -> { text('bar') }", // + "for-each(/*/PathNode/TextField).call(block01())"), // translate(lines("{BT-00-Text} foo", " {BT-00-Text} bar"))); } @@ -144,11 +143,11 @@ void testTemplateLineIdentMixedSpaceThenTab() { @Test void testTemplateLineIdentLower() { 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("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())"), translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar", "{BT-00-Code} code"))); } @@ -161,22 +160,24 @@ void testTemplateLineIdentUnexpected() { @Test void testTemplateLineJoining() { 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())"), - translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar \\ \n joined \\\n\\\nmore", "{BT-00-Code} code"))); + 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())"), + translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar \\ \n joined \\\n\\\nmore", + "{BT-00-Code} code"))); } @Test void testTemplateLine_VariableScope() { assertEquals( lines("let block01() -> { #1: eval(for $x in . return $x)", // - "for-each(.).call(block0101()) }", // - "let block0101() -> { eval(for $x in . return $x) }", // - "for-each(/*/PathNode/TextField).call(block01())"),// - translate(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}"))); + "for-each(.).call(block0101()) }", // + "let block0101() -> { eval(for $x in . return $x) }", // + "for-each(/*/PathNode/TextField).call(block01())"), // + translate(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}"))); } @@ -287,7 +288,8 @@ void testShorthandIndirectLabelReferenceForText() { @Test void testShorthandIndirectLabelReferenceForAttribute() { - assertThrows(ParseCancellationException.class, () -> translate("{BT-00-Text} #{BT-00-Attribute}")); + assertThrows(ParseCancellationException.class, + () -> translate("{BT-00-Text} #{BT-00-Attribute}")); } @Test diff --git a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java index 30d9f1cc..78197d5f 100644 --- a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java @@ -6,7 +6,6 @@ import eu.europa.ted.efx.xpath.XPathContextualizer; class XPathContextualizerTest { - private String contextualize(final String context, final String xpath) { return XPathContextualizer.contextualize(new PathExpression(context), new PathExpression(xpath)).script; 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 9b56446a..d1e5c681 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -4,10 +4,8 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; - import eu.europa.ted.efx.interfaces.MarkupGenerator; import eu.europa.ted.efx.model.Expression; import eu.europa.ted.efx.model.Expression.PathExpression; @@ -42,7 +40,8 @@ public Markup composeFragmentDefinition(String name, String number, Markup conte } @Override - public Markup composeFragmentDefinition(String name, String number, Markup content, Set parameters) { + public Markup composeFragmentDefinition(String name, String number, Markup content, + Set parameters) { if (StringUtils.isBlank(number)) { return new Markup(String.format("let %s(%s) -> { %s }", name, parameters.stream().collect(Collectors.joining(", ")), content.script)); @@ -59,8 +58,10 @@ public Markup renderFragmentInvocation(String name, PathExpression context) { @Override public Markup renderFragmentInvocation(String name, PathExpression context, Set> variables) { - return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name, variables.stream() - .map(v -> String.format("%s:%s", v.getLeft(), v.getRight())).collect(Collectors.joining(", ")))); + return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name, + variables.stream() + .map(v -> String.format("%s:%s", v.getLeft(), v.getRight())) + .collect(Collectors.joining(", ")))); } @Override diff --git a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java index f97acc38..157cdeae 100644 --- a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java @@ -6,8 +6,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.stream.Collectors; import java.util.Optional; +import java.util.stream.Collectors; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +20,9 @@ import eu.europa.ted.eforms.sdk.entity.SdkNode; import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.sdk2.entity.*; +import eu.europa.ted.efx.sdk2.entity.SdkCodelistV2; +import eu.europa.ted.efx.sdk2.entity.SdkFieldV2; +import eu.europa.ted.efx.sdk2.entity.SdkNodeV2; import eu.europa.ted.efx.xpath.XPathContextualizer; public class SymbolResolverMock implements SymbolResolver { @@ -66,8 +68,8 @@ public void loadMapData() throws JsonMappingException, JsonProcessingException { entry("BT-00-Internal-Code", new SdkFieldV2(fromString( "{\"id\":\"BT-00-Internal-Code\",\"alias\":\"internalCode\",\"type\":\"internal-code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/InternalCodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), entry("BT-00-CodeAttribute", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-CodeAttribute\",\"alias\":\"codeAttribute\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField/@attribute\",\"xpathRelative\":\"PathNode/CodeField/@attribute\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-Text-Multilingual", new SdkFieldV2(fromString( + "{\"id\":\"BT-00-CodeAttribute\",\"alias\":\"codeAttribute\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField/@attribute\",\"xpathRelative\":\"PathNode/CodeField/@attribute\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), + entry("BT-00-Text-Multilingual", new SdkFieldV2(fromString( "{\"id\":\"BT-00-Text-Multilingual\",\"alias\":\"textMultilingual\",\"type\":\"text-multilingual\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextMultilingualField\",\"xpathRelative\":\"PathNode/TextMultilingualField\"}}"))), entry("BT-00-StartDate", new SdkFieldV2(fromString( "{\"id\":\"BT-00-StartDate\",\"alias\":\"startDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartDateField\",\"xpathRelative\":\"PathNode/StartDateField\"}}"))), @@ -102,19 +104,21 @@ public void loadMapData() throws JsonMappingException, JsonProcessingException { entry("BT-01-SubNode-Text", new SdkFieldV2(fromString( "{\"id\":\"BT-01-SubNode-Text\",\"alias\":\"subNode_text\",\"type\":\"text\",\"parentNodeId\":\"ND-SubNode\",\"xpathAbsolute\":\"/*/SubNode/SubTextField\",\"xpathRelative\":\"SubTextField\"}}")))); - this.fieldByAlias = this.fieldById.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); + this.fieldByAlias = this.fieldById.entrySet().stream() + .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); this.nodeById = Map.ofEntries(// - entry("ND-Root", new SdkNodeV2("ND-Root", null, "/*", "/*", false, "Root")), entry("ND-SubNode", + entry("ND-Root", new SdkNodeV2("ND-Root", null, "/*", "/*", false, "Root")), + entry("ND-SubNode", new SdkNodeV2("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false, "SubNode"))); - this.nodeByAlias = this.nodeById.entrySet().stream().collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); + this.nodeByAlias = this.nodeById.entrySet().stream() + .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); this.codelistById = new HashMap<>(Map.ofEntries( buildCodelistMock("accessibility", Optional.empty()), buildCodelistMock("authority-activity", Optional.of("main-activity")), - buildCodelistMock("main-activity", Optional.empty()) - )); + buildCodelistMock("main-activity", Optional.empty()))); } private static Entry buildCodelistMock(final String codelistId, @@ -124,7 +128,8 @@ private static Entry buildCodelistMock(final String codel } public SdkField getFieldById(String fieldId) { - return this.fieldById.containsKey(fieldId) ? this.fieldById.get(fieldId) : this.fieldByAlias.get(fieldId); + return this.fieldById.containsKey(fieldId) ? this.fieldById.get(fieldId) + : this.fieldByAlias.get(fieldId); } @Override @@ -214,7 +219,8 @@ public PathExpression getAbsolutePathOfField(final String fieldId) { */ @Override public PathExpression getAbsolutePathOfNode(final String nodeId) { - final SdkNode sdkNode = nodeById.containsKey(nodeId) ? nodeById.get(nodeId) : nodeByAlias.get(nodeId); + final SdkNode sdkNode = + nodeById.containsKey(nodeId) ? nodeById.get(nodeId) : nodeByAlias.get(nodeId); if (sdkNode == null) { throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); } From 3dd91296c6090a488eeab8efa2db458ae6711377 Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Mon, 17 Apr 2023 18:12:25 +0200 Subject: [PATCH 11/57] [XPathAttributeLocatorTest]: Deleted public access modifier from class and test methods. --- .../eu/europa/ted/efx/XPathAttributeLocatorTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java index 0fc00f1a..909bd7ba 100644 --- a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java @@ -6,9 +6,9 @@ import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.xpath.XPathAttributeLocator; -public class XPathAttributeLocatorTest { +class XPathAttributeLocatorTest { @Test - public void testXPathAttributeLocator_WithAttribute() { + void testXPathAttributeLocator_WithAttribute() { final XPathAttributeLocator locator = XPathAttributeLocator.findAttribute(new PathExpression("/path/path/@attribute")); assertEquals("/path/path", locator.getPath().script); @@ -16,7 +16,7 @@ public void testXPathAttributeLocator_WithAttribute() { } @Test - public void testXPathAttributeLocator_WithMultipleAttributes() { + void testXPathAttributeLocator_WithMultipleAttributes() { final XPathAttributeLocator locator = XPathAttributeLocator .findAttribute(new PathExpression("/path/path[@otherAttribute = 'text']/@attribute")); assertEquals("/path/path[@otherAttribute = 'text']", locator.getPath().script); @@ -24,7 +24,7 @@ public void testXPathAttributeLocator_WithMultipleAttributes() { } @Test - public void testXPathAttributeLocator_WithoutAttribute() { + void testXPathAttributeLocator_WithoutAttribute() { final XPathAttributeLocator locator = XPathAttributeLocator .findAttribute(new PathExpression("/path/path[@otherAttribute = 'text']")); assertEquals("/path/path[@otherAttribute = 'text']", locator.getPath().script); @@ -32,7 +32,7 @@ public void testXPathAttributeLocator_WithoutAttribute() { } @Test - public void testXPathAttributeLocator_WithoutPath() { + void testXPathAttributeLocator_WithoutPath() { final XPathAttributeLocator locator = XPathAttributeLocator.findAttribute(new PathExpression("@attribute")); assertEquals("", locator.getPath().script); From dc3342956872f4be74fbe6d42b8929f3732b363e Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Mon, 17 Apr 2023 18:14:35 +0200 Subject: [PATCH 12/57] [XPathAttributeLocatorTest]: Moved common code into a new method shared by all test methods. --- .../ted/efx/XPathAttributeLocatorTest.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java index 909bd7ba..a4a32736 100644 --- a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java @@ -7,20 +7,24 @@ import eu.europa.ted.efx.xpath.XPathAttributeLocator; class XPathAttributeLocatorTest { + private void testAttribute(final String attributePath, final String expectedPath, + final String expectedAttribute) { + final XPathAttributeLocator locator = + XPathAttributeLocator.findAttribute(new PathExpression(attributePath)); + + assertEquals(expectedPath, locator.getPath().script); + assertEquals(expectedAttribute, locator.getAttribute()); + } + @Test void testXPathAttributeLocator_WithAttribute() { - final XPathAttributeLocator locator = - XPathAttributeLocator.findAttribute(new PathExpression("/path/path/@attribute")); - assertEquals("/path/path", locator.getPath().script); - assertEquals("attribute", locator.getAttribute()); + testAttribute("/path/path/@attribute", "/path/path", "attribute"); } @Test void testXPathAttributeLocator_WithMultipleAttributes() { - final XPathAttributeLocator locator = XPathAttributeLocator - .findAttribute(new PathExpression("/path/path[@otherAttribute = 'text']/@attribute")); - assertEquals("/path/path[@otherAttribute = 'text']", locator.getPath().script); - assertEquals("attribute", locator.getAttribute()); + testAttribute("/path/path[@otherAttribute = 'text']/@attribute", + "/path/path[@otherAttribute = 'text']", "attribute"); } @Test @@ -33,9 +37,6 @@ void testXPathAttributeLocator_WithoutAttribute() { @Test void testXPathAttributeLocator_WithoutPath() { - final XPathAttributeLocator locator = - XPathAttributeLocator.findAttribute(new PathExpression("@attribute")); - assertEquals("", locator.getPath().script); - assertEquals("attribute", locator.getAttribute()); + testAttribute("@attribute", "", "attribute"); } } From 17f59fa50aee79722dc2d65efaac7052fee1657d Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Tue, 18 Apr 2023 16:46:22 +0200 Subject: [PATCH 13/57] [EfxExpressionCombinedTest]: Execute tests for both SDK 1 and SDK 2. --- pom.xml | 14 +++- .../ted/efx/EfxExpressionCombinedTest.java | 77 +++++++++++++------ 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/pom.xml b/pom.xml index 8ecdacf1..e359fe1f 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 1.2.11 2.13.4 2.13.4.2 - 5.7.2 + 5.7.2 1.7.36 @@ -115,7 +115,12 @@ org.junit.jupiter junit-jupiter-api - ${version.junit-jupiter-api} + ${version.junit-jupiter} + + + org.junit.jupiter + junit-jupiter-params + ${version.junit-jupiter} org.antlr @@ -137,6 +142,11 @@ junit-jupiter-api test + + org.junit.jupiter + junit-jupiter-params + test + org.antlr antlr4-runtime diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java index fe1f9546..10841bbf 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java @@ -1,49 +1,80 @@ package eu.europa.ted.efx; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import eu.europa.ted.efx.mock.DependencyFactoryMock; /** * Test for EFX expressions that combine several aspects of the language. */ class EfxExpressionCombinedTest { - final private String SDK_VERSION = "eforms-sdk-2.0"; + private static final String[] SDK_VERSIONS = new String[] {"eforms-sdk-1.0", "eforms-sdk-2.0"}; - private String test(final String context, final String expression) { + private static Stream provideSdkVersions() { + List arguments = new ArrayList<>(); + + for (String sdkVersion : SDK_VERSIONS) { + arguments.add(Arguments.of(sdkVersion)); + } + + return Stream.of(arguments.toArray(new Arguments[0])); + } + + private void testExpressionTranslationWithContext(final String sdkVersion, + final String expectedTranslation, final String context, final String expression) { + assertEquals(expectedTranslation, + translateExpressionWithContext(sdkVersion, context, expression)); + } + + private String translateExpressionWithContext(String sdkVersion, final String context, + final String expression) { try { - return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, - SDK_VERSION, String.format("{%s} ${%s}", context, expression)); + return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, sdkVersion, + String.format("{%s} ${%s}", context, expression)); } catch (InstantiationException e) { throw new RuntimeException(e); } } - @Test - void testNotAnd() { - assertEquals("not(1 = 2) and (2 = 2)", test("BT-00-Text", "not(1 == 2) and (2 == 2)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNotAnd(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "not(1 = 2) and (2 = 2)", "BT-00-Text", + "not(1 == 2) and (2 == 2)"); } - @Test - void testNotPresentAndNotPresent() { - assertEquals("not(PathNode/TextField) and not(PathNode/IntegerField)", - test("ND-Root", "BT-00-Text is not present and BT-00-Integer is not present")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNotPresentAndNotPresent(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "not(PathNode/TextField) and not(PathNode/IntegerField)", "ND-Root", + "BT-00-Text is not present and BT-00-Integer is not present"); } - @Test - void testCountWithNodeContextOverride() { - assertEquals("count(../../PathNode/CodeField) = 1", - test("BT-00-Text", "count(ND-Root::BT-00-Code) == 1")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testCountWithNodeContextOverride(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "count(../../PathNode/CodeField) = 1", + "BT-00-Text", "count(ND-Root::BT-00-Code) == 1"); } - @Test - void testCountWithAbsoluteFieldReference() { - assertEquals("count(/*/PathNode/CodeField) = 1", test("BT-00-Text", "count(/BT-00-Code) == 1")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testCountWithAbsoluteFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "count(/*/PathNode/CodeField) = 1", + "BT-00-Text", "count(/BT-00-Code) == 1"); } - @Test - void testCountWithAbsoluteFieldReferenceAndPredicate() { - assertEquals("count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1", - test("BT-00-Text", "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testCountWithAbsoluteFieldReferenceAndPredicate(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1", "BT-00-Text", + "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); } } From 60148df6839926efa2c67d9cd61f5d67dfc1e046 Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Tue, 18 Apr 2023 16:56:54 +0200 Subject: [PATCH 14/57] [EfxExpressionTranslatorTest]: Execute tests for both SDK 1 and SDK 2. --- .../ted/efx/EfxExpressionTranslatorTest.java | 2128 ++++++++++------- 1 file changed, 1235 insertions(+), 893 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java index 94ebb350..3b9c8d3a 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java @@ -2,20 +2,48 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import eu.europa.ted.efx.mock.DependencyFactoryMock; class EfxExpressionTranslatorTest { - final private String SDK_VERSION = "eforms-sdk-2.0"; + private static final String[] SDK_VERSIONS = new String[] {"eforms-sdk-1.0", "eforms-sdk-2.0"}; - private String test(final String context, final String expression) { - return test1(String.format("{%s} ${%s}", context, expression)); + protected static Stream provideSdkVersions() { + List arguments = new ArrayList<>(); + + for (String sdkVersion : SDK_VERSIONS) { + arguments.add(Arguments.of(sdkVersion)); + } + + return Stream.of(arguments.toArray(new Arguments[0])); + } + + private void testExpressionTranslationWithContext(final String sdkVersion, + final String expectedTranslation, final String context, final String expression) { + assertEquals(expectedTranslation, + translateExpressionWithContext(sdkVersion, context, expression)); + } + + private void testExpressionTranslation(final String sdkVersion, final String expectedTranslation, + final String expression, final String... params) { + assertEquals(expectedTranslation, translateExpression(sdkVersion, expression, params)); } - private String test1(final String expression, final String... params) { + private String translateExpressionWithContext(final String sdkVersion, final String context, + final String expression) { + return translateExpression(sdkVersion, String.format("{%s} ${%s}", context, expression)); + } + + private String translateExpression(final String sdkVersion, final String expression, + final String... params) { try { - return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, SDK_VERSION, + return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, sdkVersion, expression, params); } catch (InstantiationException e) { throw new RuntimeException(e); @@ -24,442 +52,544 @@ private String test1(final String expression, final String... params) { // #region: Boolean expressions --------------------------------------------- - @Test - void testParenthesizedBooleanExpression() { - assertEquals("(true() or true()) and false()", - test("BT-00-Text", "(ALWAYS or TRUE) and NEVER")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParenthesizedBooleanExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(true() or true()) and false()", "BT-00-Text", + "(ALWAYS or TRUE) and NEVER"); } - @Test - void testLogicalOrCondition() { - assertEquals("true() or false()", test("BT-00-Text", "ALWAYS or NEVER")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testLogicalOrCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "true() or false()", "BT-00-Text", + "ALWAYS or NEVER"); } - @Test - void testLogicalAndCondition() { - assertEquals("true() and 1 + 1 = 2", test("BT-00-Text", "ALWAYS and 1 + 1 == 2")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testLogicalAndCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "true() and 1 + 1 = 2", "BT-00-Text", + "ALWAYS and 1 + 1 == 2"); } - @Test - void testInListCondition() { - assertEquals("not('x' = ('a','b','c'))", test("BT-00-Text", "'x' not in ('a', 'b', 'c')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testInListCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "not('x' = ('a','b','c'))", "BT-00-Text", + "'x' not in ('a', 'b', 'c')"); } - @Test - void testEmptinessCondition() { - assertEquals("PathNode/TextField/normalize-space(text()) = ''", - test("ND-Root", "BT-00-Text is empty")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testEmptinessCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/TextField/normalize-space(text()) = ''", "ND-Root", "BT-00-Text is empty"); } - @Test - void testEmptinessCondition_WithNot() { - assertEquals("PathNode/TextField/normalize-space(text()) != ''", - test("ND-Root", "BT-00-Text is not empty")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testEmptinessCondition_WithNot(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/TextField/normalize-space(text()) != ''", "ND-Root", "BT-00-Text is not empty"); } - @Test - void testPresenceCondition() { - assertEquals("PathNode/TextField", test("ND-Root", "BT-00-Text is present")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testPresenceCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/TextField", "ND-Root", + "BT-00-Text is present"); } - @Test - void testPresenceCondition_WithNot() { - assertEquals("not(PathNode/TextField)", test("ND-Root", "BT-00-Text is not present")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testPresenceCondition_WithNot(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "not(PathNode/TextField)", "ND-Root", + "BT-00-Text is not present"); } - @Test - void testUniqueValueCondition() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUniqueValueCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", - test("ND-Root", "BT-00-Text is unique in /BT-00-Text")); + "ND-Root", "BT-00-Text is unique in /BT-00-Text"); } - @Test - void testUniqueValueCondition_WithNot() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUniqueValueCondition_WithNot(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", - test("ND-Root", "BT-00-Text is not unique in /BT-00-Text")); + "ND-Root", "BT-00-Text is not unique in /BT-00-Text"); } - @Test - void testLikePatternCondition() { - assertEquals("fn:matches(normalize-space('123'), '[0-9]*')", - test("BT-00-Text", "'123' like '[0-9]*'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testLikePatternCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "fn:matches(normalize-space('123'), '[0-9]*')", + "BT-00-Text", "'123' like '[0-9]*'"); } - @Test - void testLikePatternCondition_WithNot() { - assertEquals("not(fn:matches(normalize-space('123'), '[0-9]*'))", - test("BT-00-Text", "'123' not like '[0-9]*'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testLikePatternCondition_WithNot(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "not(fn:matches(normalize-space('123'), '[0-9]*'))", "BT-00-Text", + "'123' not like '[0-9]*'"); } - @Test - void testFieldValueComparison_UsingTextFields() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_UsingTextFields(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", - test("Root", "text == textMultilingual")); + "Root", "text == textMultilingual"); } - @Test - void testFieldValueComparison_UsingNumericFields() { - assertEquals("PathNode/NumberField/number() <= PathNode/IntegerField/number()", - test("ND-Root", "BT-00-Number <= integer")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_UsingNumericFields(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/NumberField/number() <= PathNode/IntegerField/number()", "ND-Root", + "BT-00-Number <= integer"); } - @Test - void testFieldValueComparison_UsingIndicatorFields() { - assertEquals("PathNode/IndicatorField != PathNode/IndicatorField", - test("ND-Root", "BT-00-Indicator != BT-00-Indicator")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_UsingIndicatorFields(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/IndicatorField != PathNode/IndicatorField", "ND-Root", + "BT-00-Indicator != BT-00-Indicator"); } - @Test - void testFieldValueComparison_UsingDateFields() { - assertEquals("PathNode/StartDateField/xs:date(text()) <= PathNode/EndDateField/xs:date(text())", - test("ND-Root", "BT-00-StartDate <= BT-00-EndDate")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_UsingDateFields(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/StartDateField/xs:date(text()) <= PathNode/EndDateField/xs:date(text())", + "ND-Root", "BT-00-StartDate <= BT-00-EndDate"); } - @Test - void testFieldValueComparison_UsingTimeFields() { - assertEquals("PathNode/StartTimeField/xs:time(text()) <= PathNode/EndTimeField/xs:time(text())", - test("ND-Root", "BT-00-StartTime <= BT-00-EndTime")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_UsingTimeFields(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/StartTimeField/xs:time(text()) <= PathNode/EndTimeField/xs:time(text())", + "ND-Root", "BT-00-StartTime <= BT-00-EndTime"); } - @Test - void testFieldValueComparison_UsingMeasureFields() { + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_UsingMeasureFields(final String sdkVersion) { assertEquals( "boolean(for $T in (current-date()) return ($T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) <= $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", - test("ND-Root", "BT-00-Measure <= BT-00-Measure")); + translateExpressionWithContext(sdkVersion, "ND-Root", "BT-00-Measure <= BT-00-Measure")); } - @Test - void testFieldValueComparison_WithStringLiteral() { - assertEquals("PathNode/TextField/normalize-space(text()) = 'abc'", - test("ND-Root", "BT-00-Text == 'abc'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_WithStringLiteral(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/TextField/normalize-space(text()) = 'abc'", "ND-Root", "BT-00-Text == 'abc'"); } - @Test - void testFieldValueComparison_WithNumericLiteral() { - assertEquals("PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", - test("ND-Root", "integer - BT-00-Number > 0")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_WithNumericLiteral(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", "ND-Root", + "integer - BT-00-Number > 0"); } - @Test - void testFieldValueComparison_WithDateLiteral() { - assertEquals("xs:date('2022-01-01Z') > PathNode/StartDateField/xs:date(text())", - test("ND-Root", "2022-01-01Z > BT-00-StartDate")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_WithDateLiteral(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:date('2022-01-01Z') > PathNode/StartDateField/xs:date(text())", "ND-Root", + "2022-01-01Z > BT-00-StartDate"); } - @Test - void testFieldValueComparison_WithTimeLiteral() { - assertEquals("xs:time('00:01:00Z') > PathNode/EndTimeField/xs:time(text())", - test("ND-Root", "00:01:00Z > BT-00-EndTime")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_WithTimeLiteral(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:time('00:01:00Z') > PathNode/EndTimeField/xs:time(text())", "ND-Root", + "00:01:00Z > BT-00-EndTime"); } - @Test - void testFieldValueComparison_TypeMismatch() { + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldValueComparison_TypeMismatch(final String sdkVersion) { assertThrows(ParseCancellationException.class, - () -> test("ND-Root", "00:01:00 > BT-00-StartDate")); + () -> translateExpressionWithContext(sdkVersion, "ND-Root", "00:01:00 > BT-00-StartDate")); } - @Test - void testBooleanComparison_UsingLiterals() { - assertEquals("false() != true()", test("BT-00-Text", "NEVER != ALWAYS")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testBooleanComparison_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "false() != true()", "BT-00-Text", + "NEVER != ALWAYS"); } - @Test - void testBooleanComparison_UsingFieldReference() { - assertEquals("../IndicatorField != true()", test("BT-00-Text", "BT-00-Indicator != ALWAYS")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testBooleanComparison_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "../IndicatorField != true()", "BT-00-Text", + "BT-00-Indicator != ALWAYS"); } - @Test - void testNumericComparison() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumericComparison(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", - test("BT-00-Text", - "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer")); + "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer"); } - @Test - void testStringComparison() { - assertEquals("'aaa' < 'bbb'", test("BT-00-Text", "'aaa' < 'bbb'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringComparison(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'aaa' < 'bbb'", "BT-00-Text", + "'aaa' < 'bbb'"); } - @Test - void testDateComparison_OfTwoDateLiterals() { - assertEquals("xs:date('2018-01-01Z') > xs:date('2018-01-01Z')", - test("BT-00-Text", "2018-01-01Z > 2018-01-01Z")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateComparison_OfTwoDateLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:date('2018-01-01Z') > xs:date('2018-01-01Z')", "BT-00-Text", + "2018-01-01Z > 2018-01-01Z"); } - @Test - void testDateComparison_OfTwoDateReferences() { - assertEquals("PathNode/StartDateField/xs:date(text()) = PathNode/EndDateField/xs:date(text())", - test("ND-Root", "BT-00-StartDate == BT-00-EndDate")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateComparison_OfTwoDateReferences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/StartDateField/xs:date(text()) = PathNode/EndDateField/xs:date(text())", + "ND-Root", "BT-00-StartDate == BT-00-EndDate"); } - @Test - void testDateComparison_OfDateReferenceAndDateFunction() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateComparison_OfDateReferenceAndDateFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/StartDateField/xs:date(text()) = xs:date(PathNode/TextField/normalize-space(text()))", - test("ND-Root", "BT-00-StartDate == date(BT-00-Text)")); + "ND-Root", "BT-00-StartDate == date(BT-00-Text)"); } - @Test - void testTimeComparison_OfTwoTimeLiterals() { - assertEquals("xs:time('13:00:10Z') > xs:time('21:20:30Z')", - test("BT-00-Text", "13:00:10Z > 21:20:30Z")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimeComparison_OfTwoTimeLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('13:00:10Z') > xs:time('21:20:30Z')", + "BT-00-Text", "13:00:10Z > 21:20:30Z"); } - @Test - void testZonedTimeComparison_OfTwoTimeLiterals() { - assertEquals("xs:time('13:00:10+01:00') > xs:time('21:20:30+02:00')", - test("BT-00-Text", "13:00:10+01:00 > 21:20:30+02:00")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testZonedTimeComparison_OfTwoTimeLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:time('13:00:10+01:00') > xs:time('21:20:30+02:00')", "BT-00-Text", + "13:00:10+01:00 > 21:20:30+02:00"); } - @Test - void testTimeComparison_OfTwoTimeReferences() { - assertEquals("PathNode/StartTimeField/xs:time(text()) = PathNode/EndTimeField/xs:time(text())", - test("ND-Root", "BT-00-StartTime == BT-00-EndTime")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimeComparison_OfTwoTimeReferences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/StartTimeField/xs:time(text()) = PathNode/EndTimeField/xs:time(text())", + "ND-Root", "BT-00-StartTime == BT-00-EndTime"); } - @Test - void testTimeComparison_OfTimeReferenceAndTimeFunction() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimeComparison_OfTimeReferenceAndTimeFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/StartTimeField/xs:time(text()) = xs:time(PathNode/TextField/normalize-space(text()))", - test("ND-Root", "BT-00-StartTime == time(BT-00-Text)")); + "ND-Root", "BT-00-StartTime == time(BT-00-Text)"); } - @Test - void testDurationComparison_UsingYearMOnthDurationLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationComparison_UsingYearMOnthDurationLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P12M')))", - test("BT-00-Text", "P1Y == P12M")); + "BT-00-Text", "P1Y == P12M"); } - @Test - void testDurationComparison_UsingDayTimeDurationLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationComparison_UsingDayTimeDurationLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P21D') > $T + xs:dayTimeDuration('P7D')))", - test("BT-00-Text", "P3W > P7D")); + "BT-00-Text", "P3W > P7D"); } - @Test - void testCalculatedDurationComparison() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testCalculatedDurationComparison(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P3M') > $T + xs:dayTimeDuration(PathNode/EndDateField/xs:date(text()) - PathNode/StartDateField/xs:date(text()))))", - test("ND-Root", "P3M > (BT-00-EndDate - BT-00-StartDate)")); + "ND-Root", "P3M > (BT-00-EndDate - BT-00-StartDate)"); } - @Test - void testNegativeDuration_Literal() { - assertEquals("xs:yearMonthDuration('-P3M')", test("ND-Root", "-P3M")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNegativeDuration_Literal(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:yearMonthDuration('-P3M')", "ND-Root", + "-P3M"); } - @Test - void testNegativeDuration_ViaMultiplication() { - assertEquals("(-3 * (2 * xs:yearMonthDuration('-P3M')))", test("ND-Root", "2 * -P3M * -3")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNegativeDuration_ViaMultiplication(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(-3 * (2 * xs:yearMonthDuration('-P3M')))", + "ND-Root", "2 * -P3M * -3"); } - @Test - void testNegativeDuration_ViaMultiplicationWithField() { + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNegativeDuration_ViaMultiplicationWithField(final String sdkVersion) { assertEquals( "(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", - test("ND-Root", "2 * measure:BT-00-Measure * -3")); + translateExpressionWithContext(sdkVersion, "ND-Root", "2 * measure:BT-00-Measure * -3")); } - @Test - void testDurationAddition() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationAddition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(xs:dayTimeDuration('P3D') + xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", - test("ND-Root", "P3D + (BT-00-StartDate - BT-00-EndDate)")); + "ND-Root", "P3D + (BT-00-StartDate - BT-00-EndDate)"); } - @Test - void testDurationSubtraction() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationSubtraction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(xs:dayTimeDuration('P3D') - xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", - test("ND-Root", "P3D - (BT-00-StartDate - BT-00-EndDate)")); + "ND-Root", "P3D - (BT-00-StartDate - BT-00-EndDate)"); } - @Test - void testBooleanLiteralExpression_Always() { - assertEquals("true()", test("BT-00-Text", "ALWAYS")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testBooleanLiteralExpression_Always(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "true()", "BT-00-Text", "ALWAYS"); } - @Test - void testBooleanLiteralExpression_Never() { - assertEquals("false()", test("BT-00-Text", "NEVER")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testBooleanLiteralExpression_Never(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "false()", "BT-00-Text", "NEVER"); } // #endregion: Boolean expressions // #region: Quantified expressions ------------------------------------------ - @Test - void testStringQuantifiedExpression_UsingLiterals() { - assertEquals("every $x in ('a','b','c') satisfies $x <= 'a'", - test("ND-Root", "every text:$x in ('a', 'b', 'c') satisfies $x <= 'a'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringQuantifiedExpression_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "every $x in ('a','b','c') satisfies $x <= 'a'", "ND-Root", + "every text:$x in ('a', 'b', 'c') satisfies $x <= 'a'"); } - @Test - void testStringQuantifiedExpression_UsingFieldReference() { - assertEquals("every $x in PathNode/TextField satisfies $x <= 'a'", - test("ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringQuantifiedExpression_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "every $x in PathNode/TextField satisfies $x <= 'a'", "ND-Root", + "every text:$x in BT-00-Text satisfies $x <= 'a'"); } - @Test - void testBooleanQuantifiedExpression_UsingLiterals() { - assertEquals("every $x in (true(),false(),true()) satisfies $x", - test("ND-Root", "every indicator:$x in (TRUE, FALSE, ALWAYS) satisfies $x")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testBooleanQuantifiedExpression_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "every $x in (true(),false(),true()) satisfies $x", "ND-Root", + "every indicator:$x in (TRUE, FALSE, ALWAYS) satisfies $x"); } - @Test - void testBooleanQuantifiedExpression_UsingFieldReference() { - assertEquals("every $x in PathNode/IndicatorField satisfies $x", - test("ND-Root", "every indicator:$x in BT-00-Indicator satisfies $x")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testBooleanQuantifiedExpression_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "every $x in PathNode/IndicatorField satisfies $x", "ND-Root", + "every indicator:$x in BT-00-Indicator satisfies $x"); } - @Test - void testNumericQuantifiedExpression_UsingLiterals() { - assertEquals("every $x in (1,2,3) satisfies $x <= 1", - test("ND-Root", "every number:$x in (1, 2, 3) satisfies $x <= 1")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumericQuantifiedExpression_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "every $x in (1,2,3) satisfies $x <= 1", + "ND-Root", "every number:$x in (1, 2, 3) satisfies $x <= 1"); } - @Test - void testNumericQuantifiedExpression_UsingFieldReference() { - assertEquals("every $x in PathNode/NumberField satisfies $x <= 1", - test("ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumericQuantifiedExpression_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "every $x in PathNode/NumberField satisfies $x <= 1", "ND-Root", + "every number:$x in BT-00-Number satisfies $x <= 1"); } - @Test - void testDateQuantifiedExpression_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateQuantifiedExpression_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "every $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) satisfies $x <= xs:date('2012-01-01Z')", - test("ND-Root", - "every date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) satisfies $x <= 2012-01-01Z")); + "ND-Root", + "every date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) satisfies $x <= 2012-01-01Z"); } - @Test - void testDateQuantifiedExpression_UsingFieldReference() { - assertEquals("every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", - test("ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateQuantifiedExpression_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", + "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); } - @Test - void testDateQuantifiedExpression_UsingMultipleIterators() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateQuantifiedExpression_UsingMultipleIterators(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", - test("ND-Root", - "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z")); + "ND-Root", + "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); } - @Test - void testTimeQuantifiedExpression_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimeQuantifiedExpression_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "every $x in (xs:time('00:00:00Z'),xs:time('00:00:01Z'),xs:time('00:00:02Z')) satisfies $x <= xs:time('00:00:00Z')", - test("ND-Root", - "every time:$x in (00:00:00Z, 00:00:01Z, 00:00:02Z) satisfies $x <= 00:00:00Z")); + "ND-Root", "every time:$x in (00:00:00Z, 00:00:01Z, 00:00:02Z) satisfies $x <= 00:00:00Z"); } - @Test - void testTimeQuantifiedExpression_UsingFieldReference() { - assertEquals("every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", - test("ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimeQuantifiedExpression_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", + "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); } - @Test - void testDurationQuantifiedExpression_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationQuantifiedExpression_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "every $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P3D')) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", - test("ND-Root", "every measure:$x in (P1D, P2D, P3D) satisfies $x <= P1D")); + "ND-Root", "every measure:$x in (P1D, P2D, P3D) satisfies $x <= P1D"); } - @Test - void testDurationQuantifiedExpression_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationQuantifiedExpression_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", - test("ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D")); + "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); } // #endregion: Quantified expressions // #region: Conditional expressions ----------------------------------------- - @Test - void testConditionalExpression() { - assertEquals("(if 1 > 2 then 'a' else 'b')", test("ND-Root", "if 1 > 2 then 'a' else 'b'")); - } - - @Test - void testConditionalStringExpression_UsingLiterals() { - assertEquals("(if 'a' > 'b' then 'a' else 'b')", - test("ND-Root", "if 'a' > 'b' then 'a' else 'b'")); - } - - @Test - void testConditionalStringExpression_UsingFieldReferenceInCondition() { - assertEquals("(if 'a' > PathNode/TextField/normalize-space(text()) then 'a' else 'b')", - test("ND-Root", "if 'a' > BT-00-Text then 'a' else 'b'")); - assertEquals("(if PathNode/TextField/normalize-space(text()) >= 'a' then 'a' else 'b')", - test("ND-Root", "if BT-00-Text >= 'a' then 'a' else 'b'")); - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(if 1 > 2 then 'a' else 'b')", "ND-Root", + "if 1 > 2 then 'a' else 'b'"); + } + + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalStringExpression_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(if 'a' > 'b' then 'a' else 'b')", "ND-Root", + "if 'a' > 'b' then 'a' else 'b'"); + } + + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalStringExpression_UsingFieldReferenceInCondition(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "(if 'a' > PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", + "if 'a' > BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext(sdkVersion, + "(if PathNode/TextField/normalize-space(text()) >= 'a' then 'a' else 'b')", "ND-Root", + "if BT-00-Text >= 'a' then 'a' else 'b'"); + testExpressionTranslationWithContext(sdkVersion, "(if PathNode/TextField/normalize-space(text()) >= PathNode/TextField/normalize-space(text()) then 'a' else 'b')", - test("ND-Root", "if BT-00-Text >= BT-00-Text then 'a' else 'b'")); - assertEquals( + "ND-Root", "if BT-00-Text >= BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext(sdkVersion, "(if PathNode/StartDateField/xs:date(text()) >= PathNode/EndDateField/xs:date(text()) then 'a' else 'b')", - test("ND-Root", "if BT-00-StartDate >= BT-00-EndDate then 'a' else 'b'")); - } - - @Test - void testConditionalStringExpression_UsingFieldReference() { - assertEquals("(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else 'b')", - test("ND-Root", "if 'a' > 'b' then BT-00-Text else 'b'")); - assertEquals("(if 'a' > 'b' then 'a' else PathNode/TextField/normalize-space(text()))", - test("ND-Root", "if 'a' > 'b' then 'a' else BT-00-Text")); - assertEquals( + "ND-Root", "if BT-00-StartDate >= BT-00-EndDate then 'a' else 'b'"); + } + + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalStringExpression_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else 'b')", "ND-Root", + "if 'a' > 'b' then BT-00-Text else 'b'"); + testExpressionTranslationWithContext(sdkVersion, + "(if 'a' > 'b' then 'a' else PathNode/TextField/normalize-space(text()))", "ND-Root", + "if 'a' > 'b' then 'a' else BT-00-Text"); + testExpressionTranslationWithContext(sdkVersion, "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else PathNode/TextField/normalize-space(text()))", - test("ND-Root", "if 'a' > 'b' then BT-00-Text else BT-00-Text")); + "ND-Root", "if 'a' > 'b' then BT-00-Text else BT-00-Text"); } - @Test - void testConditionalStringExpression_UsingFieldReferences_TypeMismatch() { - assertThrows(ParseCancellationException.class, - () -> test("ND-Root", "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalStringExpression_UsingFieldReferences_TypeMismatch(final String sdkVersion) { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, + "ND-Root", "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); } - @Test - void testConditionalBooleanExpression() { - assertEquals("(if PathNode/IndicatorField then true() else false())", - test("ND-Root", "if BT-00-Indicator then TRUE else FALSE")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalBooleanExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "(if PathNode/IndicatorField then true() else false())", "ND-Root", + "if BT-00-Indicator then TRUE else FALSE"); } - @Test - void testConditionalNumericExpression() { - assertEquals("(if 1 > 2 then 1 else PathNode/NumberField/number())", - test("ND-Root", "if 1 > 2 then 1 else BT-00-Number")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalNumericExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "(if 1 > 2 then 1 else PathNode/NumberField/number())", "ND-Root", + "if 1 > 2 then 1 else BT-00-Number"); } - @Test - void testConditionalDateExpression() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalDateExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(if xs:date('2012-01-01Z') > PathNode/EndDateField/xs:date(text()) then PathNode/StartDateField/xs:date(text()) else xs:date('2012-01-02Z'))", - test("ND-Root", "if 2012-01-01Z > BT-00-EndDate then BT-00-StartDate else 2012-01-02Z")); + "ND-Root", "if 2012-01-01Z > BT-00-EndDate then BT-00-StartDate else 2012-01-02Z"); } - @Test - void testConditionalTimeExpression() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalTimeExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(if PathNode/EndTimeField/xs:time(text()) > xs:time('00:00:01Z') then PathNode/StartTimeField/xs:time(text()) else xs:time('00:00:01Z'))", - test("ND-Root", "if BT-00-EndTime > 00:00:01Z then BT-00-StartTime else 00:00:01Z")); + "ND-Root", "if BT-00-EndTime > 00:00:01Z then BT-00-StartTime else 00:00:01Z"); } - @Test - void testConditionalDurationExpression() { + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConditionalDurationExpression(final String sdkVersion) { assertEquals( "(if boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P1D') > $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))) then xs:dayTimeDuration('P1D') else xs:dayTimeDuration('P2D'))", - test("ND-Root", "if P1D > BT-00-Measure then P1D else P2D")); + translateExpressionWithContext(sdkVersion, "ND-Root", + "if P1D > BT-00-Measure then P1D else P2D")); } // #endregion: Conditional expressions @@ -468,1036 +598,1244 @@ void testConditionalDurationExpression() { // Strings from iteration --------------------------------------------------- - @Test - void testStringsFromStringIteration_UsingLiterals() { - assertEquals("'a' = (for $x in ('a','b','c') return concat($x, 'text'))", - test("ND-Root", "'a' in (for text:$x in ('a', 'b', 'c') return concat($x, 'text'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromStringIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in ('a','b','c') return concat($x, 'text'))", "ND-Root", + "'a' in (for text:$x in ('a', 'b', 'c') return concat($x, 'text'))"); } - @Test - void testStringsSequenceFromIteration_UsingMultipleIterators() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsSequenceFromIteration_UsingMultipleIterators(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", - test("ND-Root", - "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))")); + "ND-Root", + "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); } - @Test - void testStringsSequenceFromIteration_UsingObjectVariable() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsSequenceFromIteration_UsingObjectVariable(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", - test("ND-Root", - "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'")); + "ND-Root", + "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); } - @Test - void testStringsSequenceFromIteration_UsingNodeContextVariable() { - assertEquals("for $n in .[PathNode/TextField/normalize-space(text()) = 'a'] return 'text'", - test("ND-Root", "for context:$n in ND-Root[BT-00-Text == 'a'] return 'text'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsSequenceFromIteration_UsingNodeContextVariable(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "for $n in .[PathNode/TextField/normalize-space(text()) = 'a'] return 'text'", "ND-Root", + "for context:$n in ND-Root[BT-00-Text == 'a'] return 'text'"); } - @Test - void testStringsFromStringIteration_UsingFieldReference() { - assertEquals("'a' = (for $x in PathNode/TextField return concat($x, 'text'))", - test("ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromStringIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", + "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); } - @Test - void testStringsFromBooleanIteration_UsingLiterals() { - assertEquals("'a' = (for $x in (true(),false()) return 'y')", - test("ND-Root", "'a' in (for indicator:$x in (TRUE, FALSE) return 'y')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromBooleanIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in (true(),false()) return 'y')", "ND-Root", + "'a' in (for indicator:$x in (TRUE, FALSE) return 'y')"); } - @Test - void testStringsFromBooleanIteration_UsingFieldReference() { - assertEquals("'a' = (for $x in PathNode/IndicatorField return 'y')", - test("ND-Root", "'a' in (for indicator:$x in BT-00-Indicator return 'y')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromBooleanIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in PathNode/IndicatorField return 'y')", "ND-Root", + "'a' in (for indicator:$x in BT-00-Indicator return 'y')"); } - @Test - void testStringsFromNumericIteration_UsingLiterals() { - assertEquals("'a' = (for $x in (1,2,3) return 'y')", - test("ND-Root", "'a' in (for number:$x in (1, 2, 3) return 'y')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromNumericIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'a' = (for $x in (1,2,3) return 'y')", + "ND-Root", "'a' in (for number:$x in (1, 2, 3) return 'y')"); } - @Test - void testStringsFromNumericIteration_UsingFieldReference() { - assertEquals("'a' = (for $x in PathNode/NumberField return 'y')", - test("ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromNumericIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in PathNode/NumberField return 'y')", "ND-Root", + "'a' in (for number:$x in BT-00-Number return 'y')"); } - @Test - void testStringsFromDateIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromDateIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'a' = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 'y')", - test("ND-Root", - "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')")); + "ND-Root", "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')"); } - @Test - void testStringsFromDateIteration_UsingFieldReference() { - assertEquals("'a' = (for $x in PathNode/StartDateField return 'y')", - test("ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromDateIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in PathNode/StartDateField return 'y')", "ND-Root", + "'a' in (for date:$x in BT-00-StartDate return 'y')"); } - @Test - void testStringsFromTimeIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromTimeIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'a' = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 'y')", - test("ND-Root", "'a' in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 'y')")); + "ND-Root", "'a' in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 'y')"); } - @Test - void testStringsFromTimeIteration_UsingFieldReference() { - assertEquals("'a' = (for $x in PathNode/StartTimeField return 'y')", - test("ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromTimeIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in PathNode/StartTimeField return 'y')", "ND-Root", + "'a' in (for time:$x in BT-00-StartTime return 'y')"); } - @Test - void testStringsFromDurationIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromDurationIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'a' = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 'y')", - test("ND-Root", "'a' in (for measure:$x in (P1D, P1Y, P2M) return 'y')")); + "ND-Root", "'a' in (for measure:$x in (P1D, P1Y, P2M) return 'y')"); } - @Test - void testStringsFromDurationIteration_UsingFieldReference() { - assertEquals("'a' = (for $x in PathNode/MeasureField return 'y')", - test("ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringsFromDurationIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "'a' = (for $x in PathNode/MeasureField return 'y')", "ND-Root", + "'a' in (for measure:$x in BT-00-Measure return 'y')"); } // Numbers from iteration --------------------------------------------------- - @Test - void testNumbersFromStringIteration_UsingLiterals() { - assertEquals("123 = (for $x in ('a','b','c') return number($x))", - test("ND-Root", "123 in (for text:$x in ('a', 'b', 'c') return number($x))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromStringIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "123 = (for $x in ('a','b','c') return number($x))", "ND-Root", + "123 in (for text:$x in ('a', 'b', 'c') return number($x))"); } - @Test - void testNumbersFromStringIteration_UsingFieldReference() { - assertEquals("123 = (for $x in PathNode/TextField return number($x))", - test("ND-Root", "123 in (for text:$x in BT-00-Text return number($x))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromStringIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "123 = (for $x in PathNode/TextField return number($x))", "ND-Root", + "123 in (for text:$x in BT-00-Text return number($x))"); } - @Test - void testNumbersFromBooleanIteration_UsingLiterals() { - assertEquals("123 = (for $x in (true(),false()) return 0)", - test("ND-Root", "123 in (for indicator:$x in (TRUE, FALSE) return 0)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromBooleanIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "123 = (for $x in (true(),false()) return 0)", + "ND-Root", "123 in (for indicator:$x in (TRUE, FALSE) return 0)"); } - @Test - void testNumbersFromBooleanIteration_UsingFieldReference() { - assertEquals("123 = (for $x in PathNode/IndicatorField return 0)", - test("ND-Root", "123 in (for indicator:$x in BT-00-Indicator return 0)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromBooleanIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "123 = (for $x in PathNode/IndicatorField return 0)", "ND-Root", + "123 in (for indicator:$x in BT-00-Indicator return 0)"); } - @Test - void testNumbersFromNumericIteration_UsingLiterals() { - assertEquals("123 = (for $x in (1,2,3) return 0)", - test("ND-Root", "123 in (for number:$x in (1, 2, 3) return 0)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromNumericIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "123 = (for $x in (1,2,3) return 0)", + "ND-Root", "123 in (for number:$x in (1, 2, 3) return 0)"); } - @Test - void testNumbersFromNumericIteration_UsingFieldReference() { - assertEquals("123 = (for $x in PathNode/NumberField return 0)", - test("ND-Root", "123 in (for number:$x in BT-00-Number return 0)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromNumericIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "123 = (for $x in PathNode/NumberField return 0)", "ND-Root", + "123 in (for number:$x in BT-00-Number return 0)"); } - @Test - void testNumbersFromDateIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromDateIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "123 = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 0)", - test("ND-Root", - "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)")); + "ND-Root", "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)"); } - @Test - void testNumbersFromDateIteration_UsingFieldReference() { - assertEquals("123 = (for $x in PathNode/StartDateField return 0)", - test("ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromDateIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "123 = (for $x in PathNode/StartDateField return 0)", "ND-Root", + "123 in (for date:$x in BT-00-StartDate return 0)"); } - @Test - void testNumbersFromTimeIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromTimeIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "123 = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 0)", - test("ND-Root", "123 in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 0)")); + "ND-Root", "123 in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 0)"); } - @Test - void testNumbersFromTimeIteration_UsingFieldReference() { - assertEquals("123 = (for $x in PathNode/StartTimeField return 0)", - test("ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromTimeIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "123 = (for $x in PathNode/StartTimeField return 0)", "ND-Root", + "123 in (for time:$x in BT-00-StartTime return 0)"); } - @Test - void testNumbersFromDurationIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromDurationIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "123 = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 0)", - test("ND-Root", "123 in (for measure:$x in (P1D, P1Y, P2M) return 0)")); + "ND-Root", "123 in (for measure:$x in (P1D, P1Y, P2M) return 0)"); } - @Test - void testNumbersFromDurationIteration_UsingFieldReference() { - assertEquals("123 = (for $x in PathNode/MeasureField return 0)", - test("ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumbersFromDurationIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "123 = (for $x in PathNode/MeasureField return 0)", "ND-Root", + "123 in (for measure:$x in BT-00-Measure return 0)"); } // Dates from iteration --------------------------------------------------- - @Test - void testDatesFromStringIteration_UsingLiterals() { - assertEquals("xs:date('2022-01-01Z') = (for $x in ('a','b','c') return xs:date($x))", - test("ND-Root", "2022-01-01Z in (for text:$x in ('a', 'b', 'c') return date($x))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromStringIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:date('2022-01-01Z') = (for $x in ('a','b','c') return xs:date($x))", "ND-Root", + "2022-01-01Z in (for text:$x in ('a', 'b', 'c') return date($x))"); } - @Test - void testDatesFromStringIteration_UsingFieldReference() { - assertEquals("xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", - test("ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromStringIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", + "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); } - @Test - void testDatesFromBooleanIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromBooleanIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in (true(),false()) return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for indicator:$x in (TRUE, FALSE) return 2022-01-01Z)")); + "ND-Root", "2022-01-01Z in (for indicator:$x in (TRUE, FALSE) return 2022-01-01Z)"); } - @Test - void testDatesFromBooleanIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromBooleanIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in PathNode/IndicatorField return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for indicator:$x in BT-00-Indicator return 2022-01-01Z)")); + "ND-Root", "2022-01-01Z in (for indicator:$x in BT-00-Indicator return 2022-01-01Z)"); } - @Test - void testDatesFromNumericIteration_UsingLiterals() { - assertEquals("xs:date('2022-01-01Z') = (for $x in (1,2,3) return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for number:$x in (1, 2, 3) return 2022-01-01Z)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromNumericIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:date('2022-01-01Z') = (for $x in (1,2,3) return xs:date('2022-01-01Z'))", "ND-Root", + "2022-01-01Z in (for number:$x in (1, 2, 3) return 2022-01-01Z)"); } - @Test - void testDatesFromNumericIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromNumericIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)")); + "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); } - @Test - void testDatesFromDateIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromDateIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:date('2022-01-01Z'))", - test("ND-Root", - "2022-01-01Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 2022-01-01Z)")); + "ND-Root", + "2022-01-01Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 2022-01-01Z)"); } - @Test - void testDatesFromDateIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromDateIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)")); + "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); } - @Test - void testDatesFromTimeIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromTimeIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:date('2022-01-01Z'))", - test("ND-Root", - "2022-01-01Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 2022-01-01Z)")); + "ND-Root", + "2022-01-01Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 2022-01-01Z)"); } - @Test - void testDatesFromTimeIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromTimeIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)")); + "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); } - @Test - void testDatesFromDurationIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromDurationIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for measure:$x in (P1D, P1Y, P2M) return 2022-01-01Z)")); + "ND-Root", "2022-01-01Z in (for measure:$x in (P1D, P1Y, P2M) return 2022-01-01Z)"); } - @Test - void testDatesFromDurationIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDatesFromDurationIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", - test("ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)")); + "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); } // Times from iteration --------------------------------------------------- - @Test - void testTimesFromStringIteration_UsingLiterals() { - assertEquals("xs:time('12:00:00Z') = (for $x in ('a','b','c') return xs:time($x))", - test("ND-Root", "12:00:00Z in (for text:$x in ('a', 'b', 'c') return time($x))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromStringIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:time('12:00:00Z') = (for $x in ('a','b','c') return xs:time($x))", "ND-Root", + "12:00:00Z in (for text:$x in ('a', 'b', 'c') return time($x))"); } - @Test - void testTimesFromStringIteration_UsingFieldReference() { - assertEquals("xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", - test("ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromStringIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", + "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); } - @Test - void testTimesFromBooleanIteration_UsingLiterals() { - assertEquals("xs:time('12:00:00Z') = (for $x in (true(),false()) return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for indicator:$x in (TRUE, FALSE) return 12:00:00Z)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromBooleanIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:time('12:00:00Z') = (for $x in (true(),false()) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for indicator:$x in (TRUE, FALSE) return 12:00:00Z)"); } - @Test - void testTimesFromBooleanIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromBooleanIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in PathNode/IndicatorField return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for indicator:$x in BT-00-Indicator return 12:00:00Z)")); + "ND-Root", "12:00:00Z in (for indicator:$x in BT-00-Indicator return 12:00:00Z)"); } - @Test - void testTimesFromNumericIteration_UsingLiterals() { - assertEquals("xs:time('12:00:00Z') = (for $x in (1,2,3) return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for number:$x in (1, 2, 3) return 12:00:00Z)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromNumericIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:time('12:00:00Z') = (for $x in (1,2,3) return xs:time('12:00:00Z'))", "ND-Root", + "12:00:00Z in (for number:$x in (1, 2, 3) return 12:00:00Z)"); } - @Test - void testTimesFromNumericIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromNumericIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)")); + "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); } - @Test - void testTimesFromDateIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromDateIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:time('12:00:00Z'))", - test("ND-Root", - "12:00:00Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 12:00:00Z)")); + "ND-Root", + "12:00:00Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 12:00:00Z)"); } - @Test - void testTimesFromDateIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromDateIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)")); + "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); } - @Test - void testTimesFromTimeIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromTimeIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:time('12:00:00Z'))", - test("ND-Root", - "12:00:00Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 12:00:00Z)")); + "ND-Root", + "12:00:00Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 12:00:00Z)"); } - @Test - void testTimesFromTimeIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromTimeIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)")); + "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); } - @Test - void testTimesFromDurationIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromDurationIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for measure:$x in (P1D, P1Y, P2M) return 12:00:00Z)")); + "ND-Root", "12:00:00Z in (for measure:$x in (P1D, P1Y, P2M) return 12:00:00Z)"); } - @Test - void testTimesFromDurationIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimesFromDurationIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", - test("ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)")); + "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); } // Durations from iteration --------------------------------------------------- - @Test - void testDurationsFromStringIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromStringIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')) return $x)", - test("ND-Root", "P1D in (for measure:$x in (P1D, P2D, P1W) return $x)")); + "ND-Root", "P1D in (for measure:$x in (P1D, P2D, P1W) return $x)"); } - @Test - void testDurationsFromStringIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromStringIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", - test("ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))")); + "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); } - @Test - void testDurationsFromBooleanIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromBooleanIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in (true(),false()) return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for indicator:$x in (TRUE, FALSE) return P1D)")); + "ND-Root", "P1D in (for indicator:$x in (TRUE, FALSE) return P1D)"); } - @Test - void testDurationsFromBooleanIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromBooleanIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in PathNode/IndicatorField return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for indicator:$x in BT-00-Indicator return P1D)")); + "ND-Root", "P1D in (for indicator:$x in BT-00-Indicator return P1D)"); } - @Test - void testDurationsFromNumericIteration_UsingLiterals() { - assertEquals("xs:dayTimeDuration('P1D') = (for $x in (1,2,3) return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for number:$x in (1, 2, 3) return P1D)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromNumericIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:dayTimeDuration('P1D') = (for $x in (1,2,3) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for number:$x in (1, 2, 3) return P1D)"); } - @Test - void testDurationsFromNumericIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromNumericIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)")); + "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); } - @Test - void testDurationsFromDateIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromDateIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:dayTimeDuration('P1D'))", - test("ND-Root", - "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)")); + "ND-Root", "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)"); } - @Test - void testDurationsFromDateIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromDateIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)")); + "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); } - @Test - void testDurationsFromTimeIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromTimeIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return P1D)")); + "ND-Root", "P1D in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return P1D)"); } - @Test - void testDurationsFromTimeIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromTimeIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)")); + "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); } - @Test - void testDurationsFromDurationIteration_UsingLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromDurationIteration_UsingLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for measure:$x in (P1D, P1Y, P2M) return P1D)")); + "ND-Root", "P1D in (for measure:$x in (P1D, P1Y, P2M) return P1D)"); } - @Test - void testDurationsFromDurationIteration_UsingFieldReference() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationsFromDurationIteration_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", - test("ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)")); + "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); } // #endregion: Iteration expressions // #region: Numeric expressions --------------------------------------------- - @Test - void testMultiplicationExpression() { - assertEquals("3 * 4", test("BT-00-Text", "3 * 4")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testMultiplicationExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "3 * 4", "BT-00-Text", "3 * 4"); } - @Test - void testAdditionExpression() { - assertEquals("4 + 4", test("BT-00-Text", "4 + 4")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testAdditionExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "4 + 4", "BT-00-Text", "4 + 4"); } - @Test - void testParenthesizedNumericExpression() { - assertEquals("(2 + 2) * 4", test("BT-00-Text", "(2 + 2)*4")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParenthesizedNumericExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(2 + 2) * 4", "BT-00-Text", "(2 + 2)*4"); } - @Test - void testNumericLiteralExpression() { - assertEquals("3.1415", test("BT-00-Text", "3.1415")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumericLiteralExpression(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "3.1415", "BT-00-Text", "3.1415"); } // #endregion: Numeric expressions // #region: Lists ----------------------------------------------------------- - @Test - void testStringList() { - assertEquals("'a' = ('a','b','c')", test("BT-00-Text", "'a' in ('a', 'b', 'c')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringList(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'a' = ('a','b','c')", "BT-00-Text", + "'a' in ('a', 'b', 'c')"); } - @Test - void testNumericList_UsingNumericLiterals() { - assertEquals("4 = (1,2,3)", test("BT-00-Text", "4 in (1, 2, 3)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumericList_UsingNumericLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "4 = (1,2,3)", "BT-00-Text", "4 in (1, 2, 3)"); } - @Test - void testNumericList_UsingNumericField() { - assertEquals("4 = (1,../NumberField/number(),3)", - test("BT-00-Text", "4 in (1, BT-00-Number, 3)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumericList_UsingNumericField(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "4 = (1,../NumberField/number(),3)", + "BT-00-Text", "4 in (1, BT-00-Number, 3)"); } - @Test - void testNumericList_UsingTextField() { + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumericList_UsingTextField(final String sdkVersion) { assertThrows(ParseCancellationException.class, - () -> test("BT-00-Text", "4 in (1, BT-00-Text, 3)")); + () -> translateExpressionWithContext(sdkVersion, "BT-00-Text", "4 in (1, BT-00-Text, 3)")); } - @Test - void testBooleanList() { - assertEquals("false() = (true(),PathNode/IndicatorField,true())", - test("ND-Root", "NEVER in (TRUE, BT-00-Indicator, ALWAYS)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testBooleanList(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "false() = (true(),PathNode/IndicatorField,true())", "ND-Root", + "NEVER in (TRUE, BT-00-Indicator, ALWAYS)"); } - @Test - void testDateList() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateList(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:date('2022-01-01Z') = (xs:date('2022-01-02Z'),PathNode/StartDateField/xs:date(text()),xs:date('2022-02-02Z'))", - test("ND-Root", "2022-01-01Z in (2022-01-02Z, BT-00-StartDate, 2022-02-02Z)")); + "ND-Root", "2022-01-01Z in (2022-01-02Z, BT-00-StartDate, 2022-02-02Z)"); } - @Test - void testTimeList() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimeList(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:time('12:20:21Z') = (xs:time('12:30:00Z'),PathNode/StartTimeField/xs:time(text()),xs:time('13:40:00Z'))", - test("ND-Root", "12:20:21Z in (12:30:00Z, BT-00-StartTime, 13:40:00Z)")); + "ND-Root", "12:20:21Z in (12:30:00Z, BT-00-StartTime, 13:40:00Z)"); } - @Test - void testDurationList_UsingDurationLiterals() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationList_UsingDurationLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "xs:yearMonthDuration('P3M') = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", - test("BT-00-Text", "P3M in (P1M, P3M, P6M)")); + "BT-00-Text", "P3M in (P1M, P3M, P6M)"); } - @Test - void testDurationList_UsingDurationField() { + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDurationList_UsingDurationField(final String sdkVersion) { assertEquals( "(for $F in ../MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", - test("BT-00-Text", "BT-00-Measure in (P1M, P3M, P6M)")); + translateExpressionWithContext(sdkVersion, "BT-00-Text", + "BT-00-Measure in (P1M, P3M, P6M)")); } - @Test - void testCodeList() { - assertEquals("'a' = ('code1','code2','code3')", - test("BT-00-Text", "'a' in codelist:accessibility")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testCodeList(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "'a' = ('code1','code2','code3')", + "BT-00-Text", "'a' in codelist:accessibility"); } // #endregion: Lists // #region: References ------------------------------------------------------ - @Test - void testFieldAttributeValueReference() { - assertEquals("PathNode/TextField/@Attribute = 'text'", - test("ND-Root", "BT-00-Attribute == 'text'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldAttributeValueReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/TextField/@Attribute = 'text'", + "ND-Root", "BT-00-Attribute == 'text'"); } - @Test - void testFieldAttributeValueReference_SameElementContext() { - assertEquals("@Attribute = 'text'", - test("BT-00-Text", "BT-00-Attribute == 'text'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldAttributeValueReference_SameElementContext(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "@Attribute = 'text'", "BT-00-Text", + "BT-00-Attribute == 'text'"); } - @Test - void testScalarFromAttributeReference() { - assertEquals("PathNode/CodeField/@listName", test("ND-Root", "BT-00-Code/@listName")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testScalarFromAttributeReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/CodeField/@listName", "ND-Root", + "BT-00-Code/@listName"); } - @Test - void testScalarFromAttributeReference_SameElementContext() { - assertEquals("./@listName", test("BT-00-Code", "BT-00-Code/@listName")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testScalarFromAttributeReference_SameElementContext(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "./@listName", "BT-00-Code", + "BT-00-Code/@listName"); } - @Test - void testFieldReferenceWithPredicate() { - assertEquals("PathNode/IndicatorField['a' = 'a']", - test("ND-Root", "BT-00-Indicator['a' == 'a']")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReferenceWithPredicate(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/IndicatorField['a' = 'a']", + "ND-Root", "BT-00-Indicator['a' == 'a']"); } - @Test - void testFieldReferenceWithPredicate_WithFieldReferenceInPredicate() { - assertEquals("PathNode/IndicatorField[../CodeField/normalize-space(text()) = 'a']", - test("ND-Root", "BT-00-Indicator[BT-00-Code == 'a']")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReferenceWithPredicate_WithFieldReferenceInPredicate(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/IndicatorField[../CodeField/normalize-space(text()) = 'a']", "ND-Root", + "BT-00-Indicator[BT-00-Code == 'a']"); } - @Test - void testFieldReferenceInOtherNotice() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReferenceInOtherNotice(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "fn:doc(concat($urlPrefix, 'da4d46e9-490b-41ff-a2ae-8166d356a619'))/*/PathNode/TextField/normalize-space(text())", - test("ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text")); + "ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text"); } - @Test - void testFieldReferenceWithFieldContextOverride() { - assertEquals("../TextField/normalize-space(text())", - test("BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReferenceWithFieldContextOverride(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "../TextField/normalize-space(text())", + "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text"); } - @Test - void testFieldReferenceWithFieldContextOverride_WithIntegerField() { - assertEquals("../IntegerField/number()", - test("BT-00-Code", "BT-01-SubLevel-Text::integer")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReferenceWithFieldContextOverride_WithIntegerField(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "../IntegerField/number()", "BT-00-Code", + "BT-01-SubLevel-Text::integer"); } - @Test - void testFieldReferenceWithNodeContextOverride() { - assertEquals("../../PathNode/IntegerField/number()", - test("BT-00-Text", "ND-Root::integer")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReferenceWithNodeContextOverride(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "../../PathNode/IntegerField/number()", + "BT-00-Text", "ND-Root::integer"); } - @Test - void testFieldReferenceWithNodeContextOverride_WithPredicate() { - assertEquals("../../PathNode/IntegerField/number()", - test("BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::integer")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReferenceWithNodeContextOverride_WithPredicate(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "../../PathNode/IntegerField/number()", + "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::integer"); } - @Test - void testAbsoluteFieldReference() { - assertEquals("/*/PathNode/IndicatorField", test("BT-00-Text", "/BT-00-Indicator")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testAbsoluteFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "/*/PathNode/IndicatorField", "BT-00-Text", + "/BT-00-Indicator"); } - @Test - void testSimpleFieldReference() { - assertEquals("../IndicatorField", test("BT-00-Text", "BT-00-Indicator")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSimpleFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "../IndicatorField", "BT-00-Text", + "BT-00-Indicator"); } - @Test - void testFieldReference_ForDurationFields() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReference_ForDurationFields(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "(for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))", - test("ND-Root", "BT-00-Measure")); + "ND-Root", "BT-00-Measure"); } - @Test - void testFieldReference_WithAxis() { - assertEquals("./preceding::PathNode/IntegerField/number()", - test("ND-Root", "ND-Root::preceding::integer")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFieldReference_WithAxis(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "./preceding::PathNode/IntegerField/number()", + "ND-Root", "ND-Root::preceding::integer"); } // #endregion: References // #region: Boolean functions ----------------------------------------------- - @Test - void testNotFunction() { - assertEquals("not(true())", test("BT-00-Text", "not(ALWAYS)")); - assertEquals("not(1 + 1 = 2)", test("BT-00-Text", "not(1 + 1 == 2)")); - assertThrows(ParseCancellationException.class, () -> test("BT-00-Text", "not('text')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNotFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "not(true())", "BT-00-Text", "not(ALWAYS)"); + testExpressionTranslationWithContext(sdkVersion, "not(1 + 1 = 2)", "BT-00-Text", + "not(1 + 1 == 2)"); + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext(sdkVersion, "BT-00-Text", "not('text')")); } - @Test - void testContainsFunction() { - assertEquals("contains(PathNode/TextField/normalize-space(text()), 'xyz')", - test("ND-Root", "contains(BT-00-Text, 'xyz')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testContainsFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "contains(PathNode/TextField/normalize-space(text()), 'xyz')", "ND-Root", + "contains(BT-00-Text, 'xyz')"); } - @Test - void testStartsWithFunction() { - assertEquals("starts-with(PathNode/TextField/normalize-space(text()), 'abc')", - test("ND-Root", "starts-with(BT-00-Text, 'abc')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStartsWithFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "starts-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", + "starts-with(BT-00-Text, 'abc')"); } - @Test - void testEndsWithFunction() { - assertEquals("ends-with(PathNode/TextField/normalize-space(text()), 'abc')", - test("ND-Root", "ends-with(BT-00-Text, 'abc')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testEndsWithFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "ends-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", + "ends-with(BT-00-Text, 'abc')"); } // #endregion: Boolean functions // #region: Numeric functions ----------------------------------------------- - @Test - void testCountFunction_UsingFieldReference() { - assertEquals("count(PathNode/TextField)", test("ND-Root", "count(BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testCountFunction_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "count(PathNode/TextField)", "ND-Root", + "count(BT-00-Text)"); } - @Test - void testCountFunction_UsingSequenceFromIteration() { - assertEquals("count(for $x in PathNode/TextField return concat($x, '-xyz'))", - test("ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testCountFunction_UsingSequenceFromIteration(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", + "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); } - @Test - void testNumberFunction() { - assertEquals("number(PathNode/TextField/normalize-space(text()))", - test("ND-Root", "number(BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testNumberFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "number(PathNode/TextField/normalize-space(text()))", "ND-Root", "number(BT-00-Text)"); } - @Test - void testSumFunction_UsingFieldReference() { - assertEquals("sum(PathNode/NumberField)", test("ND-Root", "sum(BT-00-Number)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSumFunction_UsingFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "sum(PathNode/NumberField)", "ND-Root", + "sum(BT-00-Number)"); } - @Test - void testSumFunction_UsingNumericSequenceFromIteration() { - assertEquals("sum(for $v in PathNode/NumberField return $v + 1)", - test("ND-Root", "sum(for number:$v in BT-00-Number return $v +1)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSumFunction_UsingNumericSequenceFromIteration(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "sum(for $v in PathNode/NumberField return $v + 1)", "ND-Root", + "sum(for number:$v in BT-00-Number return $v +1)"); } - @Test - void testStringLengthFunction() { - assertEquals("string-length(PathNode/TextField/normalize-space(text()))", - test("ND-Root", "string-length(BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringLengthFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "string-length(PathNode/TextField/normalize-space(text()))", "ND-Root", + "string-length(BT-00-Text)"); } // #endregion: Numeric functions // #region: String functions ------------------------------------------------ - @Test - void testSubstringFunction() { - assertEquals("substring(PathNode/TextField/normalize-space(text()), 1, 3)", - test("ND-Root", "substring(BT-00-Text, 1, 3)")); - assertEquals("substring(PathNode/TextField/normalize-space(text()), 4)", - test("ND-Root", "substring(BT-00-Text, 4)")); - } - - @Test - void testToStringFunction() { - assertEquals("format-number(123, '0.##########')", test("ND-Root", "string(123)")); - } - - @Test - void testConcatFunction() { - assertEquals("concat('abc', 'def')", test("ND-Root", "concat('abc', 'def')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSubstringFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "substring(PathNode/TextField/normalize-space(text()), 1, 3)", "ND-Root", + "substring(BT-00-Text, 1, 3)"); + testExpressionTranslationWithContext(sdkVersion, + "substring(PathNode/TextField/normalize-space(text()), 4)", "ND-Root", + "substring(BT-00-Text, 4)"); + } + + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testToStringFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "format-number(123, '0.##########')", + "ND-Root", "string(123)"); + } + + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testConcatFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "concat('abc', 'def')", "ND-Root", + "concat('abc', 'def')"); }; - @Test - void testStringJoinFunction_withLiterals() { - assertEquals("string-join(('abc','def'), ',')", - test("ND-Root", "string-join(('abc', 'def'), ',')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringJoinFunction_withLiterals(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "string-join(('abc','def'), ',')", "ND-Root", + "string-join(('abc', 'def'), ',')"); } - @Test - void testStringJoinFunction_withFieldReference() { - assertEquals("string-join(PathNode/TextField, ',')", - test("ND-Root", "string-join(BT-00-Text, ',')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testStringJoinFunction_withFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "string-join(PathNode/TextField, ',')", + "ND-Root", "string-join(BT-00-Text, ',')"); } - @Test - void testFormatNumberFunction() { - assertEquals("format-number(PathNode/NumberField/number(), '#,##0.00')", - test("ND-Root", "format-number(BT-00-Number, '#,##0.00')")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testFormatNumberFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "format-number(PathNode/NumberField/number(), '#,##0.00')", "ND-Root", + "format-number(BT-00-Number, '#,##0.00')"); } // #endregion: String functions // #region: Date functions -------------------------------------------------- - @Test - void testDateFromStringFunction() { - assertEquals("xs:date(PathNode/TextField/normalize-space(text()))", - test("ND-Root", "date(BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDateFromStringFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:date(PathNode/TextField/normalize-space(text()))", "ND-Root", "date(BT-00-Text)"); } // #endregion: Date functions // #region: Time functions -------------------------------------------------- - @Test - void testTimeFromStringFunction() { - assertEquals("xs:time(PathNode/TextField/normalize-space(text()))", - test("ND-Root", "time(BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testTimeFromStringFunction(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "xs:time(PathNode/TextField/normalize-space(text()))", "ND-Root", "time(BT-00-Text)"); } // #endregion: Time functions // #region: Sequence Functions ---------------------------------------------- - @Test - void testDistinctValuesFunction_WithStringSequences() { - assertEquals("distinct-values(('one','two','one'))", - test("ND-Root", "distinct-values(('one', 'two', 'one'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDistinctValuesFunction_WithStringSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values(('one','two','one'))", + "ND-Root", "distinct-values(('one', 'two', 'one'))"); } - @Test - void testDistinctValuesFunction_WithNumberSequences() { - assertEquals("distinct-values((1,2,3,2,3,4))", - test("ND-Root", "distinct-values((1, 2, 3, 2, 3, 4))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDistinctValuesFunction_WithNumberSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((1,2,3,2,3,4))", "ND-Root", + "distinct-values((1, 2, 3, 2, 3, 4))"); } - @Test - void testDistinctValuesFunction_WithDateSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDistinctValuesFunction_WithDateSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", - test("ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))")); + "ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))"); } - @Test - void testDistinctValuesFunction_WithTimeSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDistinctValuesFunction_WithTimeSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", - test("ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))")); + "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); } - @Test - void testDistinctValuesFunction_WithBooleanSequences() { - assertEquals("distinct-values((true(),false(),false(),false()))", - test("ND-Root", "distinct-values((TRUE, FALSE, FALSE, NEVER))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDistinctValuesFunction_WithBooleanSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values((true(),false(),false(),false()))", "ND-Root", + "distinct-values((TRUE, FALSE, FALSE, NEVER))"); } - @Test - void testDistinctValuesFunction_WithFieldReferences() { - assertEquals("distinct-values(PathNode/TextField)", - test("ND-Root", "distinct-values(BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testDistinctValuesFunction_WithFieldReferences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values(PathNode/TextField)", + "ND-Root", "distinct-values(BT-00-Text)"); } // #region: Union - @Test - void testUnionFunction_WithStringSequences() { - assertEquals("distinct-values((('one','two'), ('two','three','four')))", - test("ND-Root", "value-union(('one', 'two'), ('two', 'three', 'four'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUnionFunction_WithStringSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values((('one','two'), ('two','three','four')))", "ND-Root", + "value-union(('one', 'two'), ('two', 'three', 'four'))"); } - @Test - void testUnionFunction_WithNumberSequences() { - assertEquals("distinct-values(((1,2,3), (2,3,4)))", - test("ND-Root", "value-union((1, 2, 3), (2, 3, 4))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUnionFunction_WithNumberSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values(((1,2,3), (2,3,4)))", + "ND-Root", "value-union((1, 2, 3), (2, 3, 4))"); } - @Test - void testUnionFunction_WithDateSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUnionFunction_WithDateSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", - test("ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); + "ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } - @Test - void testUnionFunction_WithTimeSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUnionFunction_WithTimeSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", - test("ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); + "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } - @Test - void testUnionFunction_WithBooleanSequences() { - assertEquals("distinct-values(((true(),false()), (false(),false())))", - test("ND-Root", "value-union((TRUE, FALSE), (FALSE, NEVER))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUnionFunction_WithBooleanSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values(((true(),false()), (false(),false())))", "ND-Root", + "value-union((TRUE, FALSE), (FALSE, NEVER))"); } - @Test - void testUnionFunction_WithFieldReferences() { - assertEquals("distinct-values((PathNode/TextField, PathNode/TextField))", - test("ND-Root", "value-union(BT-00-Text, BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUnionFunction_WithFieldReferences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", + "value-union(BT-00-Text, BT-00-Text)"); } - @Test - void testUnionFunction_WithTypeMismatch() { - assertThrows(ParseCancellationException.class, - () -> test("ND-Root", "value-union(BT-00-Text, BT-00-Number)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testUnionFunction_WithTypeMismatch(final String sdkVersion) { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, + "ND-Root", "value-union(BT-00-Text, BT-00-Number)")); } // #endregion: Union // #region: Intersect - @Test - void testIntersectFunction_WithStringSequences() { - assertEquals("distinct-values(('one','two')[.= ('two','three','four')])", - test("ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIntersectFunction_WithStringSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", + "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); } - @Test - void testIntersectFunction_WithNumberSequences() { - assertEquals("distinct-values((1,2,3)[.= (2,3,4)])", - test("ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIntersectFunction_WithNumberSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((1,2,3)[.= (2,3,4)])", + "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); } - @Test - void testIntersectFunction_WithDateSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIntersectFunction_WithDateSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", - test("ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); + "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } - @Test - void testIntersectFunction_WithTimeSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIntersectFunction_WithTimeSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", - test("ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); + "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } - @Test - void testIntersectFunction_WithBooleanSequences() { - assertEquals("distinct-values((true(),false())[.= (false(),false())])", - test("ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIntersectFunction_WithBooleanSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values((true(),false())[.= (false(),false())])", "ND-Root", + "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); } - @Test - void testIntersectFunction_WithFieldReferences() { - assertEquals("distinct-values(PathNode/TextField[.= PathNode/TextField])", - test("ND-Root", "value-intersect(BT-00-Text, BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIntersectFunction_WithFieldReferences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", + "value-intersect(BT-00-Text, BT-00-Text)"); } - @Test - void testIntersectFunction_WithTypeMismatch() { - assertThrows(ParseCancellationException.class, - () -> test("ND-Root", "value-intersect(BT-00-Text, BT-00-Number)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIntersectFunction_WithTypeMismatch(final String sdkVersion) { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, + "ND-Root", "value-intersect(BT-00-Text, BT-00-Number)")); } // #endregion: Intersect // #region: Except - @Test - void testExceptFunction_WithStringSequences() { - assertEquals("distinct-values(('one','two')[not(. = ('two','three','four'))])", - test("ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testExceptFunction_WithStringSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", + "value-except(('one', 'two'), ('two', 'three', 'four'))"); } - @Test - void testExceptFunction_WithNumberSequences() { - assertEquals("distinct-values((1,2,3)[not(. = (2,3,4))])", - test("ND-Root", "value-except((1, 2, 3), (2, 3, 4))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testExceptFunction_WithNumberSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((1,2,3)[not(. = (2,3,4))])", + "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); } - @Test - void testExceptFunction_WithDateSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testExceptFunction_WithDateSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", - test("ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); + "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } - @Test - void testExceptFunction_WithTimeSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testExceptFunction_WithTimeSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", - test("ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); + "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } - @Test - void testExceptFunction_WithBooleanSequences() { - assertEquals("distinct-values((true(),false())[not(. = (false(),false()))])", - test("ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testExceptFunction_WithBooleanSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", + "value-except((TRUE, FALSE), (FALSE, NEVER))"); } - @Test - void testExceptFunction_WithFieldReferences() { - assertEquals("distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", - test("ND-Root", "value-except(BT-00-Text, BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testExceptFunction_WithFieldReferences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", + "value-except(BT-00-Text, BT-00-Text)"); } - @Test - void testExceptFunction_WithTypeMismatch() { - assertThrows(ParseCancellationException.class, - () -> test("ND-Root", "value-except(BT-00-Text, BT-00-Number)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testExceptFunction_WithTypeMismatch(final String sdkVersion) { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, + "ND-Root", "value-except(BT-00-Text, BT-00-Number)")); } // #endregion: Except // #region: Compare sequences - @Test - void testSequenceEqualFunction_WithStringSequences() { - assertEquals("deep-equal(sort(('one','two')), sort(('two','three','four')))", - test("ND-Root", "sequence-equal(('one', 'two'), ('two', 'three', 'four'))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSequenceEqualFunction_WithStringSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "deep-equal(sort(('one','two')), sort(('two','three','four')))", "ND-Root", + "sequence-equal(('one', 'two'), ('two', 'three', 'four'))"); } - @Test - void testSequenceEqualFunction_WithNumberSequences() { - assertEquals("deep-equal(sort((1,2,3)), sort((2,3,4)))", - test("ND-Root", "sequence-equal((1, 2, 3), (2, 3, 4))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSequenceEqualFunction_WithNumberSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "deep-equal(sort((1,2,3)), sort((2,3,4)))", + "ND-Root", "sequence-equal((1, 2, 3), (2, 3, 4))"); } - @Test - void testSequenceEqualFunction_WithDateSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSequenceEqualFunction_WithDateSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", - test("ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))")); + "ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } - @Test - void testSequenceEqualFunction_WithTimeSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSequenceEqualFunction_WithTimeSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", - test("ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))")); + "ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } - @Test - void testSequenceEqualFunction_WithBooleanSequences() { - assertEquals("deep-equal(sort((true(),false())), sort((false(),false())))", - test("ND-Root", "sequence-equal((TRUE, FALSE), (FALSE, NEVER))")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSequenceEqualFunction_WithBooleanSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "deep-equal(sort((true(),false())), sort((false(),false())))", "ND-Root", + "sequence-equal((TRUE, FALSE), (FALSE, NEVER))"); } - @Test - void testSequenceEqualFunction_WithDurationSequences() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSequenceEqualFunction_WithDurationSequences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", - test("ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))")); + "ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))"); } - @Test - void testSequenceEqualFunction_WithFieldReferences() { - assertEquals("deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", - test("ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testSequenceEqualFunction_WithFieldReferences(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", + "sequence-equal(BT-00-Text, BT-00-Text)"); } - @Test - void testParametrizedExpression_WithStringParameter() { - assertEquals("'hello' = 'world'", - test1("{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "'hello'", "'world'")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParametrizedExpression_WithStringParameter(final String sdkVersion) { + testExpressionTranslation(sdkVersion, "'hello' = 'world'", + "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "'hello'", "'world'"); } - @Test - void testParametrizedExpression_WithUnquotedStringParameter() { - assertThrows(ParseCancellationException.class, - () -> test1("{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParametrizedExpression_WithUnquotedStringParameter(final String sdkVersion) { + assertThrows(ParseCancellationException.class, () -> translateExpression(sdkVersion, + "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); } - @Test - void testParametrizedExpression_WithNumberParameter() { - assertEquals("1 = 2", - test1("{ND-Root, number:$p1, number:$p2} ${$p1 == $p2}", "1", "2")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParametrizedExpression_WithNumberParameter(final String sdkVersion) { + testExpressionTranslation(sdkVersion, "1 = 2", + "{ND-Root, number:$p1, number:$p2} ${$p1 == $p2}", "1", "2"); } - @Test - void testParametrizedExpression_WithDateParameter() { - assertEquals("xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", - test1("{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParametrizedExpression_WithDateParameter(final String sdkVersion) { + testExpressionTranslation(sdkVersion, "xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", + "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); } - @Test - void testParametrizedExpression_WithTimeParameter() { - assertEquals("xs:time('12:00:00Z') = xs:time('13:00:00Z')", - test1("{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParametrizedExpression_WithTimeParameter(final String sdkVersion) { + testExpressionTranslation(sdkVersion, "xs:time('12:00:00Z') = xs:time('13:00:00Z')", + "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); } - @Test - void testParametrizedExpression_WithBooleanParameter() { - assertEquals("true() = false()", - test1("{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParametrizedExpression_WithBooleanParameter(final String sdkVersion) { + testExpressionTranslation(sdkVersion, "true() = false()", + "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); } - @Test - void testParametrizedExpression_WithDurationParameter() { - assertEquals( + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testParametrizedExpression_WithDurationParameter(final String sdkVersion) { + testExpressionTranslation(sdkVersion, "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", - test1("{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y")); + "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); } // #endregion: Compare sequences @@ -1506,22 +1844,26 @@ void testParametrizedExpression_WithDurationParameter() { // #region: Indexers -------------------------------------------------------- - @Test - void testIndexer_WithFieldReference() { - assertEquals("PathNode/TextField[1]", - test("ND-Root", "text:BT-00-Text[1]")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIndexer_WithFieldReference(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "PathNode/TextField[1]", "ND-Root", + "text:BT-00-Text[1]"); } - @Test - void testIndexer_WithFieldReferenceAndPredicate() { - assertEquals("PathNode/TextField[./normalize-space(text()) = 'hello'][1]", - test("ND-Root", "text:BT-00-Text[BT-00-Text == 'hello'][1]")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIndexer_WithFieldReferenceAndPredicate(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, + "PathNode/TextField[./normalize-space(text()) = 'hello'][1]", "ND-Root", + "text:BT-00-Text[BT-00-Text == 'hello'][1]"); } - @Test - void testIndexer_WithTextSequence() { - assertEquals("('a','b','c')[1]", - test("ND-Root", "('a', 'b','c')[1]")); + @ParameterizedTest + @MethodSource("provideSdkVersions") + void testIndexer_WithTextSequence(final String sdkVersion) { + testExpressionTranslationWithContext(sdkVersion, "('a','b','c')[1]", "ND-Root", + "('a', 'b','c')[1]"); } // #endregion: Indexers From a23dd047c4228c4d949c265ca8d5e3225431498b Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Tue, 18 Apr 2023 17:30:36 +0200 Subject: [PATCH 15/57] [testing]: Moved common code for EFX expression tests into a new class "EfxTestsBase". --- .../ted/efx/EfxExpressionCombinedTest.java | 36 +---------- .../ted/efx/EfxExpressionTranslatorTest.java | 59 +++---------------- .../java/eu/europa/ted/efx/EfxTestsBase.java | 48 +++++++++++++++ 3 files changed, 57 insertions(+), 86 deletions(-) create mode 100644 src/test/java/eu/europa/ted/efx/EfxTestsBase.java diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java index 10841bbf..224b28e7 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java @@ -1,46 +1,12 @@ package eu.europa.ted.efx; -import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import eu.europa.ted.efx.mock.DependencyFactoryMock; /** * Test for EFX expressions that combine several aspects of the language. */ -class EfxExpressionCombinedTest { - private static final String[] SDK_VERSIONS = new String[] {"eforms-sdk-1.0", "eforms-sdk-2.0"}; - - private static Stream provideSdkVersions() { - List arguments = new ArrayList<>(); - - for (String sdkVersion : SDK_VERSIONS) { - arguments.add(Arguments.of(sdkVersion)); - } - - return Stream.of(arguments.toArray(new Arguments[0])); - } - - private void testExpressionTranslationWithContext(final String sdkVersion, - final String expectedTranslation, final String context, final String expression) { - assertEquals(expectedTranslation, - translateExpressionWithContext(sdkVersion, context, expression)); - } - - private String translateExpressionWithContext(String sdkVersion, final String context, - final String expression) { - try { - return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, sdkVersion, - String.format("{%s} ${%s}", context, expression)); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } - } - +class EfxExpressionCombinedTest extends EfxTestsBase { @ParameterizedTest @MethodSource("provideSdkVersions") void testNotAnd(final String sdkVersion) { diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java index 3b9c8d3a..534d0f34 100644 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java @@ -2,54 +2,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import eu.europa.ted.efx.mock.DependencyFactoryMock; - -class EfxExpressionTranslatorTest { - private static final String[] SDK_VERSIONS = new String[] {"eforms-sdk-1.0", "eforms-sdk-2.0"}; - - protected static Stream provideSdkVersions() { - List arguments = new ArrayList<>(); - - for (String sdkVersion : SDK_VERSIONS) { - arguments.add(Arguments.of(sdkVersion)); - } - - return Stream.of(arguments.toArray(new Arguments[0])); - } - - private void testExpressionTranslationWithContext(final String sdkVersion, - final String expectedTranslation, final String context, final String expression) { - assertEquals(expectedTranslation, - translateExpressionWithContext(sdkVersion, context, expression)); - } - - private void testExpressionTranslation(final String sdkVersion, final String expectedTranslation, - final String expression, final String... params) { - assertEquals(expectedTranslation, translateExpression(sdkVersion, expression, params)); - } - - private String translateExpressionWithContext(final String sdkVersion, final String context, - final String expression) { - return translateExpression(sdkVersion, String.format("{%s} ${%s}", context, expression)); - } - - private String translateExpression(final String sdkVersion, final String expression, - final String... params) { - try { - return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, sdkVersion, - expression, params); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } - } +class EfxExpressionTranslatorTest extends EfxTestsBase { // #region: Boolean expressions --------------------------------------------- @ParameterizedTest @@ -1790,49 +1747,49 @@ void testSequenceEqualFunction_WithFieldReferences(final String sdkVersion) { @ParameterizedTest @MethodSource("provideSdkVersions") - void testParametrizedExpression_WithStringParameter(final String sdkVersion) { + void testParameterizedExpression_WithStringParameter(final String sdkVersion) { testExpressionTranslation(sdkVersion, "'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "'hello'", "'world'"); } @ParameterizedTest @MethodSource("provideSdkVersions") - void testParametrizedExpression_WithUnquotedStringParameter(final String sdkVersion) { + void testParameterizedExpression_WithUnquotedStringParameter(final String sdkVersion) { assertThrows(ParseCancellationException.class, () -> translateExpression(sdkVersion, "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); } @ParameterizedTest @MethodSource("provideSdkVersions") - void testParametrizedExpression_WithNumberParameter(final String sdkVersion) { + void testParameterizedExpression_WithNumberParameter(final String sdkVersion) { testExpressionTranslation(sdkVersion, "1 = 2", "{ND-Root, number:$p1, number:$p2} ${$p1 == $p2}", "1", "2"); } @ParameterizedTest @MethodSource("provideSdkVersions") - void testParametrizedExpression_WithDateParameter(final String sdkVersion) { + void testParameterizedExpression_WithDateParameter(final String sdkVersion) { testExpressionTranslation(sdkVersion, "xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); } @ParameterizedTest @MethodSource("provideSdkVersions") - void testParametrizedExpression_WithTimeParameter(final String sdkVersion) { + void testParameterizedExpression_WithTimeParameter(final String sdkVersion) { testExpressionTranslation(sdkVersion, "xs:time('12:00:00Z') = xs:time('13:00:00Z')", "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); } @ParameterizedTest @MethodSource("provideSdkVersions") - void testParametrizedExpression_WithBooleanParameter(final String sdkVersion) { + void testParameterizedExpression_WithBooleanParameter(final String sdkVersion) { testExpressionTranslation(sdkVersion, "true() = false()", "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); } @ParameterizedTest @MethodSource("provideSdkVersions") - void testParametrizedExpression_WithDurationParameter(final String sdkVersion) { + void testParameterizedExpression_WithDurationParameter(final String sdkVersion) { testExpressionTranslation(sdkVersion, "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); diff --git a/src/test/java/eu/europa/ted/efx/EfxTestsBase.java b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java new file mode 100644 index 00000000..78b80e6c --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java @@ -0,0 +1,48 @@ +package eu.europa.ted.efx; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.provider.Arguments; +import eu.europa.ted.efx.mock.DependencyFactoryMock; + +public class EfxTestsBase { + private static final String[] SDK_VERSIONS = new String[] {"eforms-sdk-1.0", "eforms-sdk-2.0"}; + + protected static Stream provideSdkVersions() { + List arguments = new ArrayList<>(); + + for (String sdkVersion : SDK_VERSIONS) { + arguments.add(Arguments.of(sdkVersion)); + } + + return Stream.of(arguments.toArray(new Arguments[0])); + } + + protected void testExpressionTranslationWithContext(final String sdkVersion, + final String expectedTranslation, final String context, final String expression) { + assertEquals(expectedTranslation, + translateExpressionWithContext(sdkVersion, context, expression)); + } + + protected void testExpressionTranslation(final String sdkVersion, + final String expectedTranslation, final String expression, final String... params) { + assertEquals(expectedTranslation, translateExpression(sdkVersion, expression, params)); + } + + protected String translateExpressionWithContext(final String sdkVersion, final String context, + final String expression) { + return translateExpression(sdkVersion, String.format("{%s} ${%s}", context, expression)); + } + + protected String translateExpression(final String sdkVersion, final String expression, + final String... params) { + try { + return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, sdkVersion, + expression, params); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + } +} From 8500c93eb9488c8a78e93dfbcab87a6be4c6ef83 Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Tue, 18 Apr 2023 17:54:25 +0200 Subject: [PATCH 16/57] [test mocks]: Added a SymbolResolverMock Factory class for instantiating the appropriate symbol resolver mock for a given SDK version. Also added "SymbolResolverMockV1" for SDK 1. --- .../ted/efx/mock/DependencyFactoryMock.java | 2 +- .../efx/mock/SymbolResolverMockFactory.java | 35 +++ .../efx/mock/sdk1/SymbolResolverMockV1.java | 211 ++++++++++++++++++ .../SymbolResolverMockV2.java} | 15 +- 4 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java create mode 100644 src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java rename src/test/java/eu/europa/ted/efx/mock/{SymbolResolverMock.java => sdk2/SymbolResolverMockV2.java} (97%) diff --git a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java index 036ac8b2..9c0c5690 100644 --- a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java @@ -22,7 +22,7 @@ private DependencyFactoryMock() {} @Override public SymbolResolver createSymbolResolver(String sdkVersion) { - return SymbolResolverMock.getInstance(sdkVersion); + return SymbolResolverMockFactory.getInstance(sdkVersion); } @Override diff --git a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java new file mode 100644 index 00000000..af5b774e --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java @@ -0,0 +1,35 @@ +package eu.europa.ted.efx.mock; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import eu.europa.ted.efx.interfaces.SymbolResolver; +import eu.europa.ted.efx.mock.sdk1.SymbolResolverMockV1; +import eu.europa.ted.efx.mock.sdk2.SymbolResolverMockV2; + +public class SymbolResolverMockFactory { + private static final Map> symbolResolversBySdkVersion = + Map.of( + "eforms-sdk-1.0", SymbolResolverMockV1.class, + "eforms-sdk-2.0", SymbolResolverMockV2.class); + + private static final Map instances = new HashMap<>(); + + public static SymbolResolver getInstance(final String sdkVersion) { + return instances.computeIfAbsent(sdkVersion, k -> { + try { + Class symbolResolverClass = + symbolResolversBySdkVersion.get(sdkVersion); + + if (symbolResolverClass == null) { + throw new IllegalArgumentException( + MessageFormat.format("Unsupported SDK version [{0}]", sdkVersion)); + } + + return symbolResolverClass.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java new file mode 100644 index 00000000..cf4ce6b8 --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java @@ -0,0 +1,211 @@ +package eu.europa.ted.efx.mock.sdk1; + +import static java.util.Map.entry; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.europa.ted.eforms.sdk.entity.SdkCodelist; +import eu.europa.ted.eforms.sdk.entity.SdkField; +import eu.europa.ted.eforms.sdk.entity.SdkNode; +import eu.europa.ted.efx.interfaces.SymbolResolver; +import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.sdk1.entity.SdkCodelistV1; +import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; +import eu.europa.ted.efx.sdk1.entity.SdkNodeV1; +import eu.europa.ted.efx.xpath.XPathContextualizer; + +public class SymbolResolverMockV1 implements SymbolResolver { + private static final Logger logger = LoggerFactory.getLogger(SymbolResolverMockV1.class); + + protected Map fieldById; + protected Map nodeById; + protected Map codelistById; + + public SymbolResolverMockV1() { + try { + this.loadMapData(); + } catch (JsonProcessingException e) { + logger.error(e.toString(), e); + } + } + + private static JsonNode fromString(final String jsonString) + throws JsonMappingException, JsonProcessingException { + final ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(jsonString, JsonNode.class); + } + + public void loadMapData() throws JsonMappingException, JsonProcessingException { + this.fieldById = Map.ofEntries(// + entry("BT-00-Text", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField\",\"xpathRelative\":\"PathNode/TextField\"}"))), + entry("BT-00-Attribute", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Attribute\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField/@Attribute\",\"xpathRelative\":\"PathNode/TextField/@Attribute\"}"))), + entry("BT-00-Indicator", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-indicator\",\"type\":\"indicator\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IndicatorField\",\"xpathRelative\":\"PathNode/IndicatorField\"}"))), + entry("BT-00-Code", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Code\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), + entry("BT-00-Internal-Code", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Internal-Code\",\"type\":\"internal-code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/InternalCodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), + entry("BT-00-CodeAttribute", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-CodeAttribute\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField/@attribute\",\"xpathRelative\":\"PathNode/CodeField/@attribute\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), + entry("BT-00-Text-Multilingual", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Text-Multilingual\",\"type\":\"text-multilingual\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextMultilingualField\",\"xpathRelative\":\"PathNode/TextMultilingualField\"}}"))), + entry("BT-00-StartDate", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-StartDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartDateField\",\"xpathRelative\":\"PathNode/StartDateField\"}}"))), + entry("BT-00-EndDate", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-EndDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndDateField\",\"xpathRelative\":\"PathNode/EndDateField\"}}"))), + entry("BT-00-StartTime", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-StartTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartTimeField\",\"xpathRelative\":\"PathNode/StartTimeField\"}}"))), + entry("BT-00-EndTime", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-EndTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndTimeField\",\"xpathRelative\":\"PathNode/EndTimeField\"}}"))), + entry("BT-00-Measure", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Measure\",\"type\":\"measure\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/MeasureField\",\"xpathRelative\":\"PathNode/MeasureField\"}}"))), + entry("BT-00-Integer", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Integer\",\"type\":\"integer\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IntegerField\",\"xpathRelative\":\"PathNode/IntegerField\"}}"))), + entry("BT-00-Amount", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Amount\",\"type\":\"amount\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/AmountField\",\"xpathRelative\":\"PathNode/AmountField\"}}"))), + entry("BT-00-Url", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Url\",\"type\":\"url\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/UrlField\",\"xpathRelative\":\"PathNode/UrlField\"}}"))), + entry("BT-00-Zoned-Date", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Zoned-Date\",\"type\":\"zoned-date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedDateField\",\"xpathRelative\":\"PathNode/ZonedDateField\"}}"))), + entry("BT-00-Zoned-Time", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Zoned-Time\",\"type\":\"zoned-time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedTimeField\",\"xpathRelative\":\"PathNode/ZonedTimeField\"}}"))), + entry("BT-00-Id-Ref", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Id-Ref\",\"type\":\"id-ref\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IdRefField\",\"xpathRelative\":\"PathNode/IdRefField\"}}"))), + entry("BT-00-Number", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Number\",\"type\":\"number\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/NumberField\",\"xpathRelative\":\"PathNode/NumberField\"}}"))), + entry("BT-00-Phone", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Phone\",\"type\":\"phone\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/PhoneField\",\"xpathRelative\":\"PathNode/PhoneField\"}}"))), + entry("BT-00-Email", new SdkFieldV1(fromString( + "{\"id\":\"BT-00-Email\",\"type\":\"email\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EmailField\",\"xpathRelative\":\"PathNode/EmailField\"}}"))), + entry("BT-01-SubLevel-Text", new SdkFieldV1(fromString( + "{\"id\":\"BT-01-SubLevel-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ChildNode/SubLevelTextField\",\"xpathRelative\":\"PathNode/ChildNode/SubLevelTextField\"}}"))), + entry("BT-01-SubNode-Text", new SdkFieldV1(fromString( + "{\"id\":\"BT-01-SubNode-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-SubNode\",\"xpathAbsolute\":\"/*/SubNode/SubTextField\",\"xpathRelative\":\"SubTextField\"}}")))); + + this.nodeById = Map.ofEntries(// + entry("ND-Root", new SdkNodeV1("ND-Root", null, "/*", "/*", false)), entry("ND-SubNode", + new SdkNodeV1("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false))); + + + this.codelistById = new HashMap<>(Map.ofEntries( + buildCodelistMock("accessibility", Optional.empty()), + buildCodelistMock("authority-activity", Optional.of("main-activity")), + buildCodelistMock("main-activity", Optional.empty()))); + } + + private static Entry buildCodelistMock(final String codelistId, + final Optional parentId) { + return entry(codelistId, new SdkCodelistV1(codelistId, "0.0.1", + Arrays.asList("code1", "code2", "code3"), parentId)); + } + + public SdkField getFieldById(String fieldId) { + return this.fieldById.get(fieldId); + } + + @Override + public PathExpression getRelativePathOfField(String fieldId, PathExpression contextPath) { + return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfField(fieldId)); + } + + @Override + public PathExpression getRelativePathOfNode(String nodeId, PathExpression contextPath) { + return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfNode(nodeId)); + } + + @Override + public PathExpression getRelativePath(PathExpression absolutePath, PathExpression contextPath) { + return XPathContextualizer.contextualize(contextPath, absolutePath); + } + + @Override + public String getTypeOfField(String fieldId) { + final SdkField sdkField = fieldById.get(fieldId); + if (sdkField == null) { + throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); + } + return sdkField.getType(); + } + + @Override + public String getRootCodelistOfField(final String fieldId) { + final SdkField sdkField = fieldById.get(fieldId); + if (sdkField == null) { + throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); + } + final String codelistId = sdkField.getCodelistId(); + if (codelistId == null) { + throw new ParseCancellationException(String.format("No codelist for field '%s'.", fieldId)); + } + + final SdkCodelist sdkCodelist = codelistById.get(codelistId); + if (sdkCodelist == null) { + throw new ParseCancellationException(String.format("Unknown codelist '%s'.", codelistId)); + } + + return sdkCodelist.getRootCodelistId(); + } + + @Override + public List expandCodelist(String codelistId) { + SdkCodelist codelist = codelistById.get(codelistId); + if (codelist == null) { + throw new ParseCancellationException(String.format("Codelist '%s' not found.", codelistId)); + } + return codelist.getCodes(); + } + + /** + * Gets the id of the parent node of a given field. + * + * @param fieldId The id of the field who's parent node we are looking for. + * @return The id of the parent node of the given field. + */ + @Override + public String getParentNodeOfField(final String fieldId) { + final SdkField sdkField = fieldById.get(fieldId); + if (sdkField != null) { + return sdkField.getParentNodeId(); + } + throw new ParseCancellationException(String.format("Unknown field '%s'", fieldId)); + } + + /** + * @param fieldId The id of a field. + * @return The xPath of the given field. + */ + @Override + public PathExpression getAbsolutePathOfField(final String fieldId) { + final SdkField sdkField = fieldById.get(fieldId); + if (sdkField == null) { + throw new ParseCancellationException( + String.format("Unknown field identifier '%s'.", fieldId)); + } + return new PathExpression(sdkField.getXpathAbsolute()); + } + + /** + * @param nodeId The id of a node or a field. + * @return The xPath of the given node or field. + */ + @Override + public PathExpression getAbsolutePathOfNode(final String nodeId) { + final SdkNode sdkNode = nodeById.get(nodeId); + if (sdkNode == null) { + throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); + } + return new PathExpression(sdkNode.getXpathAbsolute()); + } +} diff --git a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java similarity index 97% rename from src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java rename to src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java index 157cdeae..2f57aa95 100644 --- a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java @@ -1,4 +1,4 @@ -package eu.europa.ted.efx.mock; +package eu.europa.ted.efx.mock.sdk2; import static java.util.Map.entry; import java.util.Arrays; @@ -25,15 +25,8 @@ import eu.europa.ted.efx.sdk2.entity.SdkNodeV2; import eu.europa.ted.efx.xpath.XPathContextualizer; -public class SymbolResolverMock implements SymbolResolver { - - private static final Logger logger = LoggerFactory.getLogger(SymbolResolverMock.class); - - private static final Map instances = new HashMap<>(); - - public static SymbolResolverMock getInstance(final String sdkVersion) { - return instances.computeIfAbsent(sdkVersion, k -> new SymbolResolverMock()); - } +public class SymbolResolverMockV2 implements SymbolResolver { + private static final Logger logger = LoggerFactory.getLogger(SymbolResolverMockV2.class); protected Map fieldById; protected Map fieldByAlias; @@ -41,7 +34,7 @@ public static SymbolResolverMock getInstance(final String sdkVersion) { protected Map nodeByAlias; protected Map codelistById; - protected SymbolResolverMock() { + public SymbolResolverMockV2() { try { this.loadMapData(); } catch (JsonProcessingException e) { From 2d90ae3a16d3257428e423a388550de56b107e70 Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Wed, 19 Apr 2023 14:57:39 +0200 Subject: [PATCH 17/57] [test mocks]: Moved common code of symbol resolver mock classes into a parent abstract class. --- .../ted/efx/sdk1/entity/SdkFieldV1.java | 27 +++ .../ted/efx/sdk2/entity/SdkFieldV2.java | 15 ++ .../efx/mock/AbstractSymbolResolverMock.java | 173 +++++++++++++++ .../efx/mock/SymbolResolverMockFactory.java | 7 +- .../efx/mock/sdk1/SymbolResolverMockV1.java | 200 ++--------------- .../efx/mock/sdk2/SymbolResolverMockV2.java | 207 +++--------------- src/test/resources/json/fields-sdk1.json | 184 ++++++++++++++++ src/test/resources/json/fields-sdk2.json | 207 ++++++++++++++++++ 8 files changed, 663 insertions(+), 357 deletions(-) create mode 100644 src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java create mode 100644 src/test/resources/json/fields-sdk1.json create mode 100644 src/test/resources/json/fields-sdk2.json diff --git a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java index 469a9265..db807f73 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java @@ -1,5 +1,8 @@ package eu.europa.ted.efx.sdk1.entity; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; @@ -17,4 +20,28 @@ public SdkFieldV1(final String id, final String type, final String parentNodeId, public SdkFieldV1(final JsonNode field) { super(field); } + + @JsonCreator + public SdkFieldV1( + @JsonProperty("id") final String id, + @JsonProperty("type") final String type, + @JsonProperty("parentNodeId") final String parentNodeId, + @JsonProperty("xpathAbsolute") final String xpathAbsolute, + @JsonProperty("xpathRelative") final String xpathRelative, + @JsonProperty("codeList") final Map> codelist) { + this(id, type, parentNodeId, xpathAbsolute, xpathRelative, getCodelistId(codelist)); + } + + protected static String getCodelistId(Map> codelist) { + if (codelist == null) { + return null; + } + + Map value = codelist.get("value"); + if (value == null) { + return null; + } + + return value.get("id"); + } } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java index c9860847..92e92174 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java @@ -1,5 +1,8 @@ package eu.europa.ted.efx.sdk2.entity; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; @@ -20,6 +23,18 @@ public SdkFieldV2(JsonNode fieldNode) { this.alias = fieldNode.has("alias") ? fieldNode.get("alias").asText(null) : null; } + @JsonCreator + public SdkFieldV2( + @JsonProperty("id") final String id, + @JsonProperty("type") final String type, + @JsonProperty("parentNodeId") final String parentNodeId, + @JsonProperty("xpathAbsolute") final String xpathAbsolute, + @JsonProperty("xpathRelative") final String xpathRelative, + @JsonProperty("codeList") final Map> codelist, + @JsonProperty("alias") final String alias) { + this(id, type, parentNodeId, xpathAbsolute, xpathRelative, getCodelistId(codelist), alias); + } + public String getAlias() { return alias; } diff --git a/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java b/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java new file mode 100644 index 00000000..6fb8b169 --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java @@ -0,0 +1,173 @@ +package eu.europa.ted.efx.mock; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.europa.ted.eforms.sdk.entity.SdkCodelist; +import eu.europa.ted.eforms.sdk.entity.SdkField; +import eu.europa.ted.eforms.sdk.entity.SdkNode; +import eu.europa.ted.efx.interfaces.SymbolResolver; +import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.xpath.XPathContextualizer; + +public abstract class AbstractSymbolResolverMock + implements SymbolResolver { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final Path TEST_JSON_DIR = Path.of("src", "test", "resources", "json"); + + private static final ObjectMapper mapper = new ObjectMapper(); + + protected Map fieldById; + protected Map nodeById; + protected Map codelistById; + + public AbstractSymbolResolverMock() throws IOException { + this.loadMapData(); + } + + protected abstract Map createNodeById(); + + protected abstract Map createCodelistById(); + + protected abstract Class getSdkFieldClass(); + + protected abstract String getFieldsJsonFilename(); + + protected void loadMapData() throws IOException { + this.fieldById = createFieldById(); + this.nodeById = createNodeById(); + this.codelistById = createCodelistById(); + } + + protected Map createFieldById() throws IOException { + Path fieldsJsonPath = Path.of(TEST_JSON_DIR.toString(), getFieldsJsonFilename()); + + if (!Files.isRegularFile(fieldsJsonPath)) { + throw new FileNotFoundException(fieldsJsonPath.toString()); + } + + List fields = mapper + .readerForListOf(getSdkFieldClass()) + .readValue(fieldsJsonPath.toFile()); + + return fields.stream().collect(Collectors.toMap(SdkField::getId, Function.identity())); + } + + protected F getFieldById(String fieldId) { + return this.fieldById.get(fieldId); + } + + protected N getNodeById(String nodeId) { + return this.nodeById.get(nodeId); + } + + protected C getCodelistById(String codelistId) { + return this.codelistById.get(codelistId); + } + + @Override + public PathExpression getRelativePathOfField(String fieldId, PathExpression contextPath) { + return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfField(fieldId)); + } + + @Override + public PathExpression getRelativePathOfNode(String nodeId, PathExpression contextPath) { + return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfNode(nodeId)); + } + + @Override + public PathExpression getRelativePath(PathExpression absolutePath, PathExpression contextPath) { + return XPathContextualizer.contextualize(contextPath, absolutePath); + } + + @Override + public String getTypeOfField(String fieldId) { + final SdkField sdkField = getFieldById(fieldId); + if (sdkField == null) { + throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); + } + return sdkField.getType(); + } + + @Override + public String getRootCodelistOfField(final String fieldId) { + final SdkField sdkField = getFieldById(fieldId); + if (sdkField == null) { + throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); + } + final String codelistId = sdkField.getCodelistId(); + if (codelistId == null) { + throw new ParseCancellationException(String.format("No codelist for field '%s'.", fieldId)); + } + + final SdkCodelist sdkCodelist = getCodelistById(codelistId); + if (sdkCodelist == null) { + throw new ParseCancellationException(String.format("Unknown codelist '%s'.", codelistId)); + } + + return sdkCodelist.getRootCodelistId(); + } + + @Override + public List expandCodelist(String codelistId) { + SdkCodelist codelist = getCodelistById(codelistId); + if (codelist == null) { + throw new ParseCancellationException(String.format("Codelist '%s' not found.", codelistId)); + } + return codelist.getCodes(); + } + + /** + * Gets the id of the parent node of a given field. + * + * @param fieldId The id of the field who's parent node we are looking for. + * @return The id of the parent node of the given field. + */ + @Override + public String getParentNodeOfField(final String fieldId) { + final SdkField sdkField = getFieldById(fieldId); + if (sdkField != null) { + return sdkField.getParentNodeId(); + } + throw new ParseCancellationException(String.format("Unknown field '%s'", fieldId)); + } + + /** + * @param fieldId The id of a field. + * @return The xPath of the given field. + */ + @Override + public PathExpression getAbsolutePathOfField(final String fieldId) { + final SdkField sdkField = getFieldById(fieldId); + if (sdkField == null) { + throw new ParseCancellationException( + String.format("Unknown field identifier '%s'.", fieldId)); + } + return new PathExpression(sdkField.getXpathAbsolute()); + } + + /** + * @param nodeId The id of a node or a field. + * @return The xPath of the given node or field. + */ + @Override + public PathExpression getAbsolutePathOfNode(final String nodeId) { + final SdkNode sdkNode = getNodeById(nodeId); + + if (sdkNode == null) { + throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); + } + + return new PathExpression(sdkNode.getXpathAbsolute()); + } +} diff --git a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java index af5b774e..edb55874 100644 --- a/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java +++ b/src/test/java/eu/europa/ted/efx/mock/SymbolResolverMockFactory.java @@ -1,5 +1,6 @@ package eu.europa.ted.efx.mock; +import static java.util.Map.entry; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; @@ -9,9 +10,9 @@ public class SymbolResolverMockFactory { private static final Map> symbolResolversBySdkVersion = - Map.of( - "eforms-sdk-1.0", SymbolResolverMockV1.class, - "eforms-sdk-2.0", SymbolResolverMockV2.class); + Map.ofEntries( + entry("eforms-sdk-1.0", SymbolResolverMockV1.class), + entry("eforms-sdk-2.0", SymbolResolverMockV2.class)); private static final Map instances = new HashMap<>(); diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java index cf4ce6b8..eeddb116 100644 --- a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java +++ b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java @@ -1,211 +1,53 @@ package eu.europa.ted.efx.mock.sdk1; import static java.util.Map.entry; +import java.io.IOException; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.europa.ted.eforms.sdk.entity.SdkCodelist; -import eu.europa.ted.eforms.sdk.entity.SdkField; -import eu.europa.ted.eforms.sdk.entity.SdkNode; -import eu.europa.ted.efx.interfaces.SymbolResolver; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.mock.AbstractSymbolResolverMock; import eu.europa.ted.efx.sdk1.entity.SdkCodelistV1; import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; import eu.europa.ted.efx.sdk1.entity.SdkNodeV1; -import eu.europa.ted.efx.xpath.XPathContextualizer; -public class SymbolResolverMockV1 implements SymbolResolver { - private static final Logger logger = LoggerFactory.getLogger(SymbolResolverMockV1.class); +public class SymbolResolverMockV1 + extends AbstractSymbolResolverMock { - protected Map fieldById; - protected Map nodeById; - protected Map codelistById; - - public SymbolResolverMockV1() { - try { - this.loadMapData(); - } catch (JsonProcessingException e) { - logger.error(e.toString(), e); - } - } - - private static JsonNode fromString(final String jsonString) - throws JsonMappingException, JsonProcessingException { - final ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(jsonString, JsonNode.class); - } - - public void loadMapData() throws JsonMappingException, JsonProcessingException { - this.fieldById = Map.ofEntries(// - entry("BT-00-Text", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField\",\"xpathRelative\":\"PathNode/TextField\"}"))), - entry("BT-00-Attribute", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Attribute\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField/@Attribute\",\"xpathRelative\":\"PathNode/TextField/@Attribute\"}"))), - entry("BT-00-Indicator", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-indicator\",\"type\":\"indicator\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IndicatorField\",\"xpathRelative\":\"PathNode/IndicatorField\"}"))), - entry("BT-00-Code", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Code\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-Internal-Code", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Internal-Code\",\"type\":\"internal-code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/InternalCodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-CodeAttribute", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-CodeAttribute\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField/@attribute\",\"xpathRelative\":\"PathNode/CodeField/@attribute\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-Text-Multilingual", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Text-Multilingual\",\"type\":\"text-multilingual\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextMultilingualField\",\"xpathRelative\":\"PathNode/TextMultilingualField\"}}"))), - entry("BT-00-StartDate", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-StartDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartDateField\",\"xpathRelative\":\"PathNode/StartDateField\"}}"))), - entry("BT-00-EndDate", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-EndDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndDateField\",\"xpathRelative\":\"PathNode/EndDateField\"}}"))), - entry("BT-00-StartTime", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-StartTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartTimeField\",\"xpathRelative\":\"PathNode/StartTimeField\"}}"))), - entry("BT-00-EndTime", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-EndTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndTimeField\",\"xpathRelative\":\"PathNode/EndTimeField\"}}"))), - entry("BT-00-Measure", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Measure\",\"type\":\"measure\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/MeasureField\",\"xpathRelative\":\"PathNode/MeasureField\"}}"))), - entry("BT-00-Integer", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Integer\",\"type\":\"integer\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IntegerField\",\"xpathRelative\":\"PathNode/IntegerField\"}}"))), - entry("BT-00-Amount", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Amount\",\"type\":\"amount\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/AmountField\",\"xpathRelative\":\"PathNode/AmountField\"}}"))), - entry("BT-00-Url", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Url\",\"type\":\"url\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/UrlField\",\"xpathRelative\":\"PathNode/UrlField\"}}"))), - entry("BT-00-Zoned-Date", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Zoned-Date\",\"type\":\"zoned-date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedDateField\",\"xpathRelative\":\"PathNode/ZonedDateField\"}}"))), - entry("BT-00-Zoned-Time", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Zoned-Time\",\"type\":\"zoned-time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedTimeField\",\"xpathRelative\":\"PathNode/ZonedTimeField\"}}"))), - entry("BT-00-Id-Ref", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Id-Ref\",\"type\":\"id-ref\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IdRefField\",\"xpathRelative\":\"PathNode/IdRefField\"}}"))), - entry("BT-00-Number", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Number\",\"type\":\"number\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/NumberField\",\"xpathRelative\":\"PathNode/NumberField\"}}"))), - entry("BT-00-Phone", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Phone\",\"type\":\"phone\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/PhoneField\",\"xpathRelative\":\"PathNode/PhoneField\"}}"))), - entry("BT-00-Email", new SdkFieldV1(fromString( - "{\"id\":\"BT-00-Email\",\"type\":\"email\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EmailField\",\"xpathRelative\":\"PathNode/EmailField\"}}"))), - entry("BT-01-SubLevel-Text", new SdkFieldV1(fromString( - "{\"id\":\"BT-01-SubLevel-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ChildNode/SubLevelTextField\",\"xpathRelative\":\"PathNode/ChildNode/SubLevelTextField\"}}"))), - entry("BT-01-SubNode-Text", new SdkFieldV1(fromString( - "{\"id\":\"BT-01-SubNode-Text\",\"type\":\"text\",\"parentNodeId\":\"ND-SubNode\",\"xpathAbsolute\":\"/*/SubNode/SubTextField\",\"xpathRelative\":\"SubTextField\"}}")))); - - this.nodeById = Map.ofEntries(// - entry("ND-Root", new SdkNodeV1("ND-Root", null, "/*", "/*", false)), entry("ND-SubNode", - new SdkNodeV1("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false))); - - - this.codelistById = new HashMap<>(Map.ofEntries( - buildCodelistMock("accessibility", Optional.empty()), - buildCodelistMock("authority-activity", Optional.of("main-activity")), - buildCodelistMock("main-activity", Optional.empty()))); + public SymbolResolverMockV1() throws IOException { + super(); } - private static Entry buildCodelistMock(final String codelistId, + private static Entry buildCodelistMock(final String codelistId, final Optional parentId) { return entry(codelistId, new SdkCodelistV1(codelistId, "0.0.1", Arrays.asList("code1", "code2", "code3"), parentId)); } - public SdkField getFieldById(String fieldId) { - return this.fieldById.get(fieldId); - } - @Override - public PathExpression getRelativePathOfField(String fieldId, PathExpression contextPath) { - return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfField(fieldId)); - } - - @Override - public PathExpression getRelativePathOfNode(String nodeId, PathExpression contextPath) { - return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfNode(nodeId)); - } - - @Override - public PathExpression getRelativePath(PathExpression absolutePath, PathExpression contextPath) { - return XPathContextualizer.contextualize(contextPath, absolutePath); - } - - @Override - public String getTypeOfField(String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); - if (sdkField == null) { - throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); - } - return sdkField.getType(); - } - - @Override - public String getRootCodelistOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); - if (sdkField == null) { - throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); - } - final String codelistId = sdkField.getCodelistId(); - if (codelistId == null) { - throw new ParseCancellationException(String.format("No codelist for field '%s'.", fieldId)); - } - - final SdkCodelist sdkCodelist = codelistById.get(codelistId); - if (sdkCodelist == null) { - throw new ParseCancellationException(String.format("Unknown codelist '%s'.", codelistId)); - } - - return sdkCodelist.getRootCodelistId(); - } - - @Override - public List expandCodelist(String codelistId) { - SdkCodelist codelist = codelistById.get(codelistId); - if (codelist == null) { - throw new ParseCancellationException(String.format("Codelist '%s' not found.", codelistId)); - } - return codelist.getCodes(); + protected Map createNodeById() { + return Map.ofEntries( + entry("ND-Root", new SdkNodeV1("ND-Root", null, "/*", "/*", false)), + entry("ND-SubNode", + new SdkNodeV1("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false))); } - /** - * Gets the id of the parent node of a given field. - * - * @param fieldId The id of the field who's parent node we are looking for. - * @return The id of the parent node of the given field. - */ @Override - public String getParentNodeOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); - if (sdkField != null) { - return sdkField.getParentNodeId(); - } - throw new ParseCancellationException(String.format("Unknown field '%s'", fieldId)); + protected Map createCodelistById() { + return new HashMap<>(Map.ofEntries( + buildCodelistMock("accessibility", Optional.empty()), + buildCodelistMock("authority-activity", Optional.of("main-activity")), + buildCodelistMock("main-activity", Optional.empty()))); } - /** - * @param fieldId The id of a field. - * @return The xPath of the given field. - */ @Override - public PathExpression getAbsolutePathOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); - if (sdkField == null) { - throw new ParseCancellationException( - String.format("Unknown field identifier '%s'.", fieldId)); - } - return new PathExpression(sdkField.getXpathAbsolute()); + protected Class getSdkFieldClass() { + return SdkFieldV1.class; } - /** - * @param nodeId The id of a node or a field. - * @return The xPath of the given node or field. - */ @Override - public PathExpression getAbsolutePathOfNode(final String nodeId) { - final SdkNode sdkNode = nodeById.get(nodeId); - if (sdkNode == null) { - throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); - } - return new PathExpression(sdkNode.getXpathAbsolute()); + protected String getFieldsJsonFilename() { + return "fields-sdk1.json"; } } diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java index 2f57aa95..ae3e6840 100644 --- a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java +++ b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java @@ -1,117 +1,25 @@ package eu.europa.ted.efx.mock.sdk2; import static java.util.Map.entry; +import java.io.IOException; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; -import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.europa.ted.eforms.sdk.entity.SdkCodelist; -import eu.europa.ted.eforms.sdk.entity.SdkField; -import eu.europa.ted.eforms.sdk.entity.SdkNode; -import eu.europa.ted.efx.interfaces.SymbolResolver; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.mock.AbstractSymbolResolverMock; import eu.europa.ted.efx.sdk2.entity.SdkCodelistV2; import eu.europa.ted.efx.sdk2.entity.SdkFieldV2; import eu.europa.ted.efx.sdk2.entity.SdkNodeV2; -import eu.europa.ted.efx.xpath.XPathContextualizer; -public class SymbolResolverMockV2 implements SymbolResolver { - private static final Logger logger = LoggerFactory.getLogger(SymbolResolverMockV2.class); - - protected Map fieldById; +public class SymbolResolverMockV2 + extends AbstractSymbolResolverMock { protected Map fieldByAlias; - protected Map nodeById; protected Map nodeByAlias; - protected Map codelistById; - - public SymbolResolverMockV2() { - try { - this.loadMapData(); - } catch (JsonProcessingException e) { - logger.error(e.toString(), e); - } - } - private static JsonNode fromString(final String jsonString) - throws JsonMappingException, JsonProcessingException { - final ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(jsonString, JsonNode.class); - } - - public void loadMapData() throws JsonMappingException, JsonProcessingException { - this.fieldById = Map.ofEntries(// - entry("BT-00-Text", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Text\",\"alias\":\"text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField\",\"xpathRelative\":\"PathNode/TextField\"}"))), - entry("BT-00-Attribute", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Attribute\",\"alias\":\"attribute\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextField/@Attribute\",\"xpathRelative\":\"PathNode/TextField/@Attribute\"}"))), - entry("BT-00-Indicator", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-indicator\",\"alias\":\"indicator\",\"type\":\"indicator\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IndicatorField\",\"xpathRelative\":\"PathNode/IndicatorField\"}"))), - entry("BT-00-Code", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Code\",\"alias\":\"code\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-Internal-Code", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Internal-Code\",\"alias\":\"internalCode\",\"type\":\"internal-code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/InternalCodeField\",\"xpathRelative\":\"PathNode/CodeField\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-CodeAttribute", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-CodeAttribute\",\"alias\":\"codeAttribute\",\"type\":\"code\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/CodeField/@attribute\",\"xpathRelative\":\"PathNode/CodeField/@attribute\",\"codeList\":{\"value\":{\"id\":\"authority-activity\",\"type\":\"flat\",\"parentId\":\"main-activity\"}}}"))), - entry("BT-00-Text-Multilingual", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Text-Multilingual\",\"alias\":\"textMultilingual\",\"type\":\"text-multilingual\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/TextMultilingualField\",\"xpathRelative\":\"PathNode/TextMultilingualField\"}}"))), - entry("BT-00-StartDate", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-StartDate\",\"alias\":\"startDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartDateField\",\"xpathRelative\":\"PathNode/StartDateField\"}}"))), - entry("BT-00-EndDate", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-EndDate\",\"alias\":\"endDate\",\"type\":\"date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndDateField\",\"xpathRelative\":\"PathNode/EndDateField\"}}"))), - entry("BT-00-StartTime", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-StartTime\",\"alias\":\"startTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/StartTimeField\",\"xpathRelative\":\"PathNode/StartTimeField\"}}"))), - entry("BT-00-EndTime", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-EndTime\",\"alias\":\"endTime\",\"type\":\"time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EndTimeField\",\"xpathRelative\":\"PathNode/EndTimeField\"}}"))), - entry("BT-00-Measure", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Measure\",\"alias\":\"measure\",\"type\":\"measure\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/MeasureField\",\"xpathRelative\":\"PathNode/MeasureField\"}}"))), - entry("BT-00-Integer", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Integer\",\"alias\":\"integer\",\"type\":\"integer\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IntegerField\",\"xpathRelative\":\"PathNode/IntegerField\"}}"))), - entry("BT-00-Amount", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Amount\",\"alias\":\"amount\",\"type\":\"amount\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/AmountField\",\"xpathRelative\":\"PathNode/AmountField\"}}"))), - entry("BT-00-Url", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Url\",\"alias\":\"url\",\"type\":\"url\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/UrlField\",\"xpathRelative\":\"PathNode/UrlField\"}}"))), - entry("BT-00-Zoned-Date", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Zoned-Date\",\"alias\":\"zonedDate\",\"type\":\"zoned-date\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedDateField\",\"xpathRelative\":\"PathNode/ZonedDateField\"}}"))), - entry("BT-00-Zoned-Time", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Zoned-Time\",\"alias\":\"zonedTime\",\"type\":\"zoned-time\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ZonedTimeField\",\"xpathRelative\":\"PathNode/ZonedTimeField\"}}"))), - entry("BT-00-Id-Ref", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Id-Ref\",\"alias\":\"idRef\",\"type\":\"id-ref\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/IdRefField\",\"xpathRelative\":\"PathNode/IdRefField\"}}"))), - entry("BT-00-Number", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Number\",\"alias\":\"number\",\"type\":\"number\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/NumberField\",\"xpathRelative\":\"PathNode/NumberField\"}}"))), - entry("BT-00-Phone", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Phone\",\"alias\":\"phone\",\"type\":\"phone\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/PhoneField\",\"xpathRelative\":\"PathNode/PhoneField\"}}"))), - entry("BT-00-Email", new SdkFieldV2(fromString( - "{\"id\":\"BT-00-Email\",\"alias\":\"email\",\"type\":\"email\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/EmailField\",\"xpathRelative\":\"PathNode/EmailField\"}}"))), - entry("BT-01-SubLevel-Text", new SdkFieldV2(fromString( - "{\"id\":\"BT-01-SubLevel-Text\",\"alias\":\"subLevel_text\",\"type\":\"text\",\"parentNodeId\":\"ND-Root\",\"xpathAbsolute\":\"/*/PathNode/ChildNode/SubLevelTextField\",\"xpathRelative\":\"PathNode/ChildNode/SubLevelTextField\"}}"))), - entry("BT-01-SubNode-Text", new SdkFieldV2(fromString( - "{\"id\":\"BT-01-SubNode-Text\",\"alias\":\"subNode_text\",\"type\":\"text\",\"parentNodeId\":\"ND-SubNode\",\"xpathAbsolute\":\"/*/SubNode/SubTextField\",\"xpathRelative\":\"SubTextField\"}}")))); - - this.fieldByAlias = this.fieldById.entrySet().stream() - .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); - - this.nodeById = Map.ofEntries(// - entry("ND-Root", new SdkNodeV2("ND-Root", null, "/*", "/*", false, "Root")), - entry("ND-SubNode", - new SdkNodeV2("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false, "SubNode"))); - - this.nodeByAlias = this.nodeById.entrySet().stream() - .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); - - this.codelistById = new HashMap<>(Map.ofEntries( - buildCodelistMock("accessibility", Optional.empty()), - buildCodelistMock("authority-activity", Optional.of("main-activity")), - buildCodelistMock("main-activity", Optional.empty()))); + public SymbolResolverMockV2() throws IOException { + super(); } private static Entry buildCodelistMock(final String codelistId, @@ -120,103 +28,52 @@ private static Entry buildCodelistMock(final String codel Arrays.asList("code1", "code2", "code3"), parentId)); } - public SdkField getFieldById(String fieldId) { - return this.fieldById.containsKey(fieldId) ? this.fieldById.get(fieldId) - : this.fieldByAlias.get(fieldId); - } - @Override - public PathExpression getRelativePathOfField(String fieldId, PathExpression contextPath) { - return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfField(fieldId)); - } + public void loadMapData() throws IOException { + super.loadMapData(); - @Override - public PathExpression getRelativePathOfNode(String nodeId, PathExpression contextPath) { - return XPathContextualizer.contextualize(contextPath, getAbsolutePathOfNode(nodeId)); - } + this.fieldByAlias = this.fieldById.entrySet().stream() + .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); - @Override - public PathExpression getRelativePath(PathExpression absolutePath, PathExpression contextPath) { - return XPathContextualizer.contextualize(contextPath, absolutePath); + this.nodeByAlias = this.nodeById.entrySet().stream() + .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); } @Override - public String getTypeOfField(String fieldId) { - final SdkField sdkField = getFieldById(fieldId); - if (sdkField == null) { - throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); - } - return sdkField.getType(); + public SdkFieldV2 getFieldById(final String fieldId) { + return this.fieldById.containsKey(fieldId) ? this.fieldById.get(fieldId) + : this.fieldByAlias.get(fieldId); } @Override - public String getRootCodelistOfField(final String fieldId) { - final SdkField sdkField = getFieldById(fieldId); - if (sdkField == null) { - throw new ParseCancellationException(String.format("Unknown field '%s'.", fieldId)); - } - final String codelistId = sdkField.getCodelistId(); - if (codelistId == null) { - throw new ParseCancellationException(String.format("No codelist for field '%s'.", fieldId)); - } - - final SdkCodelist sdkCodelist = codelistById.get(codelistId); - if (sdkCodelist == null) { - throw new ParseCancellationException(String.format("Unknown codelist '%s'.", codelistId)); - } - - return sdkCodelist.getRootCodelistId(); + public SdkNodeV2 getNodeById(final String nodeId) { + return this.nodeById.containsKey(nodeId) ? this.nodeById.get(nodeId) + : this.nodeByAlias.get(nodeId); } @Override - public List expandCodelist(String codelistId) { - SdkCodelist codelist = codelistById.get(codelistId); - if (codelist == null) { - throw new ParseCancellationException(String.format("Codelist '%s' not found.", codelistId)); - } - return codelist.getCodes(); + protected Map createNodeById() { + return Map.ofEntries( + entry("ND-Root", new SdkNodeV2("ND-Root", null, "/*", "/*", false, "Root")), + entry("ND-SubNode", + new SdkNodeV2("ND-SubNode", "ND-Root", "/*/SubNode", "SubNode", false, "SubNode"))); } - /** - * Gets the id of the parent node of a given field. - * - * @param fieldId The id of the field who's parent node we are looking for. - * @return The id of the parent node of the given field. - */ @Override - public String getParentNodeOfField(final String fieldId) { - final SdkField sdkField = getFieldById(fieldId); - if (sdkField != null) { - return sdkField.getParentNodeId(); - } - throw new ParseCancellationException(String.format("Unknown field '%s'", fieldId)); + protected Map createCodelistById() { + return new HashMap<>(Map.ofEntries( + buildCodelistMock("accessibility", Optional.empty()), + buildCodelistMock("authority-activity", Optional.of("main-activity")), + buildCodelistMock("main-activity", Optional.empty()))); } - /** - * @param fieldId The id of a field. - * @return The xPath of the given field. - */ @Override - public PathExpression getAbsolutePathOfField(final String fieldId) { - final SdkField sdkField = getFieldById(fieldId); - if (sdkField == null) { - throw new ParseCancellationException( - String.format("Unknown field identifier '%s'.", fieldId)); - } - return new PathExpression(sdkField.getXpathAbsolute()); + protected Class getSdkFieldClass() { + return SdkFieldV2.class; } - /** - * @param nodeId The id of a node or a field. - * @return The xPath of the given node or field. - */ @Override - public PathExpression getAbsolutePathOfNode(final String nodeId) { - final SdkNode sdkNode = - nodeById.containsKey(nodeId) ? nodeById.get(nodeId) : nodeByAlias.get(nodeId); - if (sdkNode == null) { - throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); - } - return new PathExpression(sdkNode.getXpathAbsolute()); + protected String getFieldsJsonFilename() { + return "fields-sdk2.json"; } } diff --git a/src/test/resources/json/fields-sdk1.json b/src/test/resources/json/fields-sdk1.json new file mode 100644 index 00000000..2edcc39e --- /dev/null +++ b/src/test/resources/json/fields-sdk1.json @@ -0,0 +1,184 @@ +[ + { + "id": "BT-00-Text", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextField", + "xpathRelative": "PathNode/TextField" + }, + { + "id": "BT-00-Attribute", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextField/@Attribute", + "xpathRelative": "PathNode/TextField/@Attribute" + }, + { + "id": "BT-00-Indicator", + "type": "indicator", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IndicatorField", + "xpathRelative": "PathNode/IndicatorField" + }, + { + "id": "BT-00-Code", + "type": "code", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/CodeField", + "xpathRelative": "PathNode/CodeField", + "codeList": { + "value": { + "id": "authority-activity", + "type": "flat", + "parentId": "main-activity" + } + } + }, + { + "id": "BT-00-Internal-Code", + "type": "internal-code", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/InternalCodeField", + "xpathRelative": "PathNode/CodeField", + "codeList": { + "value": { + "id": "authority-activity", + "type": "flat", + "parentId": "main-activity" + } + } + }, + { + "id": "BT-00-CodeAttribute", + "type": "code", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/CodeField/@attribute", + "xpathRelative": "PathNode/CodeField/@attribute", + "codeList": { + "value": { + "id": "authority-activity", + "type": "flat", + "parentId": "main-activity" + } + } + }, + { + "id": "BT-00-Text-Multilingual", + "type": "text-multilingual", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextMultilingualField", + "xpathRelative": "PathNode/TextMultilingualField" + }, + { + "id": "BT-00-StartDate", + "type": "date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/StartDateField", + "xpathRelative": "PathNode/StartDateField" + }, + { + "id": "BT-00-EndDate", + "type": "date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EndDateField", + "xpathRelative": "PathNode/EndDateField" + }, + { + "id": "BT-00-StartTime", + "type": "time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/StartTimeField", + "xpathRelative": "PathNode/StartTimeField" + }, + { + "id": "BT-00-EndTime", + "type": "time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EndTimeField", + "xpathRelative": "PathNode/EndTimeField" + }, + { + "id": "BT-00-Measure", + "type": "measure", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/MeasureField", + "xpathRelative": "PathNode/MeasureField" + }, + { + "id": "BT-00-Integer", + "type": "integer", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IntegerField", + "xpathRelative": "PathNode/IntegerField" + }, + { + "id": "BT-00-Amount", + "type": "amount", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/AmountField", + "xpathRelative": "PathNode/AmountField" + }, + { + "id": "BT-00-Url", + "type": "url", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/UrlField", + "xpathRelative": "PathNode/UrlField" + }, + { + "id": "BT-00-Zoned-Date", + "type": "zoned-date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ZonedDateField", + "xpathRelative": "PathNode/ZonedDateField" + }, + { + "id": "BT-00-Zoned-Time", + "type": "zoned-time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ZonedTimeField", + "xpathRelative": "PathNode/ZonedTimeField" + }, + { + "id": "BT-00-Id-Ref", + "type": "id-ref", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IdRefField", + "xpathRelative": "PathNode/IdRefField" + }, + { + "id": "BT-00-Number", + "type": "number", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/NumberField", + "xpathRelative": "PathNode/NumberField" + }, + { + "id": "BT-00-Phone", + "type": "phone", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/PhoneField", + "xpathRelative": "PathNode/PhoneField" + }, + { + "id": "BT-00-Email", + "type": "email", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EmailField", + "xpathRelative": "PathNode/EmailField" + }, + { + "id": "BT-01-SubLevel-Text", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ChildNode/SubLevelTextField", + "xpathRelative": "PathNode/ChildNode/SubLevelTextField" + }, + { + "id": "BT-01-SubNode-Text", + "type": "text", + "parentNodeId": "ND-SubNode", + "xpathAbsolute": "/*/SubNode/SubTextField", + "xpathRelative": "SubTextField" + } +] diff --git a/src/test/resources/json/fields-sdk2.json b/src/test/resources/json/fields-sdk2.json new file mode 100644 index 00000000..a07130eb --- /dev/null +++ b/src/test/resources/json/fields-sdk2.json @@ -0,0 +1,207 @@ +[ + { + "id": "BT-00-Text", + "alias": "text", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextField", + "xpathRelative": "PathNode/TextField" + }, + { + "id": "BT-00-Attribute", + "alias": "attribute", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextField/@Attribute", + "xpathRelative": "PathNode/TextField/@Attribute" + }, + { + "id": "BT-00-Indicator", + "alias": "indicator", + "type": "indicator", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IndicatorField", + "xpathRelative": "PathNode/IndicatorField" + }, + { + "id": "BT-00-Code", + "alias": "code", + "type": "code", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/CodeField", + "xpathRelative": "PathNode/CodeField", + "codeList": { + "value": { + "id": "authority-activity", + "type": "flat", + "parentId": "main-activity" + } + } + }, + { + "id": "BT-00-Internal-Code", + "alias": "internalCode", + "type": "internal-code", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/InternalCodeField", + "xpathRelative": "PathNode/CodeField", + "codeList": { + "value": { + "id": "authority-activity", + "type": "flat", + "parentId": "main-activity" + } + } + }, + { + "id": "BT-00-CodeAttribute", + "alias": "codeAttribute", + "type": "code", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/CodeField/@attribute", + "xpathRelative": "PathNode/CodeField/@attribute", + "codeList": { + "value": { + "id": "authority-activity", + "type": "flat", + "parentId": "main-activity" + } + } + }, + { + "id": "BT-00-Text-Multilingual", + "alias": "textMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextMultilingualField", + "xpathRelative": "PathNode/TextMultilingualField" + }, + { + "id": "BT-00-StartDate", + "alias": "startDate", + "type": "date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/StartDateField", + "xpathRelative": "PathNode/StartDateField" + }, + { + "id": "BT-00-EndDate", + "alias": "endDate", + "type": "date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EndDateField", + "xpathRelative": "PathNode/EndDateField" + }, + { + "id": "BT-00-StartTime", + "alias": "startTime", + "type": "time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/StartTimeField", + "xpathRelative": "PathNode/StartTimeField" + }, + { + "id": "BT-00-EndTime", + "alias": "endTime", + "type": "time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EndTimeField", + "xpathRelative": "PathNode/EndTimeField" + }, + { + "id": "BT-00-Measure", + "alias": "measure", + "type": "measure", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/MeasureField", + "xpathRelative": "PathNode/MeasureField" + }, + { + "id": "BT-00-Integer", + "alias": "integer", + "type": "integer", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IntegerField", + "xpathRelative": "PathNode/IntegerField" + }, + { + "id": "BT-00-Amount", + "alias": "amount", + "type": "amount", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/AmountField", + "xpathRelative": "PathNode/AmountField" + }, + { + "id": "BT-00-Url", + "alias": "url", + "type": "url", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/UrlField", + "xpathRelative": "PathNode/UrlField" + }, + { + "id": "BT-00-Zoned-Date", + "alias": "zonedDate", + "type": "zoned-date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ZonedDateField", + "xpathRelative": "PathNode/ZonedDateField" + }, + { + "id": "BT-00-Zoned-Time", + "alias": "zonedTime", + "type": "zoned-time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ZonedTimeField", + "xpathRelative": "PathNode/ZonedTimeField" + }, + { + "id": "BT-00-Id-Ref", + "alias": "idRef", + "type": "id-ref", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IdRefField", + "xpathRelative": "PathNode/IdRefField" + }, + { + "id": "BT-00-Number", + "alias": "number", + "type": "number", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/NumberField", + "xpathRelative": "PathNode/NumberField" + }, + { + "id": "BT-00-Phone", + "alias": "phone", + "type": "phone", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/PhoneField", + "xpathRelative": "PathNode/PhoneField" + }, + { + "id": "BT-00-Email", + "alias": "email", + "type": "email", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EmailField", + "xpathRelative": "PathNode/EmailField" + }, + { + "id": "BT-01-SubLevel-Text", + "alias": "subLevel_text", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ChildNode/SubLevelTextField", + "xpathRelative": "PathNode/ChildNode/SubLevelTextField" + }, + { + "id": "BT-01-SubNode-Text", + "alias": "subNode_text", + "type": "text", + "parentNodeId": "ND-SubNode", + "xpathAbsolute": "/*/SubNode/SubTextField", + "xpathRelative": "SubTextField" + } +] From 67fc379451e46c1402e830395b5a2a52f314988c Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Fri, 21 Apr 2023 18:44:02 +0200 Subject: [PATCH 18/57] [testing]: Added missing tests for SDK 1. --- .../ted/efx/EfxExpressionCombinedTest.java | 46 - .../ted/efx/EfxExpressionTranslatorTest.java | 1827 ----------------- .../java/eu/europa/ted/efx/EfxTestsBase.java | 52 +- .../efx/sdk1/EfxExpressionCombinedV1Test.java | 46 + .../sdk1/EfxExpressionTranslatorV1Test.java | 1606 +++++++++++++++ .../efx/sdk1/EfxTemplateTranslatorV1Test.java | 345 ++++ .../efx/sdk2/EfxExpressionCombinedV2Test.java | 45 + .../sdk2/EfxExpressionTranslatorV2Test.java | 1234 +++++++++++ .../EfxTemplateTranslatorV2Test.java} | 112 +- 9 files changed, 3354 insertions(+), 1959 deletions(-) delete mode 100644 src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java delete mode 100644 src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java create mode 100644 src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java create mode 100644 src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java create mode 100644 src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java create mode 100644 src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java create mode 100644 src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java rename src/test/java/eu/europa/ted/efx/{EfxTemplateTranslatorTest.java => sdk2/EfxTemplateTranslatorV2Test.java} (79%) diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java deleted file mode 100644 index 224b28e7..00000000 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package eu.europa.ted.efx; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -/** - * Test for EFX expressions that combine several aspects of the language. - */ -class EfxExpressionCombinedTest extends EfxTestsBase { - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNotAnd(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "not(1 = 2) and (2 = 2)", "BT-00-Text", - "not(1 == 2) and (2 == 2)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNotPresentAndNotPresent(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "not(PathNode/TextField) and not(PathNode/IntegerField)", "ND-Root", - "BT-00-Text is not present and BT-00-Integer is not present"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testCountWithNodeContextOverride(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "count(../../PathNode/CodeField) = 1", - "BT-00-Text", "count(ND-Root::BT-00-Code) == 1"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testCountWithAbsoluteFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "count(/*/PathNode/CodeField) = 1", - "BT-00-Text", "count(/BT-00-Code) == 1"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testCountWithAbsoluteFieldReferenceAndPredicate(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1", "BT-00-Text", - "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); - } -} diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java deleted file mode 100644 index 534d0f34..00000000 --- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java +++ /dev/null @@ -1,1827 +0,0 @@ -package eu.europa.ted.efx; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -class EfxExpressionTranslatorTest extends EfxTestsBase { - // #region: Boolean expressions --------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParenthesizedBooleanExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "(true() or true()) and false()", "BT-00-Text", - "(ALWAYS or TRUE) and NEVER"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testLogicalOrCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "true() or false()", "BT-00-Text", - "ALWAYS or NEVER"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testLogicalAndCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "true() and 1 + 1 = 2", "BT-00-Text", - "ALWAYS and 1 + 1 == 2"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testInListCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "not('x' = ('a','b','c'))", "BT-00-Text", - "'x' not in ('a', 'b', 'c')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testEmptinessCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/TextField/normalize-space(text()) = ''", "ND-Root", "BT-00-Text is empty"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testEmptinessCondition_WithNot(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/TextField/normalize-space(text()) != ''", "ND-Root", "BT-00-Text is not empty"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testPresenceCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "PathNode/TextField", "ND-Root", - "BT-00-Text is present"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testPresenceCondition_WithNot(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "not(PathNode/TextField)", "ND-Root", - "BT-00-Text is not present"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUniqueValueCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", - "ND-Root", "BT-00-Text is unique in /BT-00-Text"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUniqueValueCondition_WithNot(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", - "ND-Root", "BT-00-Text is not unique in /BT-00-Text"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testLikePatternCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "fn:matches(normalize-space('123'), '[0-9]*')", - "BT-00-Text", "'123' like '[0-9]*'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testLikePatternCondition_WithNot(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "not(fn:matches(normalize-space('123'), '[0-9]*'))", "BT-00-Text", - "'123' not like '[0-9]*'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_UsingTextFields(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", - "Root", "text == textMultilingual"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_UsingNumericFields(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/NumberField/number() <= PathNode/IntegerField/number()", "ND-Root", - "BT-00-Number <= integer"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_UsingIndicatorFields(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/IndicatorField != PathNode/IndicatorField", "ND-Root", - "BT-00-Indicator != BT-00-Indicator"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_UsingDateFields(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/StartDateField/xs:date(text()) <= PathNode/EndDateField/xs:date(text())", - "ND-Root", "BT-00-StartDate <= BT-00-EndDate"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_UsingTimeFields(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/StartTimeField/xs:time(text()) <= PathNode/EndTimeField/xs:time(text())", - "ND-Root", "BT-00-StartTime <= BT-00-EndTime"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_UsingMeasureFields(final String sdkVersion) { - assertEquals( - "boolean(for $T in (current-date()) return ($T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) <= $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", - translateExpressionWithContext(sdkVersion, "ND-Root", "BT-00-Measure <= BT-00-Measure")); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_WithStringLiteral(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/TextField/normalize-space(text()) = 'abc'", "ND-Root", "BT-00-Text == 'abc'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_WithNumericLiteral(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", "ND-Root", - "integer - BT-00-Number > 0"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_WithDateLiteral(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') > PathNode/StartDateField/xs:date(text())", "ND-Root", - "2022-01-01Z > BT-00-StartDate"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_WithTimeLiteral(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('00:01:00Z') > PathNode/EndTimeField/xs:time(text())", "ND-Root", - "00:01:00Z > BT-00-EndTime"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldValueComparison_TypeMismatch(final String sdkVersion) { - assertThrows(ParseCancellationException.class, - () -> translateExpressionWithContext(sdkVersion, "ND-Root", "00:01:00 > BT-00-StartDate")); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testBooleanComparison_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "false() != true()", "BT-00-Text", - "NEVER != ALWAYS"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testBooleanComparison_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "../IndicatorField != true()", "BT-00-Text", - "BT-00-Indicator != ALWAYS"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumericComparison(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", - "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringComparison(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "'aaa' < 'bbb'", "BT-00-Text", - "'aaa' < 'bbb'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateComparison_OfTwoDateLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2018-01-01Z') > xs:date('2018-01-01Z')", "BT-00-Text", - "2018-01-01Z > 2018-01-01Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateComparison_OfTwoDateReferences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/StartDateField/xs:date(text()) = PathNode/EndDateField/xs:date(text())", - "ND-Root", "BT-00-StartDate == BT-00-EndDate"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateComparison_OfDateReferenceAndDateFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/StartDateField/xs:date(text()) = xs:date(PathNode/TextField/normalize-space(text()))", - "ND-Root", "BT-00-StartDate == date(BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimeComparison_OfTwoTimeLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "xs:time('13:00:10Z') > xs:time('21:20:30Z')", - "BT-00-Text", "13:00:10Z > 21:20:30Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testZonedTimeComparison_OfTwoTimeLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('13:00:10+01:00') > xs:time('21:20:30+02:00')", "BT-00-Text", - "13:00:10+01:00 > 21:20:30+02:00"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimeComparison_OfTwoTimeReferences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/StartTimeField/xs:time(text()) = PathNode/EndTimeField/xs:time(text())", - "ND-Root", "BT-00-StartTime == BT-00-EndTime"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimeComparison_OfTimeReferenceAndTimeFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/StartTimeField/xs:time(text()) = xs:time(PathNode/TextField/normalize-space(text()))", - "ND-Root", "BT-00-StartTime == time(BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationComparison_UsingYearMOnthDurationLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P12M')))", - "BT-00-Text", "P1Y == P12M"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationComparison_UsingDayTimeDurationLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P21D') > $T + xs:dayTimeDuration('P7D')))", - "BT-00-Text", "P3W > P7D"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testCalculatedDurationComparison(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P3M') > $T + xs:dayTimeDuration(PathNode/EndDateField/xs:date(text()) - PathNode/StartDateField/xs:date(text()))))", - "ND-Root", "P3M > (BT-00-EndDate - BT-00-StartDate)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNegativeDuration_Literal(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "xs:yearMonthDuration('-P3M')", "ND-Root", - "-P3M"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNegativeDuration_ViaMultiplication(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "(-3 * (2 * xs:yearMonthDuration('-P3M')))", - "ND-Root", "2 * -P3M * -3"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNegativeDuration_ViaMultiplicationWithField(final String sdkVersion) { - assertEquals( - "(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", - translateExpressionWithContext(sdkVersion, "ND-Root", "2 * measure:BT-00-Measure * -3")); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationAddition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(xs:dayTimeDuration('P3D') + xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", - "ND-Root", "P3D + (BT-00-StartDate - BT-00-EndDate)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationSubtraction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(xs:dayTimeDuration('P3D') - xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", - "ND-Root", "P3D - (BT-00-StartDate - BT-00-EndDate)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testBooleanLiteralExpression_Always(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "true()", "BT-00-Text", "ALWAYS"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testBooleanLiteralExpression_Never(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "false()", "BT-00-Text", "NEVER"); - } - - // #endregion: Boolean expressions - - // #region: Quantified expressions ------------------------------------------ - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringQuantifiedExpression_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in ('a','b','c') satisfies $x <= 'a'", "ND-Root", - "every text:$x in ('a', 'b', 'c') satisfies $x <= 'a'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringQuantifiedExpression_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in PathNode/TextField satisfies $x <= 'a'", "ND-Root", - "every text:$x in BT-00-Text satisfies $x <= 'a'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testBooleanQuantifiedExpression_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in (true(),false(),true()) satisfies $x", "ND-Root", - "every indicator:$x in (TRUE, FALSE, ALWAYS) satisfies $x"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testBooleanQuantifiedExpression_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in PathNode/IndicatorField satisfies $x", "ND-Root", - "every indicator:$x in BT-00-Indicator satisfies $x"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumericQuantifiedExpression_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "every $x in (1,2,3) satisfies $x <= 1", - "ND-Root", "every number:$x in (1, 2, 3) satisfies $x <= 1"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumericQuantifiedExpression_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in PathNode/NumberField satisfies $x <= 1", "ND-Root", - "every number:$x in BT-00-Number satisfies $x <= 1"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateQuantifiedExpression_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) satisfies $x <= xs:date('2012-01-01Z')", - "ND-Root", - "every date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) satisfies $x <= 2012-01-01Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateQuantifiedExpression_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", - "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateQuantifiedExpression_UsingMultipleIterators(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", - "ND-Root", - "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimeQuantifiedExpression_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in (xs:time('00:00:00Z'),xs:time('00:00:01Z'),xs:time('00:00:02Z')) satisfies $x <= xs:time('00:00:00Z')", - "ND-Root", "every time:$x in (00:00:00Z, 00:00:01Z, 00:00:02Z) satisfies $x <= 00:00:00Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimeQuantifiedExpression_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", - "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationQuantifiedExpression_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P3D')) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", - "ND-Root", "every measure:$x in (P1D, P2D, P3D) satisfies $x <= P1D"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationQuantifiedExpression_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", - "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); - } - - // #endregion: Quantified expressions - - // #region: Conditional expressions ----------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "(if 1 > 2 then 'a' else 'b')", "ND-Root", - "if 1 > 2 then 'a' else 'b'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalStringExpression_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "(if 'a' > 'b' then 'a' else 'b')", "ND-Root", - "if 'a' > 'b' then 'a' else 'b'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalStringExpression_UsingFieldReferenceInCondition(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(if 'a' > PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", - "if 'a' > BT-00-Text then 'a' else 'b'"); - testExpressionTranslationWithContext(sdkVersion, - "(if PathNode/TextField/normalize-space(text()) >= 'a' then 'a' else 'b')", "ND-Root", - "if BT-00-Text >= 'a' then 'a' else 'b'"); - testExpressionTranslationWithContext(sdkVersion, - "(if PathNode/TextField/normalize-space(text()) >= PathNode/TextField/normalize-space(text()) then 'a' else 'b')", - "ND-Root", "if BT-00-Text >= BT-00-Text then 'a' else 'b'"); - testExpressionTranslationWithContext(sdkVersion, - "(if PathNode/StartDateField/xs:date(text()) >= PathNode/EndDateField/xs:date(text()) then 'a' else 'b')", - "ND-Root", "if BT-00-StartDate >= BT-00-EndDate then 'a' else 'b'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalStringExpression_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else 'b')", "ND-Root", - "if 'a' > 'b' then BT-00-Text else 'b'"); - testExpressionTranslationWithContext(sdkVersion, - "(if 'a' > 'b' then 'a' else PathNode/TextField/normalize-space(text()))", "ND-Root", - "if 'a' > 'b' then 'a' else BT-00-Text"); - testExpressionTranslationWithContext(sdkVersion, - "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else PathNode/TextField/normalize-space(text()))", - "ND-Root", "if 'a' > 'b' then BT-00-Text else BT-00-Text"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalStringExpression_UsingFieldReferences_TypeMismatch(final String sdkVersion) { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, - "ND-Root", "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalBooleanExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(if PathNode/IndicatorField then true() else false())", "ND-Root", - "if BT-00-Indicator then TRUE else FALSE"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalNumericExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(if 1 > 2 then 1 else PathNode/NumberField/number())", "ND-Root", - "if 1 > 2 then 1 else BT-00-Number"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalDateExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(if xs:date('2012-01-01Z') > PathNode/EndDateField/xs:date(text()) then PathNode/StartDateField/xs:date(text()) else xs:date('2012-01-02Z'))", - "ND-Root", "if 2012-01-01Z > BT-00-EndDate then BT-00-StartDate else 2012-01-02Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalTimeExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(if PathNode/EndTimeField/xs:time(text()) > xs:time('00:00:01Z') then PathNode/StartTimeField/xs:time(text()) else xs:time('00:00:01Z'))", - "ND-Root", "if BT-00-EndTime > 00:00:01Z then BT-00-StartTime else 00:00:01Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConditionalDurationExpression(final String sdkVersion) { - assertEquals( - "(if boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P1D') > $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))) then xs:dayTimeDuration('P1D') else xs:dayTimeDuration('P2D'))", - translateExpressionWithContext(sdkVersion, "ND-Root", - "if P1D > BT-00-Measure then P1D else P2D")); - } - - // #endregion: Conditional expressions - - // #region: Iteration expressions ------------------------------------------- - - // Strings from iteration --------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromStringIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in ('a','b','c') return concat($x, 'text'))", "ND-Root", - "'a' in (for text:$x in ('a', 'b', 'c') return concat($x, 'text'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsSequenceFromIteration_UsingMultipleIterators(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", - "ND-Root", - "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsSequenceFromIteration_UsingObjectVariable(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", - "ND-Root", - "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsSequenceFromIteration_UsingNodeContextVariable(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "for $n in .[PathNode/TextField/normalize-space(text()) = 'a'] return 'text'", "ND-Root", - "for context:$n in ND-Root[BT-00-Text == 'a'] return 'text'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromStringIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", - "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromBooleanIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in (true(),false()) return 'y')", "ND-Root", - "'a' in (for indicator:$x in (TRUE, FALSE) return 'y')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromBooleanIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in PathNode/IndicatorField return 'y')", "ND-Root", - "'a' in (for indicator:$x in BT-00-Indicator return 'y')"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromNumericIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "'a' = (for $x in (1,2,3) return 'y')", - "ND-Root", "'a' in (for number:$x in (1, 2, 3) return 'y')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromNumericIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in PathNode/NumberField return 'y')", "ND-Root", - "'a' in (for number:$x in BT-00-Number return 'y')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromDateIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 'y')", - "ND-Root", "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromDateIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in PathNode/StartDateField return 'y')", "ND-Root", - "'a' in (for date:$x in BT-00-StartDate return 'y')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromTimeIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 'y')", - "ND-Root", "'a' in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 'y')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromTimeIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in PathNode/StartTimeField return 'y')", "ND-Root", - "'a' in (for time:$x in BT-00-StartTime return 'y')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromDurationIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 'y')", - "ND-Root", "'a' in (for measure:$x in (P1D, P1Y, P2M) return 'y')"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringsFromDurationIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "'a' = (for $x in PathNode/MeasureField return 'y')", "ND-Root", - "'a' in (for measure:$x in BT-00-Measure return 'y')"); - } - - // Numbers from iteration --------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromStringIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in ('a','b','c') return number($x))", "ND-Root", - "123 in (for text:$x in ('a', 'b', 'c') return number($x))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromStringIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in PathNode/TextField return number($x))", "ND-Root", - "123 in (for text:$x in BT-00-Text return number($x))"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromBooleanIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "123 = (for $x in (true(),false()) return 0)", - "ND-Root", "123 in (for indicator:$x in (TRUE, FALSE) return 0)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromBooleanIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in PathNode/IndicatorField return 0)", "ND-Root", - "123 in (for indicator:$x in BT-00-Indicator return 0)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromNumericIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "123 = (for $x in (1,2,3) return 0)", - "ND-Root", "123 in (for number:$x in (1, 2, 3) return 0)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromNumericIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in PathNode/NumberField return 0)", "ND-Root", - "123 in (for number:$x in BT-00-Number return 0)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromDateIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 0)", - "ND-Root", "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromDateIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in PathNode/StartDateField return 0)", "ND-Root", - "123 in (for date:$x in BT-00-StartDate return 0)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromTimeIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 0)", - "ND-Root", "123 in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 0)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromTimeIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in PathNode/StartTimeField return 0)", "ND-Root", - "123 in (for time:$x in BT-00-StartTime return 0)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromDurationIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 0)", - "ND-Root", "123 in (for measure:$x in (P1D, P1Y, P2M) return 0)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumbersFromDurationIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "123 = (for $x in PathNode/MeasureField return 0)", "ND-Root", - "123 in (for measure:$x in BT-00-Measure return 0)"); - } - - // Dates from iteration --------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromStringIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in ('a','b','c') return xs:date($x))", "ND-Root", - "2022-01-01Z in (for text:$x in ('a', 'b', 'c') return date($x))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromStringIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", - "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromBooleanIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in (true(),false()) return xs:date('2022-01-01Z'))", - "ND-Root", "2022-01-01Z in (for indicator:$x in (TRUE, FALSE) return 2022-01-01Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromBooleanIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in PathNode/IndicatorField return xs:date('2022-01-01Z'))", - "ND-Root", "2022-01-01Z in (for indicator:$x in BT-00-Indicator return 2022-01-01Z)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromNumericIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in (1,2,3) return xs:date('2022-01-01Z'))", "ND-Root", - "2022-01-01Z in (for number:$x in (1, 2, 3) return 2022-01-01Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromNumericIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", - "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromDateIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:date('2022-01-01Z'))", - "ND-Root", - "2022-01-01Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 2022-01-01Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromDateIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", - "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromTimeIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:date('2022-01-01Z'))", - "ND-Root", - "2022-01-01Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 2022-01-01Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromTimeIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", - "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromDurationIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:date('2022-01-01Z'))", - "ND-Root", "2022-01-01Z in (for measure:$x in (P1D, P1Y, P2M) return 2022-01-01Z)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDatesFromDurationIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", - "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); - } - - // Times from iteration --------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromStringIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in ('a','b','c') return xs:time($x))", "ND-Root", - "12:00:00Z in (for text:$x in ('a', 'b', 'c') return time($x))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromStringIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", - "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromBooleanIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in (true(),false()) return xs:time('12:00:00Z'))", - "ND-Root", "12:00:00Z in (for indicator:$x in (TRUE, FALSE) return 12:00:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromBooleanIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in PathNode/IndicatorField return xs:time('12:00:00Z'))", - "ND-Root", "12:00:00Z in (for indicator:$x in BT-00-Indicator return 12:00:00Z)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromNumericIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in (1,2,3) return xs:time('12:00:00Z'))", "ND-Root", - "12:00:00Z in (for number:$x in (1, 2, 3) return 12:00:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromNumericIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", - "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromDateIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:time('12:00:00Z'))", - "ND-Root", - "12:00:00Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 12:00:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromDateIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", - "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromTimeIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:time('12:00:00Z'))", - "ND-Root", - "12:00:00Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 12:00:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromTimeIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", - "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromDurationIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:time('12:00:00Z'))", - "ND-Root", "12:00:00Z in (for measure:$x in (P1D, P1Y, P2M) return 12:00:00Z)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimesFromDurationIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", - "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); - } - - // Durations from iteration --------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromStringIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')) return $x)", - "ND-Root", "P1D in (for measure:$x in (P1D, P2D, P1W) return $x)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromStringIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", - "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromBooleanIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in (true(),false()) return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for indicator:$x in (TRUE, FALSE) return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromBooleanIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in PathNode/IndicatorField return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for indicator:$x in BT-00-Indicator return P1D)"); - } - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromNumericIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in (1,2,3) return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for number:$x in (1, 2, 3) return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromNumericIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromDateIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromDateIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromTimeIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromTimeIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromDurationIteration_UsingLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for measure:$x in (P1D, P1Y, P2M) return P1D)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationsFromDurationIteration_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", - "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); - } - - // #endregion: Iteration expressions - - // #region: Numeric expressions --------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testMultiplicationExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "3 * 4", "BT-00-Text", "3 * 4"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testAdditionExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "4 + 4", "BT-00-Text", "4 + 4"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParenthesizedNumericExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "(2 + 2) * 4", "BT-00-Text", "(2 + 2)*4"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumericLiteralExpression(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "3.1415", "BT-00-Text", "3.1415"); - } - - // #endregion: Numeric expressions - - // #region: Lists ----------------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringList(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "'a' = ('a','b','c')", "BT-00-Text", - "'a' in ('a', 'b', 'c')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumericList_UsingNumericLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "4 = (1,2,3)", "BT-00-Text", "4 in (1, 2, 3)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumericList_UsingNumericField(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "4 = (1,../NumberField/number(),3)", - "BT-00-Text", "4 in (1, BT-00-Number, 3)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumericList_UsingTextField(final String sdkVersion) { - assertThrows(ParseCancellationException.class, - () -> translateExpressionWithContext(sdkVersion, "BT-00-Text", "4 in (1, BT-00-Text, 3)")); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testBooleanList(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "false() = (true(),PathNode/IndicatorField,true())", "ND-Root", - "NEVER in (TRUE, BT-00-Indicator, ALWAYS)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateList(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date('2022-01-01Z') = (xs:date('2022-01-02Z'),PathNode/StartDateField/xs:date(text()),xs:date('2022-02-02Z'))", - "ND-Root", "2022-01-01Z in (2022-01-02Z, BT-00-StartDate, 2022-02-02Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimeList(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time('12:20:21Z') = (xs:time('12:30:00Z'),PathNode/StartTimeField/xs:time(text()),xs:time('13:40:00Z'))", - "ND-Root", "12:20:21Z in (12:30:00Z, BT-00-StartTime, 13:40:00Z)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationList_UsingDurationLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:yearMonthDuration('P3M') = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", - "BT-00-Text", "P3M in (P1M, P3M, P6M)"); - } - - - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDurationList_UsingDurationField(final String sdkVersion) { - assertEquals( - "(for $F in ../MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", - translateExpressionWithContext(sdkVersion, "BT-00-Text", - "BT-00-Measure in (P1M, P3M, P6M)")); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testCodeList(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "'a' = ('code1','code2','code3')", - "BT-00-Text", "'a' in codelist:accessibility"); - } - - // #endregion: Lists - - // #region: References ------------------------------------------------------ - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldAttributeValueReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "PathNode/TextField/@Attribute = 'text'", - "ND-Root", "BT-00-Attribute == 'text'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldAttributeValueReference_SameElementContext(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "@Attribute = 'text'", "BT-00-Text", - "BT-00-Attribute == 'text'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testScalarFromAttributeReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "PathNode/CodeField/@listName", "ND-Root", - "BT-00-Code/@listName"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testScalarFromAttributeReference_SameElementContext(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "./@listName", "BT-00-Code", - "BT-00-Code/@listName"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReferenceWithPredicate(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "PathNode/IndicatorField['a' = 'a']", - "ND-Root", "BT-00-Indicator['a' == 'a']"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReferenceWithPredicate_WithFieldReferenceInPredicate(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/IndicatorField[../CodeField/normalize-space(text()) = 'a']", "ND-Root", - "BT-00-Indicator[BT-00-Code == 'a']"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReferenceInOtherNotice(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "fn:doc(concat($urlPrefix, 'da4d46e9-490b-41ff-a2ae-8166d356a619'))/*/PathNode/TextField/normalize-space(text())", - "ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReferenceWithFieldContextOverride(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "../TextField/normalize-space(text())", - "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReferenceWithFieldContextOverride_WithIntegerField(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "../IntegerField/number()", "BT-00-Code", - "BT-01-SubLevel-Text::integer"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReferenceWithNodeContextOverride(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "../../PathNode/IntegerField/number()", - "BT-00-Text", "ND-Root::integer"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReferenceWithNodeContextOverride_WithPredicate(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "../../PathNode/IntegerField/number()", - "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::integer"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testAbsoluteFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "/*/PathNode/IndicatorField", "BT-00-Text", - "/BT-00-Indicator"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSimpleFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "../IndicatorField", "BT-00-Text", - "BT-00-Indicator"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReference_ForDurationFields(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "(for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))", - "ND-Root", "BT-00-Measure"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFieldReference_WithAxis(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "./preceding::PathNode/IntegerField/number()", - "ND-Root", "ND-Root::preceding::integer"); - } - - // #endregion: References - - // #region: Boolean functions ----------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNotFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "not(true())", "BT-00-Text", "not(ALWAYS)"); - testExpressionTranslationWithContext(sdkVersion, "not(1 + 1 = 2)", "BT-00-Text", - "not(1 + 1 == 2)"); - assertThrows(ParseCancellationException.class, - () -> translateExpressionWithContext(sdkVersion, "BT-00-Text", "not('text')")); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testContainsFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "contains(PathNode/TextField/normalize-space(text()), 'xyz')", "ND-Root", - "contains(BT-00-Text, 'xyz')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStartsWithFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "starts-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", - "starts-with(BT-00-Text, 'abc')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testEndsWithFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "ends-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", - "ends-with(BT-00-Text, 'abc')"); - } - - // #endregion: Boolean functions - - // #region: Numeric functions ----------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testCountFunction_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "count(PathNode/TextField)", "ND-Root", - "count(BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testCountFunction_UsingSequenceFromIteration(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", - "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testNumberFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "number(PathNode/TextField/normalize-space(text()))", "ND-Root", "number(BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSumFunction_UsingFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "sum(PathNode/NumberField)", "ND-Root", - "sum(BT-00-Number)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSumFunction_UsingNumericSequenceFromIteration(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "sum(for $v in PathNode/NumberField return $v + 1)", "ND-Root", - "sum(for number:$v in BT-00-Number return $v +1)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringLengthFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "string-length(PathNode/TextField/normalize-space(text()))", "ND-Root", - "string-length(BT-00-Text)"); - } - - // #endregion: Numeric functions - - // #region: String functions ------------------------------------------------ - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSubstringFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "substring(PathNode/TextField/normalize-space(text()), 1, 3)", "ND-Root", - "substring(BT-00-Text, 1, 3)"); - testExpressionTranslationWithContext(sdkVersion, - "substring(PathNode/TextField/normalize-space(text()), 4)", "ND-Root", - "substring(BT-00-Text, 4)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testToStringFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "format-number(123, '0.##########')", - "ND-Root", "string(123)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testConcatFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "concat('abc', 'def')", "ND-Root", - "concat('abc', 'def')"); - }; - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringJoinFunction_withLiterals(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "string-join(('abc','def'), ',')", "ND-Root", - "string-join(('abc', 'def'), ',')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testStringJoinFunction_withFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "string-join(PathNode/TextField, ',')", - "ND-Root", "string-join(BT-00-Text, ',')"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testFormatNumberFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "format-number(PathNode/NumberField/number(), '#,##0.00')", "ND-Root", - "format-number(BT-00-Number, '#,##0.00')"); - } - - // #endregion: String functions - - // #region: Date functions -------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDateFromStringFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:date(PathNode/TextField/normalize-space(text()))", "ND-Root", "date(BT-00-Text)"); - } - - // #endregion: Date functions - - // #region: Time functions -------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testTimeFromStringFunction(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "xs:time(PathNode/TextField/normalize-space(text()))", "ND-Root", "time(BT-00-Text)"); - } - - // #endregion: Time functions - - // #region: Sequence Functions ---------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDistinctValuesFunction_WithStringSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "distinct-values(('one','two','one'))", - "ND-Root", "distinct-values(('one', 'two', 'one'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDistinctValuesFunction_WithNumberSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "distinct-values((1,2,3,2,3,4))", "ND-Root", - "distinct-values((1, 2, 3, 2, 3, 4))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDistinctValuesFunction_WithDateSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", - "ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDistinctValuesFunction_WithTimeSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", - "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDistinctValuesFunction_WithBooleanSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((true(),false(),false(),false()))", "ND-Root", - "distinct-values((TRUE, FALSE, FALSE, NEVER))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testDistinctValuesFunction_WithFieldReferences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "distinct-values(PathNode/TextField)", - "ND-Root", "distinct-values(BT-00-Text)"); - } - - // #region: Union - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUnionFunction_WithStringSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((('one','two'), ('two','three','four')))", "ND-Root", - "value-union(('one', 'two'), ('two', 'three', 'four'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUnionFunction_WithNumberSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "distinct-values(((1,2,3), (2,3,4)))", - "ND-Root", "value-union((1, 2, 3), (2, 3, 4))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUnionFunction_WithDateSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", - "ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUnionFunction_WithTimeSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", - "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUnionFunction_WithBooleanSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values(((true(),false()), (false(),false())))", "ND-Root", - "value-union((TRUE, FALSE), (FALSE, NEVER))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUnionFunction_WithFieldReferences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", - "value-union(BT-00-Text, BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testUnionFunction_WithTypeMismatch(final String sdkVersion) { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, - "ND-Root", "value-union(BT-00-Text, BT-00-Number)")); - } - - // #endregion: Union - - // #region: Intersect - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIntersectFunction_WithStringSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", - "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIntersectFunction_WithNumberSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "distinct-values((1,2,3)[.= (2,3,4)])", - "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIntersectFunction_WithDateSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", - "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIntersectFunction_WithTimeSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", - "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIntersectFunction_WithBooleanSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((true(),false())[.= (false(),false())])", "ND-Root", - "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIntersectFunction_WithFieldReferences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", - "value-intersect(BT-00-Text, BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIntersectFunction_WithTypeMismatch(final String sdkVersion) { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, - "ND-Root", "value-intersect(BT-00-Text, BT-00-Number)")); - } - - // #endregion: Intersect - - // #region: Except - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testExceptFunction_WithStringSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", - "value-except(('one', 'two'), ('two', 'three', 'four'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testExceptFunction_WithNumberSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "distinct-values((1,2,3)[not(. = (2,3,4))])", - "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testExceptFunction_WithDateSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", - "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testExceptFunction_WithTimeSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", - "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testExceptFunction_WithBooleanSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", - "value-except((TRUE, FALSE), (FALSE, NEVER))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testExceptFunction_WithFieldReferences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", - "value-except(BT-00-Text, BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testExceptFunction_WithTypeMismatch(final String sdkVersion) { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext(sdkVersion, - "ND-Root", "value-except(BT-00-Text, BT-00-Number)")); - } - - // #endregion: Except - - // #region: Compare sequences - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSequenceEqualFunction_WithStringSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "deep-equal(sort(('one','two')), sort(('two','three','four')))", "ND-Root", - "sequence-equal(('one', 'two'), ('two', 'three', 'four'))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSequenceEqualFunction_WithNumberSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "deep-equal(sort((1,2,3)), sort((2,3,4)))", - "ND-Root", "sequence-equal((1, 2, 3), (2, 3, 4))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSequenceEqualFunction_WithDateSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", - "ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSequenceEqualFunction_WithTimeSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", - "ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSequenceEqualFunction_WithBooleanSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "deep-equal(sort((true(),false())), sort((false(),false())))", "ND-Root", - "sequence-equal((TRUE, FALSE), (FALSE, NEVER))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSequenceEqualFunction_WithDurationSequences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", - "ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testSequenceEqualFunction_WithFieldReferences(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", - "sequence-equal(BT-00-Text, BT-00-Text)"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParameterizedExpression_WithStringParameter(final String sdkVersion) { - testExpressionTranslation(sdkVersion, "'hello' = 'world'", - "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "'hello'", "'world'"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParameterizedExpression_WithUnquotedStringParameter(final String sdkVersion) { - assertThrows(ParseCancellationException.class, () -> translateExpression(sdkVersion, - "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParameterizedExpression_WithNumberParameter(final String sdkVersion) { - testExpressionTranslation(sdkVersion, "1 = 2", - "{ND-Root, number:$p1, number:$p2} ${$p1 == $p2}", "1", "2"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParameterizedExpression_WithDateParameter(final String sdkVersion) { - testExpressionTranslation(sdkVersion, "xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", - "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParameterizedExpression_WithTimeParameter(final String sdkVersion) { - testExpressionTranslation(sdkVersion, "xs:time('12:00:00Z') = xs:time('13:00:00Z')", - "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParameterizedExpression_WithBooleanParameter(final String sdkVersion) { - testExpressionTranslation(sdkVersion, "true() = false()", - "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testParameterizedExpression_WithDurationParameter(final String sdkVersion) { - testExpressionTranslation(sdkVersion, - "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", - "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); - } - - // #endregion: Compare sequences - - // #endregion Sequence Functions - - // #region: Indexers -------------------------------------------------------- - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIndexer_WithFieldReference(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "PathNode/TextField[1]", "ND-Root", - "text:BT-00-Text[1]"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIndexer_WithFieldReferenceAndPredicate(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, - "PathNode/TextField[./normalize-space(text()) = 'hello'][1]", "ND-Root", - "text:BT-00-Text[BT-00-Text == 'hello'][1]"); - } - - @ParameterizedTest - @MethodSource("provideSdkVersions") - void testIndexer_WithTextSequence(final String sdkVersion) { - testExpressionTranslationWithContext(sdkVersion, "('a','b','c')[1]", "ND-Root", - "('a', 'b','c')[1]"); - } - - // #endregion: Indexers -} diff --git a/src/test/java/eu/europa/ted/efx/EfxTestsBase.java b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java index 78b80e6c..992c8fec 100644 --- a/src/test/java/eu/europa/ted/efx/EfxTestsBase.java +++ b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java @@ -1,48 +1,44 @@ package eu.europa.ted.efx; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; -import org.junit.jupiter.params.provider.Arguments; import eu.europa.ted.efx.mock.DependencyFactoryMock; -public class EfxTestsBase { - private static final String[] SDK_VERSIONS = new String[] {"eforms-sdk-1.0", "eforms-sdk-2.0"}; +public abstract class EfxTestsBase { + protected abstract String getSdkVersion(); - protected static Stream provideSdkVersions() { - List arguments = new ArrayList<>(); - - for (String sdkVersion : SDK_VERSIONS) { - arguments.add(Arguments.of(sdkVersion)); - } - - return Stream.of(arguments.toArray(new Arguments[0])); + protected void testExpressionTranslationWithContext(final String expectedTranslation, + final String context, final String expression) { + assertEquals(expectedTranslation, translateExpressionWithContext(context, expression)); } - protected void testExpressionTranslationWithContext(final String sdkVersion, - final String expectedTranslation, final String context, final String expression) { - assertEquals(expectedTranslation, - translateExpressionWithContext(sdkVersion, context, expression)); + protected void testExpressionTranslation(final String expectedTranslation, + final String expression, final String... params) { + assertEquals(expectedTranslation, translateExpression(expression, params)); } - protected void testExpressionTranslation(final String sdkVersion, - final String expectedTranslation, final String expression, final String... params) { - assertEquals(expectedTranslation, translateExpression(sdkVersion, expression, params)); + protected String translateExpressionWithContext(final String context, final String expression) { + return translateExpression(String.format("{%s} ${%s}", context, expression)); } - protected String translateExpressionWithContext(final String sdkVersion, final String context, - final String expression) { - return translateExpression(sdkVersion, String.format("{%s} ${%s}", context, expression)); + protected String translateExpression(final String expression, final String... params) { + try { + return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, getSdkVersion(), + expression, params); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } } - protected String translateExpression(final String sdkVersion, final String expression, - final String... params) { + protected String translateTemplate(final String template) { try { - return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, sdkVersion, - expression, params); + return EfxTranslator.translateTemplate(DependencyFactoryMock.INSTANCE, getSdkVersion(), + template + "\n"); } catch (InstantiationException e) { throw new RuntimeException(e); } } + + protected String lines(String... lines) { + return String.join("\n", lines); + } } diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java new file mode 100644 index 00000000..5434aadb --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java @@ -0,0 +1,46 @@ +package eu.europa.ted.efx.sdk1; + +import org.junit.jupiter.api.Test; +import eu.europa.ted.efx.EfxTestsBase; + +/** + * Test for EFX expressions that combine several aspects of the language. + */ +class EfxExpressionCombinedV1Test extends EfxTestsBase { + @Override + protected String getSdkVersion() { + return "eforms-sdk-1.0"; + } + + @Test + void testNotAnd() { + testExpressionTranslationWithContext("not(1 = 2) and (2 = 2)", "BT-00-Text", + "not(1 == 2) and (2 == 2)"); + } + + @Test + void testNotPresentAndNotPresent() { + testExpressionTranslationWithContext("not(PathNode/TextField) and not(PathNode/IntegerField)", + "ND-Root", "BT-00-Text is not present and BT-00-Integer is not present"); + } + + @Test + void testCountWithNodeContextOverride() { + testExpressionTranslationWithContext( + "count(../../PathNode/CodeField/normalize-space(text())) = 1", "BT-00-Text", + "count(ND-Root::BT-00-Code) == 1"); + } + + @Test + void testCountWithAbsoluteFieldReference() { + testExpressionTranslationWithContext("count(/*/PathNode/CodeField/normalize-space(text())) = 1", + "BT-00-Text", "count(/BT-00-Code) == 1"); + } + + @Test + void testCountWithAbsoluteFieldReferenceAndPredicate() { + testExpressionTranslationWithContext( + "count(/*/PathNode/CodeField[../IndicatorField = true()]/normalize-space(text())) = 1", + "BT-00-Text", "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); + } +} diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java new file mode 100644 index 00000000..78c7fe38 --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -0,0 +1,1606 @@ +package eu.europa.ted.efx.sdk1; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.junit.jupiter.api.Test; +import eu.europa.ted.efx.EfxTestsBase; + +class EfxExpressionTranslatorV1Test extends EfxTestsBase { + @Override + protected String getSdkVersion() { + return "eforms-sdk-1.0"; + } + + /*** Boolean expressions ***/ + + @Test + void testParenthesizedBooleanExpression() { + testExpressionTranslationWithContext("(true() or true()) and false()", "BT-00-Text", + "(ALWAYS or TRUE) and NEVER"); + } + + @Test + void testLogicalOrCondition() { + testExpressionTranslationWithContext("true() or false()", "BT-00-Text", "ALWAYS or NEVER"); + } + + @Test + void testLogicalAndCondition() { + testExpressionTranslationWithContext("true() and 1 + 1 = 2", "BT-00-Text", + "ALWAYS and 1 + 1 == 2"); + } + + @Test + void testInListCondition() { + testExpressionTranslationWithContext("not('x' = ('a','b','c'))", "BT-00-Text", + "'x' not in ('a', 'b', 'c')"); + } + + @Test + void testEmptinessCondition() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = ''", + "ND-Root", "BT-00-Text is empty"); + } + + @Test + void testEmptinessCondition_WithNot() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) != ''", + "ND-Root", "BT-00-Text is not empty"); + } + + @Test + void testPresenceCondition() { + testExpressionTranslationWithContext("PathNode/TextField", "ND-Root", "BT-00-Text is present"); + } + + @Test + void testPresenceCondition_WithNot() { + testExpressionTranslationWithContext("not(PathNode/TextField)", "ND-Root", + "BT-00-Text is not present"); + } + + @Test + void testUniqueValueCondition() { + testExpressionTranslationWithContext( + "count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", + "ND-Root", "BT-00-Text is unique in /BT-00-Text"); + } + + @Test + void testUniqueValueCondition_WithNot() { + testExpressionTranslationWithContext( + "not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", + "ND-Root", "BT-00-Text is not unique in /BT-00-Text"); + } + + + @Test + void testLikePatternCondition() { + testExpressionTranslationWithContext("fn:matches(normalize-space('123'), '[0-9]*')", + "BT-00-Text", "'123' like '[0-9]*'"); + } + + @Test + void testLikePatternCondition_WithNot() { + testExpressionTranslationWithContext("not(fn:matches(normalize-space('123'), '[0-9]*'))", + "BT-00-Text", "'123' not like '[0-9]*'"); + } + + @Test + void testFieldValueComparison_UsingTextFields() { + testExpressionTranslationWithContext( + "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", + "ND-Root", "BT-00-Text == BT-00-Text-Multilingual"); + } + + @Test + void testFieldValueComparison_UsingNumericFields() { + testExpressionTranslationWithContext( + "PathNode/NumberField/number() <= PathNode/IntegerField/number()", "ND-Root", + "BT-00-Number <= BT-00-Integer"); + } + + @Test + void testFieldValueComparison_UsingIndicatorFields() { + testExpressionTranslationWithContext("PathNode/IndicatorField != PathNode/IndicatorField", + "ND-Root", "BT-00-Indicator != BT-00-Indicator"); + } + + @Test + void testFieldValueComparison_UsingDateFields() { + testExpressionTranslationWithContext( + "PathNode/StartDateField/xs:date(text()) <= PathNode/EndDateField/xs:date(text())", + "ND-Root", "BT-00-StartDate <= BT-00-EndDate"); + } + + @Test + void testFieldValueComparison_UsingTimeFields() { + testExpressionTranslationWithContext( + "PathNode/StartTimeField/xs:time(text()) <= PathNode/EndTimeField/xs:time(text())", + "ND-Root", "BT-00-StartTime <= BT-00-EndTime"); + } + + @Test + void testFieldValueComparison_UsingMeasureFields() { + testExpressionTranslationWithContext( + "boolean(for $T in (current-date()) return ($T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) <= $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", + "ND-Root", "BT-00-Measure <= BT-00-Measure"); + } + + @Test + void testFieldValueComparison_WithStringLiteral() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = 'abc'", + "ND-Root", "BT-00-Text == 'abc'"); + } + + @Test + void testFieldValueComparison_WithNumericLiteral() { + testExpressionTranslationWithContext( + "PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", "ND-Root", + "BT-00-Integer - BT-00-Number > 0"); + } + + @Test + void testFieldValueComparison_WithDateLiteral() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') > PathNode/StartDateField/xs:date(text())", "ND-Root", + "2022-01-01Z > BT-00-StartDate"); + } + + @Test + void testFieldValueComparison_WithTimeLiteral() { + testExpressionTranslationWithContext( + "xs:time('00:01:00Z') > PathNode/EndTimeField/xs:time(text())", "ND-Root", + "00:01:00Z > BT-00-EndTime"); + } + + @Test + void testFieldValueComparison_TypeMismatch() { + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", "00:01:00 > BT-00-StartDate")); + } + + + @Test + void testBooleanComparison_UsingLiterals() { + testExpressionTranslationWithContext("false() != true()", "BT-00-Text", "NEVER != ALWAYS"); + } + + @Test + void testBooleanComparison_UsingFieldReference() { + testExpressionTranslationWithContext("../IndicatorField != true()", "BT-00-Text", + "BT-00-Indicator != ALWAYS"); + } + + @Test + void testNumericComparison() { + testExpressionTranslationWithContext( + "2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", + "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > BT-00-Integer"); + } + + @Test + void testStringComparison() { + testExpressionTranslationWithContext("'aaa' < 'bbb'", "BT-00-Text", "'aaa' < 'bbb'"); + } + + @Test + void testDateComparison_OfTwoDateLiterals() { + testExpressionTranslationWithContext("xs:date('2018-01-01Z') > xs:date('2018-01-01Z')", + "BT-00-Text", "2018-01-01Z > 2018-01-01Z"); + } + + @Test + void testDateComparison_OfTwoDateReferences() { + testExpressionTranslationWithContext( + "PathNode/StartDateField/xs:date(text()) = PathNode/EndDateField/xs:date(text())", + "ND-Root", "BT-00-StartDate == BT-00-EndDate"); + } + + @Test + void testDateComparison_OfDateReferenceAndDateFunction() { + testExpressionTranslationWithContext( + "PathNode/StartDateField/xs:date(text()) = xs:date(PathNode/TextField/normalize-space(text()))", + "ND-Root", "BT-00-StartDate == date(BT-00-Text)"); + } + + @Test + void testTimeComparison_OfTwoTimeLiterals() { + testExpressionTranslationWithContext("xs:time('13:00:10Z') > xs:time('21:20:30Z')", + "BT-00-Text", "13:00:10Z > 21:20:30Z"); + } + + @Test + void testZonedTimeComparison_OfTwoTimeLiterals() { + testExpressionTranslationWithContext("xs:time('13:00:10+01:00') > xs:time('21:20:30+02:00')", + "BT-00-Text", "13:00:10+01:00 > 21:20:30+02:00"); + } + + @Test + void testTimeComparison_OfTwoTimeReferences() { + testExpressionTranslationWithContext( + "PathNode/StartTimeField/xs:time(text()) = PathNode/EndTimeField/xs:time(text())", + "ND-Root", "BT-00-StartTime == BT-00-EndTime"); + } + + @Test + void testTimeComparison_OfTimeReferenceAndTimeFunction() { + testExpressionTranslationWithContext( + "PathNode/StartTimeField/xs:time(text()) = xs:time(PathNode/TextField/normalize-space(text()))", + "ND-Root", "BT-00-StartTime == time(BT-00-Text)"); + } + + @Test + void testDurationComparison_UsingYearMOnthDurationLiterals() { + testExpressionTranslationWithContext( + "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P12M')))", + "BT-00-Text", "P1Y == P12M"); + } + + @Test + void testDurationComparison_UsingDayTimeDurationLiterals() { + testExpressionTranslationWithContext( + "boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P21D') > $T + xs:dayTimeDuration('P7D')))", + "BT-00-Text", "P3W > P7D"); + } + + @Test + void testCalculatedDurationComparison() { + testExpressionTranslationWithContext( + "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P3M') > $T + xs:dayTimeDuration(PathNode/EndDateField/xs:date(text()) - PathNode/StartDateField/xs:date(text()))))", + "ND-Root", "P3M > (BT-00-EndDate - BT-00-StartDate)"); + } + + + @Test + void testNegativeDuration_Literal() { + testExpressionTranslationWithContext("xs:yearMonthDuration('-P3M')", "ND-Root", "-P3M"); + } + + @Test + void testNegativeDuration_ViaMultiplication() { + testExpressionTranslationWithContext("(-3 * (2 * xs:yearMonthDuration('-P3M')))", "ND-Root", + "2 * -P3M * -3"); + } + + @Test + void testNegativeDuration_ViaMultiplicationWithField() { + testExpressionTranslationWithContext( + "(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", + "ND-Root", "2 * measure:BT-00-Measure * -3"); + } + + @Test + void testDurationAddition() { + testExpressionTranslationWithContext( + "(xs:dayTimeDuration('P3D') + xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", + "ND-Root", "P3D + (BT-00-StartDate - BT-00-EndDate)"); + } + + @Test + void testDurationSubtraction() { + testExpressionTranslationWithContext( + "(xs:dayTimeDuration('P3D') - xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", + "ND-Root", "P3D - (BT-00-StartDate - BT-00-EndDate)"); + } + + @Test + void testBooleanLiteralExpression_Always() { + testExpressionTranslationWithContext("true()", "BT-00-Text", "ALWAYS"); + } + + @Test + void testBooleanLiteralExpression_Never() { + testExpressionTranslationWithContext("false()", "BT-00-Text", "NEVER"); + } + + /*** Quantified expressions ***/ + + @Test + void testStringQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in ('a','b','c') satisfies $x <= 'a'", "ND-Root", + "every text:$x in ('a', 'b', 'c') satisfies $x <= 'a'"); + } + + @Test + void testStringQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext( + "every $x in PathNode/TextField/normalize-space(text()) satisfies $x <= 'a'", "ND-Root", + "every text:$x in BT-00-Text satisfies $x <= 'a'"); + } + + @Test + void testBooleanQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in (true(),false(),true()) satisfies $x", + "ND-Root", "every indicator:$x in (TRUE, FALSE, ALWAYS) satisfies $x"); + } + + @Test + void testBooleanQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext("every $x in PathNode/IndicatorField satisfies $x", + "ND-Root", "every indicator:$x in BT-00-Indicator satisfies $x"); + } + + @Test + void testNumericQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in (1,2,3) satisfies $x <= 1", "ND-Root", + "every number:$x in (1, 2, 3) satisfies $x <= 1"); + } + + @Test + void testNumericQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext( + "every $x in PathNode/NumberField/number() satisfies $x <= 1", "ND-Root", + "every number:$x in BT-00-Number satisfies $x <= 1"); + } + + @Test + void testDateQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext( + "every $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) satisfies $x <= xs:date('2012-01-01Z')", + "ND-Root", + "every date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) satisfies $x <= 2012-01-01Z"); + } + + @Test + void testDateQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext( + "every $x in PathNode/StartDateField/xs:date(text()) satisfies $x <= xs:date('2012-01-01Z')", + "ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); + } + + @Test + void testDateQuantifiedExpression_UsingMultipleIterators() { + testExpressionTranslationWithContext( + "every $x in PathNode/StartDateField/xs:date(text()), $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", + "ND-Root", + "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); + } + + @Test + void testTimeQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext( + "every $x in (xs:time('00:00:00Z'),xs:time('00:00:01Z'),xs:time('00:00:02Z')) satisfies $x <= xs:time('00:00:00Z')", + "ND-Root", "every time:$x in (00:00:00Z, 00:00:01Z, 00:00:02Z) satisfies $x <= 00:00:00Z"); + } + + @Test + void testTimeQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext( + "every $x in PathNode/StartTimeField/xs:time(text()) satisfies $x <= xs:time('00:00:00Z')", + "ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); + } + + @Test + void testDurationQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext( + "every $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P3D')) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", + "ND-Root", "every measure:$x in (P1D, P2D, P3D) satisfies $x <= P1D"); + } + + @Test + void testDurationQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext( + "every $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", + "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); + } + + /*** Conditional expressions ***/ + + @Test + void testConditionalExpression() { + testExpressionTranslationWithContext("(if 1 > 2 then 'a' else 'b')", "ND-Root", + "if 1 > 2 then 'a' else 'b'"); + } + + @Test + void testConditionalStringExpression_UsingLiterals() { + testExpressionTranslationWithContext("(if 'a' > 'b' then 'a' else 'b')", "ND-Root", + "if 'a' > 'b' then 'a' else 'b'"); + } + + @Test + void testConditionalStringExpression_UsingFieldReferenceInCondition() { + testExpressionTranslationWithContext( + "(if 'a' > PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", + "if 'a' > BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext( + "(if PathNode/TextField/normalize-space(text()) >= 'a' then 'a' else 'b')", "ND-Root", + "if BT-00-Text >= 'a' then 'a' else 'b'"); + testExpressionTranslationWithContext( + "(if PathNode/TextField/normalize-space(text()) >= PathNode/TextField/normalize-space(text()) then 'a' else 'b')", + "ND-Root", "if BT-00-Text >= BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext( + "(if PathNode/StartDateField/xs:date(text()) >= PathNode/EndDateField/xs:date(text()) then 'a' else 'b')", + "ND-Root", "if BT-00-StartDate >= BT-00-EndDate then 'a' else 'b'"); + } + + @Test + void testConditionalStringExpression_UsingFieldReference() { + testExpressionTranslationWithContext( + "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else 'b')", "ND-Root", + "if 'a' > 'b' then BT-00-Text else 'b'"); + testExpressionTranslationWithContext( + "(if 'a' > 'b' then 'a' else PathNode/TextField/normalize-space(text()))", "ND-Root", + "if 'a' > 'b' then 'a' else BT-00-Text"); + testExpressionTranslationWithContext( + "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else PathNode/TextField/normalize-space(text()))", + "ND-Root", "if 'a' > 'b' then BT-00-Text else BT-00-Text"); + } + + @Test + void testConditionalStringExpression_UsingFieldReferences_TypeMismatch() { + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", + "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); + } + + @Test + void testConditionalBooleanExpression() { + testExpressionTranslationWithContext("(if PathNode/IndicatorField then true() else false())", + "ND-Root", "if BT-00-Indicator then TRUE else FALSE"); + } + + @Test + void testConditionalNumericExpression() { + testExpressionTranslationWithContext("(if 1 > 2 then 1 else PathNode/NumberField/number())", + "ND-Root", "if 1 > 2 then 1 else BT-00-Number"); + } + + @Test + void testConditionalDateExpression() { + testExpressionTranslationWithContext( + "(if xs:date('2012-01-01Z') > PathNode/EndDateField/xs:date(text()) then PathNode/StartDateField/xs:date(text()) else xs:date('2012-01-02Z'))", + "ND-Root", "if 2012-01-01Z > BT-00-EndDate then BT-00-StartDate else 2012-01-02Z"); + } + + @Test + void testConditionalTimeExpression() { + testExpressionTranslationWithContext( + "(if PathNode/EndTimeField/xs:time(text()) > xs:time('00:00:01Z') then PathNode/StartTimeField/xs:time(text()) else xs:time('00:00:01Z'))", + "ND-Root", "if BT-00-EndTime > 00:00:01Z then BT-00-StartTime else 00:00:01Z"); + } + + @Test + void testConditionalDurationExpression() { + testExpressionTranslationWithContext( + "(if boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P1D') > $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))) then xs:dayTimeDuration('P1D') else xs:dayTimeDuration('P2D'))", + "ND-Root", "if P1D > BT-00-Measure then P1D else P2D"); + } + + /*** Iteration expressions ***/ + + // Strings from iteration --------------------------------------------------- + + @Test + void testStringsFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "'a' = (for $x in ('a','b','c') return concat($x, 'text'))", "ND-Root", + "'a' in (for text:$x in ('a', 'b', 'c') return concat($x, 'text'))"); + } + + @Test + void testStringsSequenceFromIteration_UsingMultipleIterators() { + testExpressionTranslationWithContext( + "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0,##########'), 'text'))", + "ND-Root", + "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); + } + + @Test + void testStringsSequenceFromIteration_UsingObjectVariable() { + testExpressionTranslationWithContext( + "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField/xs:date(text()) return 'text'", + "ND-Root", + "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); + } + + @Test + void testStringsSequenceFromIteration_UsingNodeContextVariable() { + testExpressionTranslationWithContext( + "for $n in .[PathNode/TextField/normalize-space(text()) = 'a'] return 'text'", "ND-Root", + "for context:$n in ND-Root[BT-00-Text == 'a'] return 'text'"); + } + + @Test + void testStringsFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "'a' = (for $x in PathNode/TextField/normalize-space(text()) return concat($x, 'text'))", + "ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); + } + + + @Test + void testStringsFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in (true(),false()) return 'y')", "ND-Root", + "'a' in (for indicator:$x in (TRUE, FALSE) return 'y')"); + } + + @Test + void testStringsFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext("'a' = (for $x in PathNode/IndicatorField return 'y')", + "ND-Root", "'a' in (for indicator:$x in BT-00-Indicator return 'y')"); + } + + + @Test + void testStringsFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in (1,2,3) return 'y')", "ND-Root", + "'a' in (for number:$x in (1, 2, 3) return 'y')"); + } + + @Test + void testStringsFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "'a' = (for $x in PathNode/NumberField/number() return 'y')", "ND-Root", + "'a' in (for number:$x in BT-00-Number return 'y')"); + } + + @Test + void testStringsFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "'a' = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 'y')", + "ND-Root", "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')"); + } + + @Test + void testStringsFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "'a' = (for $x in PathNode/StartDateField/xs:date(text()) return 'y')", "ND-Root", + "'a' in (for date:$x in BT-00-StartDate return 'y')"); + } + + @Test + void testStringsFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "'a' = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 'y')", + "ND-Root", "'a' in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 'y')"); + } + + @Test + void testStringsFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "'a' = (for $x in PathNode/StartTimeField/xs:time(text()) return 'y')", "ND-Root", + "'a' in (for time:$x in BT-00-StartTime return 'y')"); + } + + @Test + void testStringsFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "'a' = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 'y')", + "ND-Root", "'a' in (for measure:$x in (P1D, P1Y, P2M) return 'y')"); + } + + + @Test + void testStringsFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "'a' = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 'y')", + "ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"); + } + + // Numbers from iteration --------------------------------------------------- + + @Test + void testNumbersFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in ('a','b','c') return number($x))", + "ND-Root", "123 in (for text:$x in ('a', 'b', 'c') return number($x))"); + } + + @Test + void testNumbersFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "123 = (for $x in PathNode/TextField/normalize-space(text()) return number($x))", "ND-Root", + "123 in (for text:$x in BT-00-Text return number($x))"); + } + + + @Test + void testNumbersFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in (true(),false()) return 0)", "ND-Root", + "123 in (for indicator:$x in (TRUE, FALSE) return 0)"); + } + + @Test + void testNumbersFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/IndicatorField return 0)", + "ND-Root", "123 in (for indicator:$x in BT-00-Indicator return 0)"); + } + + + @Test + void testNumbersFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in (1,2,3) return 0)", "ND-Root", + "123 in (for number:$x in (1, 2, 3) return 0)"); + } + + @Test + void testNumbersFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField/number() return 0)", + "ND-Root", "123 in (for number:$x in BT-00-Number return 0)"); + } + + @Test + void testNumbersFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "123 = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 0)", + "ND-Root", "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)"); + } + + @Test + void testNumbersFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "123 = (for $x in PathNode/StartDateField/xs:date(text()) return 0)", "ND-Root", + "123 in (for date:$x in BT-00-StartDate return 0)"); + } + + @Test + void testNumbersFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "123 = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 0)", + "ND-Root", "123 in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 0)"); + } + + @Test + void testNumbersFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "123 = (for $x in PathNode/StartTimeField/xs:time(text()) return 0)", "ND-Root", + "123 in (for time:$x in BT-00-StartTime return 0)"); + } + + @Test + void testNumbersFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "123 = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 0)", + "ND-Root", "123 in (for measure:$x in (P1D, P1Y, P2M) return 0)"); + } + + + @Test + void testNumbersFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "123 = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 0)", + "ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"); + } + + // Dates from iteration --------------------------------------------------- + + @Test + void testDatesFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in ('a','b','c') return xs:date($x))", "ND-Root", + "2022-01-01Z in (for text:$x in ('a', 'b', 'c') return date($x))"); + } + + @Test + void testDatesFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:date($x))", + "ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); + } + + + @Test + void testDatesFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (true(),false()) return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for indicator:$x in (TRUE, FALSE) return 2022-01-01Z)"); + } + + @Test + void testDatesFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/IndicatorField return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for indicator:$x in BT-00-Indicator return 2022-01-01Z)"); + } + + + @Test + void testDatesFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (1,2,3) return xs:date('2022-01-01Z'))", "ND-Root", + "2022-01-01Z in (for number:$x in (1, 2, 3) return 2022-01-01Z)"); + } + + @Test + void testDatesFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField/number() return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); + } + + @Test + void testDatesFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:date('2022-01-01Z'))", + "ND-Root", + "2022-01-01Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 2022-01-01Z)"); + } + + @Test + void testDatesFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); + } + + @Test + void testDatesFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:date('2022-01-01Z'))", + "ND-Root", + "2022-01-01Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 2022-01-01Z)"); + } + + @Test + void testDatesFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); + } + + @Test + void testDatesFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for measure:$x in (P1D, P1Y, P2M) return 2022-01-01Z)"); + } + + + @Test + void testDatesFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); + } + + // Times from iteration --------------------------------------------------- + + @Test + void testTimesFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in ('a','b','c') return xs:time($x))", "ND-Root", + "12:00:00Z in (for text:$x in ('a', 'b', 'c') return time($x))"); + } + + @Test + void testTimesFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:time($x))", + "ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); + } + + + @Test + void testTimesFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (true(),false()) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for indicator:$x in (TRUE, FALSE) return 12:00:00Z)"); + } + + @Test + void testTimesFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/IndicatorField return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for indicator:$x in BT-00-Indicator return 12:00:00Z)"); + } + + + @Test + void testTimesFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (1,2,3) return xs:time('12:00:00Z'))", "ND-Root", + "12:00:00Z in (for number:$x in (1, 2, 3) return 12:00:00Z)"); + } + + @Test + void testTimesFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/NumberField/number() return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); + } + + @Test + void testTimesFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:time('12:00:00Z'))", + "ND-Root", + "12:00:00Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 12:00:00Z)"); + } + + @Test + void testTimesFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); + } + + @Test + void testTimesFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:time('12:00:00Z'))", + "ND-Root", + "12:00:00Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 12:00:00Z)"); + } + + @Test + void testTimesFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); + } + + @Test + void testTimesFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for measure:$x in (P1D, P1Y, P2M) return 12:00:00Z)"); + } + + + @Test + void testTimesFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); + } + + // Durations from iteration --------------------------------------------------- + + @Test + void testDurationsFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')) return $x)", + "ND-Root", "P1D in (for measure:$x in (P1D, P2D, P1W) return $x)"); + } + + @Test + void testDurationsFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField/normalize-space(text()) return xs:dayTimeDuration($x))", + "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); + } + + + @Test + void testDurationsFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (true(),false()) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for indicator:$x in (TRUE, FALSE) return P1D)"); + } + + @Test + void testDurationsFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/IndicatorField return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for indicator:$x in BT-00-Indicator return P1D)"); + } + + + @Test + void testDurationsFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (1,2,3) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for number:$x in (1, 2, 3) return P1D)"); + } + + @Test + void testDurationsFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField/number() return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); + } + + @Test + void testDurationsFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)"); + } + + @Test + void testDurationsFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); + } + + @Test + void testDurationsFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return P1D)"); + } + + @Test + void testDurationsFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); + } + + @Test + void testDurationsFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for measure:$x in (P1D, P1Y, P2M) return P1D)"); + } + + @Test + void testDurationsFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); + } + + /*** Numeric expressions ***/ + + @Test + void testMultiplicationExpression() { + testExpressionTranslationWithContext("3 * 4", "BT-00-Text", "3 * 4"); + } + + @Test + void testAdditionExpression() { + testExpressionTranslationWithContext("4 + 4", "BT-00-Text", "4 + 4"); + } + + @Test + void testParenthesizedNumericExpression() { + testExpressionTranslationWithContext("(2 + 2) * 4", "BT-00-Text", "(2 + 2)*4"); + } + + @Test + void testNumericLiteralExpression() { + testExpressionTranslationWithContext("3.1415", "BT-00-Text", "3.1415"); + } + + /*** List ***/ + + @Test + void testStringList() { + testExpressionTranslationWithContext("'a' = ('a','b','c')", "BT-00-Text", + "'a' in ('a', 'b', 'c')"); + } + + @Test + void testNumericList_UsingNumericLiterals() { + testExpressionTranslationWithContext("4 = (1,2,3)", "BT-00-Text", "4 in (1, 2, 3)"); + } + + @Test + void testNumericList_UsingNumericField() { + testExpressionTranslationWithContext("4 = (1,../NumberField/number(),3)", "BT-00-Text", + "4 in (1, BT-00-Number, 3)"); + } + + @Test + void testNumericList_UsingTextField() { + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("BT-00-Text", "4 in (1, BT-00-Text, 3)")); + } + + @Test + void testBooleanList() { + testExpressionTranslationWithContext("false() = (true(),PathNode/IndicatorField,true())", + "ND-Root", "NEVER in (TRUE, BT-00-Indicator, ALWAYS)"); + } + + @Test + void testDateList() { + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (xs:date('2022-01-02Z'),PathNode/StartDateField/xs:date(text()),xs:date('2022-02-02Z'))", + "ND-Root", "2022-01-01Z in (2022-01-02Z, BT-00-StartDate, 2022-02-02Z)"); + } + + @Test + void testTimeList() { + testExpressionTranslationWithContext( + "xs:time('12:20:21Z') = (xs:time('12:30:00Z'),PathNode/StartTimeField/xs:time(text()),xs:time('13:40:00Z'))", + "ND-Root", "12:20:21Z in (12:30:00Z, BT-00-StartTime, 13:40:00Z)"); + } + + @Test + void testDurationList_UsingDurationLiterals() { + testExpressionTranslationWithContext( + "xs:yearMonthDuration('P3M') = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", + "BT-00-Text", "P3M in (P1M, P3M, P6M)"); + } + + @Test + void testDurationList_UsingDurationField() { + testExpressionTranslationWithContext( + "(for $F in ../MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", + "BT-00-Text", "BT-00-Measure in (P1M, P3M, P6M)"); + } + + @Test + void testCodeList() { + testExpressionTranslationWithContext("'a' = ('code1','code2','code3')", "BT-00-Text", + "'a' in (accessibility)"); + } + + /*** References ***/ + + @Test + void testFieldAttributeValueReference() { + testExpressionTranslationWithContext("PathNode/TextField/@Attribute = 'text'", "ND-Root", + "BT-00-Attribute == 'text'"); + } + + @Test + void testFieldAttributeValueReference_SameElementContext() { + testExpressionTranslationWithContext("@Attribute = 'text'", "BT-00-Text", + "BT-00-Attribute == 'text'"); + } + + @Test + void testScalarFromAttributeReference() { + testExpressionTranslationWithContext("PathNode/CodeField/@listName", "ND-Root", + "BT-00-Code/@listName"); + } + + @Test + void testScalarFromAttributeReference_SameElementContext() { + testExpressionTranslationWithContext("./@listName", "BT-00-Code", "BT-00-Code/@listName"); + } + + @Test + void testFieldReferenceWithPredicate() { + testExpressionTranslationWithContext("PathNode/IndicatorField['a' = 'a']", "ND-Root", + "BT-00-Indicator['a' == 'a']"); + } + + @Test + void testFieldReferenceWithPredicate_WithFieldReferenceInPredicate() { + testExpressionTranslationWithContext( + "PathNode/IndicatorField[../CodeField/normalize-space(text()) = 'a']", "ND-Root", + "BT-00-Indicator[BT-00-Code == 'a']"); + } + + @Test + void testFieldReferenceInOtherNotice() { + testExpressionTranslationWithContext( + "fn:doc(concat($urlPrefix, 'da4d46e9-490b-41ff-a2ae-8166d356a619'))/*/PathNode/TextField/normalize-space(text())", + "ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text"); + } + + @Test + void testFieldReferenceWithFieldContextOverride() { + testExpressionTranslationWithContext("../TextField/normalize-space(text())", "BT-00-Code", + "BT-01-SubLevel-Text::BT-00-Text"); + } + + @Test + void testFieldReferenceWithFieldContextOverride_WithIntegerField() { + testExpressionTranslationWithContext("../IntegerField/number()", "BT-00-Code", + "BT-01-SubLevel-Text::BT-00-Integer"); + } + + @Test + void testFieldReferenceWithNodeContextOverride() { + testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", + "ND-Root::BT-00-Integer"); + } + + @Test + void testFieldReferenceWithNodeContextOverride_WithPredicate() { + testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", + "ND-Root[BT-00-Indicator == TRUE]::BT-00-Integer"); + } + + @Test + void testAbsoluteFieldReference() { + testExpressionTranslationWithContext("/*/PathNode/IndicatorField", "BT-00-Text", + "/BT-00-Indicator"); + } + + @Test + void testSimpleFieldReference() { + testExpressionTranslationWithContext("../IndicatorField", "BT-00-Text", "BT-00-Indicator"); + } + + @Test + void testFieldReference_ForDurationFields() { + testExpressionTranslationWithContext( + "(for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))", + "ND-Root", "BT-00-Measure"); + } + + @Test + void testFieldReference_WithAxis() { + testExpressionTranslationWithContext("./preceding::PathNode/IntegerField/number()", "ND-Root", + "ND-Root::preceding::BT-00-Integer"); + } + + /*** Boolean functions ***/ + + @Test + void testNotFunction() { + testExpressionTranslationWithContext("not(true())", "BT-00-Text", "not(ALWAYS)"); + testExpressionTranslationWithContext("not(1 + 1 = 2)", "BT-00-Text", "not(1 + 1 == 2)"); + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("BT-00-Text", "not('text')")); + } + + @Test + void testContainsFunction() { + testExpressionTranslationWithContext( + "contains(PathNode/TextField/normalize-space(text()), 'xyz')", "ND-Root", + "contains(BT-00-Text, 'xyz')"); + } + + @Test + void testStartsWithFunction() { + testExpressionTranslationWithContext( + "starts-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", + "starts-with(BT-00-Text, 'abc')"); + } + + @Test + void testEndsWithFunction() { + testExpressionTranslationWithContext( + "ends-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", + "ends-with(BT-00-Text, 'abc')"); + } + + /*** Numeric functions ***/ + + @Test + void testCountFunction_UsingFieldReference() { + testExpressionTranslationWithContext("count(PathNode/TextField/normalize-space(text()))", + "ND-Root", "count(BT-00-Text)"); + } + + @Test + void testCountFunction_UsingSequenceFromIteration() { + testExpressionTranslationWithContext( + "count(for $x in PathNode/TextField/normalize-space(text()) return concat($x, '-xyz'))", + "ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); + } + + @Test + void testNumberFunction() { + testExpressionTranslationWithContext("number(PathNode/TextField/normalize-space(text()))", + "ND-Root", "number(BT-00-Text)"); + } + + @Test + void testSumFunction_UsingFieldReference() { + testExpressionTranslationWithContext("sum(PathNode/NumberField/number())", "ND-Root", + "sum(BT-00-Number)"); + } + + @Test + void testSumFunction_UsingNumericSequenceFromIteration() { + testExpressionTranslationWithContext( + "sum(for $v in PathNode/NumberField/number() return $v + 1)", "ND-Root", + "sum(for number:$v in BT-00-Number return $v +1)"); + } + + @Test + void testStringLengthFunction() { + testExpressionTranslationWithContext( + "string-length(PathNode/TextField/normalize-space(text()))", "ND-Root", + "string-length(BT-00-Text)"); + } + + /*** String functions ***/ + + @Test + void testSubstringFunction() { + testExpressionTranslationWithContext( + "substring(PathNode/TextField/normalize-space(text()), 1, 3)", "ND-Root", + "substring(BT-00-Text, 1, 3)"); + testExpressionTranslationWithContext("substring(PathNode/TextField/normalize-space(text()), 4)", + "ND-Root", "substring(BT-00-Text, 4)"); + } + + @Test + void testToStringFunction() { + testExpressionTranslationWithContext("format-number(123, '0,##########')", "ND-Root", + "string(123)"); + } + + @Test + void testConcatFunction() { + testExpressionTranslationWithContext("concat('abc', 'def')", "ND-Root", "concat('abc', 'def')"); + }; + + @Test + void testFormatNumberFunction() { + testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '# ##0,00')", + "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); + } + + + /*** Date functions ***/ + + @Test + void testDateFromStringFunction() { + testExpressionTranslationWithContext("xs:date(PathNode/TextField/normalize-space(text()))", + "ND-Root", "date(BT-00-Text)"); + } + + @Test + void testDatePlusMeasureFunction() { + testExpressionTranslationWithContext( + "(PathNode/StartDateField/xs:date(text()) + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))", + "ND-Root", "add-measure(BT-00-StartDate, BT-00-Measure)"); + } + + @Test + void testDateMinusMeasureFunction() { + testExpressionTranslationWithContext( + "(PathNode/StartDateField/xs:date(text()) - (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))", + "ND-Root", "subtract-measure(BT-00-StartDate, BT-00-Measure)"); + } + + /*** Time functions ***/ + + @Test + void testTimeFromStringFunction() { + testExpressionTranslationWithContext("xs:time(PathNode/TextField/normalize-space(text()))", + "ND-Root", "time(BT-00-Text)"); + } + + /*** Duration functions ***/ + + @Test + void testDayTimeDurationFromStringFunction() { + testExpressionTranslationWithContext( + "xs:yearMonthDuration(PathNode/TextField/normalize-space(text()))", "ND-Root", + "year-month-duration(BT-00-Text)"); + } + + @Test + void testYearMonthDurationFromStringFunction() { + testExpressionTranslationWithContext( + "xs:dayTimeDuration(PathNode/TextField/normalize-space(text()))", "ND-Root", + "day-time-duration(BT-00-Text)"); + } + + /*** Sequence Functions ***/ + + @Test + void testDistinctValuesFunction_WithStringSequences() { + testExpressionTranslationWithContext("distinct-values(('one','two','one'))", "ND-Root", + "distinct-values(('one', 'two', 'one'))"); + } + + @Test + void testDistinctValuesFunction_WithNumberSequences() { + testExpressionTranslationWithContext("distinct-values((1,2,3,2,3,4))", "ND-Root", + "distinct-values((1, 2, 3, 2, 3, 4))"); + } + + @Test + void testDistinctValuesFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", + "ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testDistinctValuesFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", + "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); + } + + @Test + void testDistinctValuesFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "distinct-values((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')))", + "ND-Root", "distinct-values((P1W, P2D, P2D, P5D))"); + } + + @Test + void testDistinctValuesFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("distinct-values((true(),false(),false(),false()))", + "ND-Root", "distinct-values((TRUE, FALSE, FALSE, NEVER))"); + } + + @Test + void testDistinctValuesFunction_WithFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(PathNode/TextField/normalize-space(text()))", "ND-Root", + "distinct-values(BT-00-Text)"); + } + + /* Union */ + + @Test + void testUnionFunction_WithStringSequences() { + testExpressionTranslationWithContext("distinct-values((('one','two'), ('two','three','four')))", + "ND-Root", "value-union(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testUnionFunction_WithNumberSequences() { + testExpressionTranslationWithContext("distinct-values(((1,2,3), (2,3,4)))", "ND-Root", + "value-union((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testUnionFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", + "ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testUnionFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", + "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testUnionFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "distinct-values(((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')), (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D'))))", + "ND-Root", "value-union((P1W, P2D), (P2D, P5D))"); + } + + @Test + void testUnionFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("distinct-values(((true(),false()), (false(),false())))", + "ND-Root", "value-union((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testUnionFunction_WithFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values((PathNode/TextField/normalize-space(text()), PathNode/TextField/normalize-space(text())))", + "ND-Root", "value-union(BT-00-Text, BT-00-Text)"); + } + + @Test + void testUnionFunction_WithTypeMismatch() { + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", "value-union(BT-00-Text, BT-00-Number)")); + } + + /* Intersect */ + + @Test + void testIntersectFunction_WithStringSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in ('one','two') return if (some $L2 in ('two','three','four') satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testIntersectFunction_WithNumberSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (1,2,3) return if (some $L2 in (2,3,4) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testIntersectFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (some $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testIntersectFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (some $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testIntersectFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (some $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect((P1W, P2D), (P2D, P5D))"); + } + + @Test + void testIntersectFunction_WithBooleanSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (true(),false()) return if (some $L2 in (false(),false()) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testIntersectFunction_WithFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (some $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect(BT-00-Text, BT-00-Text)"); + } + + @Test + void testIntersectFunction_WithTypeMismatch() { + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", + "value-intersect(BT-00-Text, BT-00-Number)")); + } + + /* Except */ + + @Test + void testExceptFunction_WithStringSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in ('one','two') return if (every $L2 in ('two','three','four') satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testExceptFunction_WithNumberSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (1,2,3) return if (every $L2 in (2,3,4) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testExceptFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (every $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testExceptFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (every $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testExceptFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (every $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except((P1W, P2D), (P2D, P5D))"); + } + + @Test + void testExceptFunction_WithBooleanSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (true(),false()) return if (every $L2 in (false(),false()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testExceptFunction_WithTextFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Text, BT-00-Text)"); + } + + @Test + void testExceptFunction_WithNumberFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in PathNode/IntegerField/number() return if (every $L2 in PathNode/IntegerField/number() satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Integer, BT-00-Integer)"); + } + + @Test + void testExceptFunction_WithBooleanFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in PathNode/IndicatorField return if (every $L2 in PathNode/IndicatorField satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Indicator, BT-00-Indicator)"); + } + + @Test + void testExceptFunction_WithDateFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in PathNode/StartDateField/xs:date(text()) return if (every $L2 in PathNode/StartDateField/xs:date(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-StartDate, BT-00-StartDate)"); + } + + @Test + void testExceptFunction_WithTimeFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in PathNode/StartTimeField/xs:time(text()) return if (every $L2 in PathNode/StartTimeField/xs:time(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-StartTime, BT-00-StartTime)"); + } + + @Test + void testExceptFunction_WithDurationFieldReferences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return if (every $L2 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Measure, BT-00-Measure)"); + } + + @Test + void testExceptFunction_WithTypeMismatch() { + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", "value-except(BT-00-Text, BT-00-Number)")); + } + + /* Compare sequences */ + + @Test + void testSequenceEqualFunction_WithStringSequences() { + testExpressionTranslationWithContext( + "deep-equal(sort(('one','two')), sort(('two','three','four')))", "ND-Root", + "sequence-equal(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testSequenceEqualFunction_WithNumberSequences() { + testExpressionTranslationWithContext("deep-equal(sort((1,2,3)), sort((2,3,4)))", "ND-Root", + "sequence-equal((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testSequenceEqualFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", + "ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testSequenceEqualFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", + "ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testSequenceEqualFunction_WithBooleanSequences() { + testExpressionTranslationWithContext( + "deep-equal(sort((true(),false())), sort((false(),false())))", "ND-Root", + "sequence-equal((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testSequenceEqualFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", + "ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))"); + } + + @Test + void testSequenceEqualFunction_WithFieldReferences() { + testExpressionTranslationWithContext( + "deep-equal(sort(PathNode/TextField/normalize-space(text())), sort(PathNode/TextField/normalize-space(text())))", + "ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)"); + } + + @Test + void testParametrizedExpression_WithStringParameter() { + testExpressionTranslation("'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", + "'hello'", "'world'"); + } + + @Test + void testParametrizedExpression_WithUnquotedStringParameter() { + assertThrows(ParseCancellationException.class, + () -> translateExpression("{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); + } + + @Test + void testParametrizedExpression_WithNumberParameter() { + testExpressionTranslation("1 = 2", "{ND-Root, number:$p1, number:$p2} ${$p1 == $p2}", "1", "2"); + } + + @Test + void testParametrizedExpression_WithDateParameter() { + testExpressionTranslation("xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", + "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); + } + + @Test + void testParametrizedExpression_WithTimeParameter() { + testExpressionTranslation("xs:time('12:00:00Z') = xs:time('13:00:00Z')", + "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); + } + + @Test + void testParametrizedExpression_WithBooleanParameter() { + testExpressionTranslation("true() = false()", + "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); + } + + @Test + void testParametrizedExpression_WithDurationParameter() { + testExpressionTranslation( + "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", + "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); + } +} diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java new file mode 100644 index 00000000..5690dfe7 --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java @@ -0,0 +1,345 @@ +package eu.europa.ted.efx.sdk1; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.junit.jupiter.api.Test; +import eu.europa.ted.efx.EfxTestsBase; + +class EfxTemplateTranslatorTestV1 extends EfxTestsBase { + @Override + protected String getSdkVersion() { + return "eforms-sdk-1.0"; + } + + /*** Template line ***/ + + @Test + void testTemplateLineNoIdent() { + assertEquals( + "let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} foo")); + } + + /** + * All nodes that contain any children get an auto generated outline number. + */ + @Test + void testTemplateLineOutline_Autogenerated() { + assertEquals(lines("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') }", + "for-each(/*/PathNode/TextField).call(block01())"), // + translateTemplate(lines("{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + } + + /** + * The autogenerated number for a node can be overridden. Leaf nodes don't get an outline number. + */ + @Test + void testTemplateLineOutline_Explicit() { + 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())"), // + translateTemplate( + lines("2{BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + } + + /** + * The autogenerated number for some nodes can be overridden. The other nodes get an auto + * generated outline number. Leaf nodes don't get an outline number. + */ + @Test + void testTemplateLineOutline_Mixed() { + 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())"), // + translateTemplate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + } + + /** + * The outline number can be suppressed for a line if overridden with the value zero. Leaf nodes + * don't get an outline number. + */ + @Test + void testTemplateLineOutline_Suppressed() { + 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())"), // + translateTemplate( + lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + } + + /** + * The outline number can be suppressed for a line if overridden with the value zero. Child nodes + * will still get an outline number. Leaf nodes still won't get an outline number. + */ + @Test + void testTemplateLineOutline_SuppressedAtParent() { + // 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())"), // + translateTemplate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + } + + @Test + void testTemplateLineFirstIndented() { + assertThrows(ParseCancellationException.class, () -> translateTemplate(" {BT-00-Text} foo")); + } + + @Test + void testTemplateLineIdentTab() { + assertEquals( + lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // + "let block0101() -> { text('bar') }", // + "for-each(/*/PathNode/TextField).call(block01())"), // + translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); + } + + @Test + void testTemplateLineIdentSpaces() { + assertEquals( + lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // + "let block0101() -> { text('bar') }", // + "for-each(/*/PathNode/TextField).call(block01())"), // + translateTemplate(lines("{BT-00-Text} foo", " {BT-00-Text} bar"))); + } + + @Test + void testTemplateLineIdentMixed() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate(lines("{BT-00-Text} foo", "\t {BT-00-Text} bar"))); + } + + @Test + void testTemplateLineIdentMixedSpaceThenTab() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate(lines("{BT-00-Text} foo", " \t{BT-00-Text} bar"))); + } + + @Test + void testTemplateLineIdentLower() { + 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())"), + translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar", "{BT-00-Code} code"))); + } + + @Test + void testTemplateLineIdentUnexpected() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate(lines("{BT-00-Text} foo", "\t\t{BT-00-Text} bar"))); + } + + @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())"), // + 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}"))); + + } + + + /*** Labels ***/ + + @Test + void testStandardLabelReference() { + assertEquals( + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\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())", + translateTemplate("{BT-00-Text} #{auxiliary|text|value}")); + } + + @Test + void testShorthandBtLabelReference() { + assertEquals( + "let block01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\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())", + translateTemplate("{BT-00-Text} #{name|BT-00-Text}")); + } + + @Test + void testShorthandBtLabelReference_MissingLabelType() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{BT-01}")); + } + + @Test + void testShorthandIndirectLabelReferenceForIndicator() { + assertEquals( + "let block01() -> { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{BT-00-Indicator}")); + } + + @Test + void testShorthandIndirectLabelReferenceForCode() { + assertEquals( + "let block01() -> { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{BT-00-Code}")); + } + + @Test + void testShorthandIndirectLabelReferenceForInternalCode() { + assertEquals( + "let block01() -> { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}")); + } + + @Test + void testShorthandIndirectLabelReferenceForCodeAttribute() { + assertEquals( + "let block01() -> { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{BT-00-CodeAttribute}")); + } + + @Test + void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameAttributeInContext() { + assertEquals( + "let block01() -> { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())", + translateTemplate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}")); + } + + @Test + void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() { + assertEquals( + "let block01() -> { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", + translateTemplate("{BT-00-Code} #{BT-00-CodeAttribute}")); + } + + @Test + void testShorthandIndirectLabelReferenceForText() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{BT-00-Text}")); + } + + @Test + void testShorthandIndirectLabelReferenceForAttribute() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{BT-00-Attribute}")); + } + + @Test + void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndIndicatorField() { + assertEquals( + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\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())", + translateTemplate("{BT-00-Code} #{name}")); + } + + @Test + void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndTextField() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{value}")); + } + + @Test + void testShorthandLabelReferenceFromContext_WithOtherLabelType() { + assertEquals( + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{name}")); + } + + @Test + void testShorthandLabelReferenceFromContext_WithUnknownLabelType() { + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{whatever}")); + } + + @Test + void testShorthandLabelReferenceFromContext_WithNodeContext() { + assertEquals( + "let block01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01())", + translateTemplate("{ND-Root} #{name}")); + } + + @Test + void testShorthandIndirectLabelReferenceFromContextField() { + assertEquals( + "let block01() -> { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", + translateTemplate("{BT-00-Code} #value")); + } + + @Test + void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() { + assertThrows(ParseCancellationException.class, () -> translateTemplate("{ND-Root} #value")); + } + + + /*** Expression block ***/ + + @Test + void testShorthandFieldValueReferenceFromContextField() { + assertEquals("let block01() -> { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01())", + translateTemplate("{BT-00-Code} $value")); + } + + @Test + void testShorthandFieldValueReferenceFromContextField_WithText() { + assertEquals( + "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", + translateTemplate("{BT-00-Code} blah #value blah $value blah")); + } + + @Test + void testShorthandFieldValueReferenceFromContextField_WithNodeContext() { + assertThrows(ParseCancellationException.class, () -> translateTemplate("{ND-Root} $value")); + } + + + /*** Other ***/ + + @Test + void testNestedExpression() { + assertEquals( + "let block01() -> { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\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())", + translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); + } + +} diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java new file mode 100644 index 00000000..4d458d4c --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java @@ -0,0 +1,45 @@ +package eu.europa.ted.efx.sdk2; + +import org.junit.jupiter.api.Test; +import eu.europa.ted.efx.EfxTestsBase; + +/** + * Test for EFX expressions that combine several aspects of the language. + */ +class EfxExpressionCombinedV2Test extends EfxTestsBase { + @Override + protected String getSdkVersion() { + return "eforms-sdk-2.0"; + } + + @Test + void testNotAnd() { + testExpressionTranslationWithContext("not(1 = 2) and (2 = 2)", "BT-00-Text", + "not(1 == 2) and (2 == 2)"); + } + + @Test + void testNotPresentAndNotPresent() { + testExpressionTranslationWithContext("not(PathNode/TextField) and not(PathNode/IntegerField)", + "ND-Root", "BT-00-Text is not present and BT-00-Integer is not present"); + } + + @Test + void testCountWithNodeContextOverride() { + testExpressionTranslationWithContext("count(../../PathNode/CodeField) = 1", "BT-00-Text", + "count(ND-Root::BT-00-Code) == 1"); + } + + @Test + void testCountWithAbsoluteFieldReference() { + testExpressionTranslationWithContext("count(/*/PathNode/CodeField) = 1", "BT-00-Text", + "count(/BT-00-Code) == 1"); + } + + @Test + void testCountWithAbsoluteFieldReferenceAndPredicate() { + testExpressionTranslationWithContext( + "count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1", "BT-00-Text", + "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); + } +} diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java new file mode 100644 index 00000000..1c9ac4dd --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -0,0 +1,1234 @@ +package eu.europa.ted.efx.sdk2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.junit.jupiter.api.Test; +import eu.europa.ted.efx.EfxTestsBase; + +class EfxExpressionTranslatorV2Test extends EfxTestsBase { + @Override + protected String getSdkVersion() { + return "eforms-sdk-2.0"; + } + + // #region: Boolean expressions --------------------------------------------- + + @Test + void testParenthesizedBooleanExpression() { + testExpressionTranslationWithContext("(true() or true()) and false()", "BT-00-Text", "(ALWAYS or TRUE) and NEVER"); + } + + @Test + void testLogicalOrCondition() { + testExpressionTranslationWithContext("true() or false()", "BT-00-Text", "ALWAYS or NEVER"); + } + + @Test + void testLogicalAndCondition() { + testExpressionTranslationWithContext("true() and 1 + 1 = 2", "BT-00-Text", "ALWAYS and 1 + 1 == 2"); + } + + @Test + void testInListCondition() { + testExpressionTranslationWithContext("not('x' = ('a','b','c'))", "BT-00-Text", "'x' not in ('a', 'b', 'c')"); + } + + @Test + void testEmptinessCondition() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = ''", "ND-Root", "BT-00-Text is empty"); + } + + @Test + void testEmptinessCondition_WithNot() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) != ''", "ND-Root", "BT-00-Text is not empty"); + } + + @Test + void testPresenceCondition() { + testExpressionTranslationWithContext("PathNode/TextField", "ND-Root", "BT-00-Text is present"); + } + + @Test + void testPresenceCondition_WithNot() { + testExpressionTranslationWithContext("not(PathNode/TextField)", "ND-Root", "BT-00-Text is not present"); + } + + @Test + void testUniqueValueCondition() { + testExpressionTranslationWithContext("count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", "ND-Root", "BT-00-Text is unique in /BT-00-Text"); + } + + @Test + void testUniqueValueCondition_WithNot() { + testExpressionTranslationWithContext("not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", "ND-Root", "BT-00-Text is not unique in /BT-00-Text"); + } + + + @Test + void testLikePatternCondition() { + testExpressionTranslationWithContext("fn:matches(normalize-space('123'), '[0-9]*')", "BT-00-Text", "'123' like '[0-9]*'"); + } + + @Test + void testLikePatternCondition_WithNot() { + testExpressionTranslationWithContext("not(fn:matches(normalize-space('123'), '[0-9]*'))", "BT-00-Text", "'123' not like '[0-9]*'"); + } + + @Test + void testFieldValueComparison_UsingTextFields() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", "Root", "text == textMultilingual"); + } + + @Test + void testFieldValueComparison_UsingNumericFields() { + testExpressionTranslationWithContext("PathNode/NumberField/number() <= PathNode/IntegerField/number()", "ND-Root", "BT-00-Number <= integer"); + } + + @Test + void testFieldValueComparison_UsingIndicatorFields() { + testExpressionTranslationWithContext("PathNode/IndicatorField != PathNode/IndicatorField", "ND-Root", "BT-00-Indicator != BT-00-Indicator"); + } + + @Test + void testFieldValueComparison_UsingDateFields() { + testExpressionTranslationWithContext("PathNode/StartDateField/xs:date(text()) <= PathNode/EndDateField/xs:date(text())", "ND-Root", "BT-00-StartDate <= BT-00-EndDate"); + } + + @Test + void testFieldValueComparison_UsingTimeFields() { + testExpressionTranslationWithContext("PathNode/StartTimeField/xs:time(text()) <= PathNode/EndTimeField/xs:time(text())", "ND-Root", "BT-00-StartTime <= BT-00-EndTime"); + } + + @Test + void testFieldValueComparison_UsingMeasureFields() { + assertEquals( + "boolean(for $T in (current-date()) return ($T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) <= $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", + translateExpressionWithContext("ND-Root", "BT-00-Measure <= BT-00-Measure")); + } + + @Test + void testFieldValueComparison_WithStringLiteral() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = 'abc'", "ND-Root", "BT-00-Text == 'abc'"); + } + + @Test + void testFieldValueComparison_WithNumericLiteral() { + testExpressionTranslationWithContext("PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", "ND-Root", "integer - BT-00-Number > 0"); + } + + @Test + void testFieldValueComparison_WithDateLiteral() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') > PathNode/StartDateField/xs:date(text())", "ND-Root", "2022-01-01Z > BT-00-StartDate"); + } + + @Test + void testFieldValueComparison_WithTimeLiteral() { + testExpressionTranslationWithContext("xs:time('00:01:00Z') > PathNode/EndTimeField/xs:time(text())", "ND-Root", "00:01:00Z > BT-00-EndTime"); + } + + @Test + void testFieldValueComparison_TypeMismatch() { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "00:01:00 > BT-00-StartDate")); + } + + + @Test + void testBooleanComparison_UsingLiterals() { + testExpressionTranslationWithContext("false() != true()", "BT-00-Text", "NEVER != ALWAYS"); + } + + @Test + void testBooleanComparison_UsingFieldReference() { + testExpressionTranslationWithContext("../IndicatorField != true()", "BT-00-Text", "BT-00-Indicator != ALWAYS"); + } + + @Test + void testNumericComparison() { + testExpressionTranslationWithContext("2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer"); + } + + @Test + void testStringComparison() { + testExpressionTranslationWithContext("'aaa' < 'bbb'", "BT-00-Text", "'aaa' < 'bbb'"); + } + + @Test + void testDateComparison_OfTwoDateLiterals() { + testExpressionTranslationWithContext("xs:date('2018-01-01Z') > xs:date('2018-01-01Z')", "BT-00-Text", "2018-01-01Z > 2018-01-01Z"); + } + + @Test + void testDateComparison_OfTwoDateReferences() { + testExpressionTranslationWithContext("PathNode/StartDateField/xs:date(text()) = PathNode/EndDateField/xs:date(text())", "ND-Root", "BT-00-StartDate == BT-00-EndDate"); + } + + @Test + void testDateComparison_OfDateReferenceAndDateFunction() { + testExpressionTranslationWithContext("PathNode/StartDateField/xs:date(text()) = xs:date(PathNode/TextField/normalize-space(text()))", "ND-Root", "BT-00-StartDate == date(BT-00-Text)"); + } + + @Test + void testTimeComparison_OfTwoTimeLiterals() { + testExpressionTranslationWithContext("xs:time('13:00:10Z') > xs:time('21:20:30Z')", "BT-00-Text", "13:00:10Z > 21:20:30Z"); + } + + @Test + void testZonedTimeComparison_OfTwoTimeLiterals() { + testExpressionTranslationWithContext("xs:time('13:00:10+01:00') > xs:time('21:20:30+02:00')", "BT-00-Text", "13:00:10+01:00 > 21:20:30+02:00"); + } + + @Test + void testTimeComparison_OfTwoTimeReferences() { + testExpressionTranslationWithContext("PathNode/StartTimeField/xs:time(text()) = PathNode/EndTimeField/xs:time(text())", "ND-Root", "BT-00-StartTime == BT-00-EndTime"); + } + + @Test + void testTimeComparison_OfTimeReferenceAndTimeFunction() { + testExpressionTranslationWithContext("PathNode/StartTimeField/xs:time(text()) = xs:time(PathNode/TextField/normalize-space(text()))", "ND-Root", "BT-00-StartTime == time(BT-00-Text)"); + } + + @Test + void testDurationComparison_UsingYearMOnthDurationLiterals() { + testExpressionTranslationWithContext("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P12M')))", "BT-00-Text", "P1Y == P12M"); + } + + @Test + void testDurationComparison_UsingDayTimeDurationLiterals() { + testExpressionTranslationWithContext("boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P21D') > $T + xs:dayTimeDuration('P7D')))", "BT-00-Text", "P3W > P7D"); + } + + @Test + void testCalculatedDurationComparison() { + testExpressionTranslationWithContext("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P3M') > $T + xs:dayTimeDuration(PathNode/EndDateField/xs:date(text()) - PathNode/StartDateField/xs:date(text()))))", "ND-Root", "P3M > (BT-00-EndDate - BT-00-StartDate)"); + } + + + @Test + void testNegativeDuration_Literal() { + testExpressionTranslationWithContext("xs:yearMonthDuration('-P3M')", "ND-Root", "-P3M"); + } + + @Test + void testNegativeDuration_ViaMultiplication() { + testExpressionTranslationWithContext("(-3 * (2 * xs:yearMonthDuration('-P3M')))", "ND-Root", "2 * -P3M * -3"); + } + + @Test + void testNegativeDuration_ViaMultiplicationWithField() { + assertEquals("(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", + translateExpressionWithContext("ND-Root", "2 * measure:BT-00-Measure * -3")); + } + + @Test + void testDurationAddition() { + testExpressionTranslationWithContext("(xs:dayTimeDuration('P3D') + xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", "ND-Root", "P3D + (BT-00-StartDate - BT-00-EndDate)"); + } + + @Test + void testDurationSubtraction() { + testExpressionTranslationWithContext("(xs:dayTimeDuration('P3D') - xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", "ND-Root", "P3D - (BT-00-StartDate - BT-00-EndDate)"); + } + + @Test + void testBooleanLiteralExpression_Always() { + testExpressionTranslationWithContext("true()", "BT-00-Text", "ALWAYS"); + } + + @Test + void testBooleanLiteralExpression_Never() { + testExpressionTranslationWithContext("false()", "BT-00-Text", "NEVER"); + } + + // #endregion: Boolean expressions + + // #region: Quantified expressions ------------------------------------------ + + @Test + void testStringQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in ('a','b','c') satisfies $x <= 'a'", "ND-Root", "every text:$x in ('a', 'b', 'c') satisfies $x <= 'a'"); + } + + @Test + void testStringQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext("every $x in PathNode/TextField satisfies $x <= 'a'", "ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'"); + } + + @Test + void testBooleanQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in (true(),false(),true()) satisfies $x", "ND-Root", "every indicator:$x in (TRUE, FALSE, ALWAYS) satisfies $x"); + } + + @Test + void testBooleanQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext("every $x in PathNode/IndicatorField satisfies $x", "ND-Root", "every indicator:$x in BT-00-Indicator satisfies $x"); + } + + @Test + void testNumericQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in (1,2,3) satisfies $x <= 1", "ND-Root", "every number:$x in (1, 2, 3) satisfies $x <= 1"); + } + + @Test + void testNumericQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext("every $x in PathNode/NumberField satisfies $x <= 1", "ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1"); + } + + @Test + void testDateQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) satisfies $x <= 2012-01-01Z"); + } + + @Test + void testDateQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext("every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); + } + + @Test + void testDateQuantifiedExpression_UsingMultipleIterators() { + testExpressionTranslationWithContext("every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); + } + + @Test + void testTimeQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in (xs:time('00:00:00Z'),xs:time('00:00:01Z'),xs:time('00:00:02Z')) satisfies $x <= xs:time('00:00:00Z')", "ND-Root", "every time:$x in (00:00:00Z, 00:00:01Z, 00:00:02Z) satisfies $x <= 00:00:00Z"); + } + + @Test + void testTimeQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext("every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); + } + + @Test + void testDurationQuantifiedExpression_UsingLiterals() { + testExpressionTranslationWithContext("every $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P3D')) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", "ND-Root", "every measure:$x in (P1D, P2D, P3D) satisfies $x <= P1D"); + } + + @Test + void testDurationQuantifiedExpression_UsingFieldReference() { + testExpressionTranslationWithContext("every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); + } + + // #endregion: Quantified expressions + + // #region: Conditional expressions ----------------------------------------- + + @Test + void testConditionalExpression() { + testExpressionTranslationWithContext("(if 1 > 2 then 'a' else 'b')", "ND-Root", "if 1 > 2 then 'a' else 'b'"); + } + + @Test + void testConditionalStringExpression_UsingLiterals() { + testExpressionTranslationWithContext("(if 'a' > 'b' then 'a' else 'b')", "ND-Root", "if 'a' > 'b' then 'a' else 'b'"); + } + + @Test + void testConditionalStringExpression_UsingFieldReferenceInCondition() { + testExpressionTranslationWithContext("(if 'a' > PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", "if 'a' > BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext("(if PathNode/TextField/normalize-space(text()) >= 'a' then 'a' else 'b')", "ND-Root", "if BT-00-Text >= 'a' then 'a' else 'b'"); + testExpressionTranslationWithContext("(if PathNode/TextField/normalize-space(text()) >= PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", "if BT-00-Text >= BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext("(if PathNode/StartDateField/xs:date(text()) >= PathNode/EndDateField/xs:date(text()) then 'a' else 'b')", "ND-Root", "if BT-00-StartDate >= BT-00-EndDate then 'a' else 'b'"); + } + + @Test + void testConditionalStringExpression_UsingFieldReference() { + testExpressionTranslationWithContext("(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else 'b')", "ND-Root", "if 'a' > 'b' then BT-00-Text else 'b'"); + testExpressionTranslationWithContext("(if 'a' > 'b' then 'a' else PathNode/TextField/normalize-space(text()))", "ND-Root", "if 'a' > 'b' then 'a' else BT-00-Text"); + testExpressionTranslationWithContext("(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else PathNode/TextField/normalize-space(text()))", "ND-Root", "if 'a' > 'b' then BT-00-Text else BT-00-Text"); + } + + @Test + void testConditionalStringExpression_UsingFieldReferences_TypeMismatch() { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); + } + + @Test + void testConditionalBooleanExpression() { + testExpressionTranslationWithContext("(if PathNode/IndicatorField then true() else false())", "ND-Root", "if BT-00-Indicator then TRUE else FALSE"); + } + + @Test + void testConditionalNumericExpression() { + testExpressionTranslationWithContext("(if 1 > 2 then 1 else PathNode/NumberField/number())", "ND-Root", "if 1 > 2 then 1 else BT-00-Number"); + } + + @Test + void testConditionalDateExpression() { + testExpressionTranslationWithContext("(if xs:date('2012-01-01Z') > PathNode/EndDateField/xs:date(text()) then PathNode/StartDateField/xs:date(text()) else xs:date('2012-01-02Z'))", "ND-Root", "if 2012-01-01Z > BT-00-EndDate then BT-00-StartDate else 2012-01-02Z"); + } + + @Test + void testConditionalTimeExpression() { + testExpressionTranslationWithContext("(if PathNode/EndTimeField/xs:time(text()) > xs:time('00:00:01Z') then PathNode/StartTimeField/xs:time(text()) else xs:time('00:00:01Z'))", "ND-Root", "if BT-00-EndTime > 00:00:01Z then BT-00-StartTime else 00:00:01Z"); + } + + @Test + void testConditionalDurationExpression() { + assertEquals( + "(if boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P1D') > $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))) then xs:dayTimeDuration('P1D') else xs:dayTimeDuration('P2D'))", + translateExpressionWithContext("ND-Root", "if P1D > BT-00-Measure then P1D else P2D")); + } + + // #endregion: Conditional expressions + + // #region: Iteration expressions ------------------------------------------- + + // Strings from iteration --------------------------------------------------- + + @Test + void testStringsFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in ('a','b','c') return concat($x, 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c') return concat($x, 'text'))"); + } + + @Test + void testStringsSequenceFromIteration_UsingMultipleIterators() { + testExpressionTranslationWithContext("'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); + } + + @Test + void testStringsSequenceFromIteration_UsingObjectVariable() { + testExpressionTranslationWithContext("for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", "ND-Root", "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); + } + + @Test + void testStringsSequenceFromIteration_UsingNodeContextVariable() { + testExpressionTranslationWithContext("for $n in .[PathNode/TextField/normalize-space(text()) = 'a'] return 'text'", "ND-Root", "for context:$n in ND-Root[BT-00-Text == 'a'] return 'text'"); + } + + @Test + void testStringsFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext("'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); + } + + + @Test + void testStringsFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in (true(),false()) return 'y')", "ND-Root", "'a' in (for indicator:$x in (TRUE, FALSE) return 'y')"); + } + + @Test + void testStringsFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext("'a' = (for $x in PathNode/IndicatorField return 'y')", "ND-Root", "'a' in (for indicator:$x in BT-00-Indicator return 'y')"); + } + + + @Test + void testStringsFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in (1,2,3) return 'y')", "ND-Root", "'a' in (for number:$x in (1, 2, 3) return 'y')"); + } + + @Test + void testStringsFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField return 'y')", "ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')"); + } + + @Test + void testStringsFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 'y')", "ND-Root", "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')"); + } + + @Test + void testStringsFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField return 'y')", "ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')"); + } + + @Test + void testStringsFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 'y')", "ND-Root", "'a' in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 'y')"); + } + + @Test + void testStringsFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField return 'y')", "ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')"); + } + + @Test + void testStringsFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext("'a' = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 'y')", "ND-Root", "'a' in (for measure:$x in (P1D, P1Y, P2M) return 'y')"); + } + + + @Test + void testStringsFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext("'a' = (for $x in PathNode/MeasureField return 'y')", "ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"); + } + + // Numbers from iteration --------------------------------------------------- + + @Test + void testNumbersFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in ('a','b','c') return number($x))", "ND-Root", "123 in (for text:$x in ('a', 'b', 'c') return number($x))"); + } + + @Test + void testNumbersFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField return number($x))", "ND-Root", "123 in (for text:$x in BT-00-Text return number($x))"); + } + + + @Test + void testNumbersFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in (true(),false()) return 0)", "ND-Root", "123 in (for indicator:$x in (TRUE, FALSE) return 0)"); + } + + @Test + void testNumbersFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/IndicatorField return 0)", "ND-Root", "123 in (for indicator:$x in BT-00-Indicator return 0)"); + } + + + @Test + void testNumbersFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in (1,2,3) return 0)", "ND-Root", "123 in (for number:$x in (1, 2, 3) return 0)"); + } + + @Test + void testNumbersFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField return 0)", "ND-Root", "123 in (for number:$x in BT-00-Number return 0)"); + } + + @Test + void testNumbersFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 0)", "ND-Root", "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)"); + } + + @Test + void testNumbersFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField return 0)", "ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)"); + } + + @Test + void testNumbersFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 0)", "ND-Root", "123 in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 0)"); + } + + @Test + void testNumbersFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField return 0)", "ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)"); + } + + @Test + void testNumbersFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext("123 = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 0)", "ND-Root", "123 in (for measure:$x in (P1D, P1Y, P2M) return 0)"); + } + + + @Test + void testNumbersFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext("123 = (for $x in PathNode/MeasureField return 0)", "ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"); + } + + // Dates from iteration --------------------------------------------------- + + @Test + void testDatesFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in ('a','b','c') return xs:date($x))", "ND-Root", "2022-01-01Z in (for text:$x in ('a', 'b', 'c') return date($x))"); + } + + @Test + void testDatesFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); + } + + + @Test + void testDatesFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (true(),false()) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for indicator:$x in (TRUE, FALSE) return 2022-01-01Z)"); + } + + @Test + void testDatesFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/IndicatorField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for indicator:$x in BT-00-Indicator return 2022-01-01Z)"); + } + + + @Test + void testDatesFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (1,2,3) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for number:$x in (1, 2, 3) return 2022-01-01Z)"); + } + + @Test + void testDatesFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); + } + + @Test + void testDatesFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 2022-01-01Z)"); + } + + @Test + void testDatesFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); + } + + @Test + void testDatesFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 2022-01-01Z)"); + } + + @Test + void testDatesFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); + } + + @Test + void testDatesFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for measure:$x in (P1D, P1Y, P2M) return 2022-01-01Z)"); + } + + + @Test + void testDatesFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); + } + + // Times from iteration --------------------------------------------------- + + @Test + void testTimesFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in ('a','b','c') return xs:time($x))", "ND-Root", "12:00:00Z in (for text:$x in ('a', 'b', 'c') return time($x))"); + } + + @Test + void testTimesFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); + } + + + @Test + void testTimesFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (true(),false()) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for indicator:$x in (TRUE, FALSE) return 12:00:00Z)"); + } + + @Test + void testTimesFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/IndicatorField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for indicator:$x in BT-00-Indicator return 12:00:00Z)"); + } + + + @Test + void testTimesFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (1,2,3) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for number:$x in (1, 2, 3) return 12:00:00Z)"); + } + + @Test + void testTimesFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); + } + + @Test + void testTimesFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 12:00:00Z)"); + } + + @Test + void testTimesFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); + } + + @Test + void testTimesFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 12:00:00Z)"); + } + + @Test + void testTimesFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); + } + + @Test + void testTimesFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for measure:$x in (P1D, P1Y, P2M) return 12:00:00Z)"); + } + + + @Test + void testTimesFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); + } + + // Durations from iteration --------------------------------------------------- + + @Test + void testDurationsFromStringIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')) return $x)", "ND-Root", "P1D in (for measure:$x in (P1D, P2D, P1W) return $x)"); + } + + @Test + void testDurationsFromStringIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); + } + + + @Test + void testDurationsFromBooleanIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (true(),false()) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for indicator:$x in (TRUE, FALSE) return P1D)"); + } + + @Test + void testDurationsFromBooleanIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/IndicatorField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for indicator:$x in BT-00-Indicator return P1D)"); + } + + + @Test + void testDurationsFromNumericIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (1,2,3) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for number:$x in (1, 2, 3) return P1D)"); + } + + @Test + void testDurationsFromNumericIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); + } + + @Test + void testDurationsFromDateIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)"); + } + + @Test + void testDurationsFromDateIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); + } + + @Test + void testDurationsFromTimeIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return P1D)"); + } + + @Test + void testDurationsFromTimeIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); + } + + @Test + void testDurationsFromDurationIteration_UsingLiterals() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for measure:$x in (P1D, P1Y, P2M) return P1D)"); + } + + @Test + void testDurationsFromDurationIteration_UsingFieldReference() { + testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); + } + + // #endregion: Iteration expressions + + // #region: Numeric expressions --------------------------------------------- + + @Test + void testMultiplicationExpression() { + testExpressionTranslationWithContext("3 * 4", "BT-00-Text", "3 * 4"); + } + + @Test + void testAdditionExpression() { + testExpressionTranslationWithContext("4 + 4", "BT-00-Text", "4 + 4"); + } + + @Test + void testParenthesizedNumericExpression() { + testExpressionTranslationWithContext("(2 + 2) * 4", "BT-00-Text", "(2 + 2)*4"); + } + + @Test + void testNumericLiteralExpression() { + testExpressionTranslationWithContext("3.1415", "BT-00-Text", "3.1415"); + } + + // #endregion: Numeric expressions + + // #region: Lists ----------------------------------------------------------- + + @Test + void testStringList() { + testExpressionTranslationWithContext("'a' = ('a','b','c')", "BT-00-Text", "'a' in ('a', 'b', 'c')"); + } + + @Test + void testNumericList_UsingNumericLiterals() { + testExpressionTranslationWithContext("4 = (1,2,3)", "BT-00-Text", "4 in (1, 2, 3)"); + } + + @Test + void testNumericList_UsingNumericField() { + testExpressionTranslationWithContext("4 = (1,../NumberField/number(),3)", "BT-00-Text", "4 in (1, BT-00-Number, 3)"); + } + + @Test + void testNumericList_UsingTextField() { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("BT-00-Text", "4 in (1, BT-00-Text, 3)")); + } + + @Test + void testBooleanList() { + testExpressionTranslationWithContext("false() = (true(),PathNode/IndicatorField,true())", "ND-Root", "NEVER in (TRUE, BT-00-Indicator, ALWAYS)"); + } + + @Test + void testDateList() { + testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (xs:date('2022-01-02Z'),PathNode/StartDateField/xs:date(text()),xs:date('2022-02-02Z'))", "ND-Root", "2022-01-01Z in (2022-01-02Z, BT-00-StartDate, 2022-02-02Z)"); + } + + @Test + void testTimeList() { + testExpressionTranslationWithContext("xs:time('12:20:21Z') = (xs:time('12:30:00Z'),PathNode/StartTimeField/xs:time(text()),xs:time('13:40:00Z'))", "ND-Root", "12:20:21Z in (12:30:00Z, BT-00-StartTime, 13:40:00Z)"); + } + + @Test + void testDurationList_UsingDurationLiterals() { + testExpressionTranslationWithContext("xs:yearMonthDuration('P3M') = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", "BT-00-Text", "P3M in (P1M, P3M, P6M)"); + } + + + + @Test + void testDurationList_UsingDurationField() { + assertEquals("(for $F in ../MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", + translateExpressionWithContext("BT-00-Text", "BT-00-Measure in (P1M, P3M, P6M)")); + } + + @Test + void testCodeList() { + testExpressionTranslationWithContext("'a' = ('code1','code2','code3')", "BT-00-Text", "'a' in codelist:accessibility"); + } + + // #endregion: Lists + + // #region: References ------------------------------------------------------ + + @Test + void testFieldAttributeValueReference() { + testExpressionTranslationWithContext("PathNode/TextField/@Attribute = 'text'", "ND-Root", "BT-00-Attribute == 'text'"); + } + + @Test + void testFieldAttributeValueReference_SameElementContext() { + testExpressionTranslationWithContext("@Attribute = 'text'", "BT-00-Text", "BT-00-Attribute == 'text'"); + } + + @Test + void testScalarFromAttributeReference() { + testExpressionTranslationWithContext("PathNode/CodeField/@listName", "ND-Root", "BT-00-Code/@listName"); + } + + @Test + void testScalarFromAttributeReference_SameElementContext() { + testExpressionTranslationWithContext("./@listName", "BT-00-Code", "BT-00-Code/@listName"); + } + + @Test + void testFieldReferenceWithPredicate() { + testExpressionTranslationWithContext("PathNode/IndicatorField['a' = 'a']", "ND-Root", "BT-00-Indicator['a' == 'a']"); + } + + @Test + void testFieldReferenceWithPredicate_WithFieldReferenceInPredicate() { + testExpressionTranslationWithContext("PathNode/IndicatorField[../CodeField/normalize-space(text()) = 'a']", "ND-Root", "BT-00-Indicator[BT-00-Code == 'a']"); + } + + @Test + void testFieldReferenceInOtherNotice() { + testExpressionTranslationWithContext("fn:doc(concat($urlPrefix, 'da4d46e9-490b-41ff-a2ae-8166d356a619'))/*/PathNode/TextField/normalize-space(text())", "ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text"); + } + + @Test + void testFieldReferenceWithFieldContextOverride() { + testExpressionTranslationWithContext("../TextField/normalize-space(text())", "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text"); + } + + @Test + void testFieldReferenceWithFieldContextOverride_WithIntegerField() { + testExpressionTranslationWithContext("../IntegerField/number()", "BT-00-Code", "BT-01-SubLevel-Text::integer"); + } + + @Test + void testFieldReferenceWithNodeContextOverride() { + testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", "ND-Root::integer"); + } + + @Test + void testFieldReferenceWithNodeContextOverride_WithPredicate() { + testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::integer"); + } + + @Test + void testAbsoluteFieldReference() { + testExpressionTranslationWithContext("/*/PathNode/IndicatorField", "BT-00-Text", "/BT-00-Indicator"); + } + + @Test + void testSimpleFieldReference() { + testExpressionTranslationWithContext("../IndicatorField", "BT-00-Text", "BT-00-Indicator"); + } + + @Test + void testFieldReference_ForDurationFields() { + testExpressionTranslationWithContext("(for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))", "ND-Root", "BT-00-Measure"); + } + + @Test + void testFieldReference_WithAxis() { + testExpressionTranslationWithContext("./preceding::PathNode/IntegerField/number()", "ND-Root", "ND-Root::preceding::integer"); + } + + // #endregion: References + + // #region: Boolean functions ----------------------------------------------- + + @Test + void testNotFunction() { + testExpressionTranslationWithContext("not(true())", "BT-00-Text", "not(ALWAYS)"); + testExpressionTranslationWithContext("not(1 + 1 = 2)", "BT-00-Text", "not(1 + 1 == 2)"); + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("BT-00-Text", "not('text')")); + } + + @Test + void testContainsFunction() { + testExpressionTranslationWithContext("contains(PathNode/TextField/normalize-space(text()), 'xyz')", "ND-Root", "contains(BT-00-Text, 'xyz')"); + } + + @Test + void testStartsWithFunction() { + testExpressionTranslationWithContext("starts-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", "starts-with(BT-00-Text, 'abc')"); + } + + @Test + void testEndsWithFunction() { + testExpressionTranslationWithContext("ends-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", "ends-with(BT-00-Text, 'abc')"); + } + + // #endregion: Boolean functions + + // #region: Numeric functions ----------------------------------------------- + + @Test + void testCountFunction_UsingFieldReference() { + testExpressionTranslationWithContext("count(PathNode/TextField)", "ND-Root", "count(BT-00-Text)"); + } + + @Test + void testCountFunction_UsingSequenceFromIteration() { + testExpressionTranslationWithContext("count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); + } + + @Test + void testNumberFunction() { + testExpressionTranslationWithContext("number(PathNode/TextField/normalize-space(text()))", "ND-Root", "number(BT-00-Text)"); + } + + @Test + void testSumFunction_UsingFieldReference() { + testExpressionTranslationWithContext("sum(PathNode/NumberField)", "ND-Root", "sum(BT-00-Number)"); + } + + @Test + void testSumFunction_UsingNumericSequenceFromIteration() { + testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField return $v + 1)", "ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"); + } + + @Test + void testStringLengthFunction() { + testExpressionTranslationWithContext("string-length(PathNode/TextField/normalize-space(text()))", "ND-Root", "string-length(BT-00-Text)"); + } + + // #endregion: Numeric functions + + // #region: String functions ------------------------------------------------ + + @Test + void testSubstringFunction() { + testExpressionTranslationWithContext("substring(PathNode/TextField/normalize-space(text()), 1, 3)", "ND-Root", "substring(BT-00-Text, 1, 3)"); + testExpressionTranslationWithContext("substring(PathNode/TextField/normalize-space(text()), 4)", "ND-Root", "substring(BT-00-Text, 4)"); + } + + @Test + void testToStringFunction() { + testExpressionTranslationWithContext("format-number(123, '0.##########')", "ND-Root", "string(123)"); + } + + @Test + void testConcatFunction() { + testExpressionTranslationWithContext("concat('abc', 'def')", "ND-Root", "concat('abc', 'def')"); + }; + + @Test + void testStringJoinFunction_withLiterals() { + testExpressionTranslationWithContext("string-join(('abc','def'), ',')", "ND-Root", "string-join(('abc', 'def'), ',')"); + } + + @Test + void testStringJoinFunction_withFieldReference() { + testExpressionTranslationWithContext("string-join(PathNode/TextField, ',')", "ND-Root", "string-join(BT-00-Text, ',')"); + } + + @Test + void testFormatNumberFunction() { + testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '#,##0.00')", "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); + } + + // #endregion: String functions + + // #region: Date functions -------------------------------------------------- + + @Test + void testDateFromStringFunction() { + testExpressionTranslationWithContext("xs:date(PathNode/TextField/normalize-space(text()))", "ND-Root", "date(BT-00-Text)"); + } + + // #endregion: Date functions + + // #region: Time functions -------------------------------------------------- + + @Test + void testTimeFromStringFunction() { + testExpressionTranslationWithContext("xs:time(PathNode/TextField/normalize-space(text()))", "ND-Root", "time(BT-00-Text)"); + } + + // #endregion: Time functions + + // #region: Sequence Functions ---------------------------------------------- + + @Test + void testDistinctValuesFunction_WithStringSequences() { + testExpressionTranslationWithContext("distinct-values(('one','two','one'))", "ND-Root", "distinct-values(('one', 'two', 'one'))"); + } + + @Test + void testDistinctValuesFunction_WithNumberSequences() { + testExpressionTranslationWithContext("distinct-values((1,2,3,2,3,4))", "ND-Root", "distinct-values((1, 2, 3, 2, 3, 4))"); + } + + @Test + void testDistinctValuesFunction_WithDateSequences() { + testExpressionTranslationWithContext("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", "ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testDistinctValuesFunction_WithTimeSequences() { + testExpressionTranslationWithContext("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); + } + + @Test + void testDistinctValuesFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("distinct-values((true(),false(),false(),false()))", "ND-Root", "distinct-values((TRUE, FALSE, FALSE, NEVER))"); + } + + @Test + void testDistinctValuesFunction_WithFieldReferences() { + testExpressionTranslationWithContext("distinct-values(PathNode/TextField)", "ND-Root", "distinct-values(BT-00-Text)"); + } + + // #region: Union + + @Test + void testUnionFunction_WithStringSequences() { + testExpressionTranslationWithContext("distinct-values((('one','two'), ('two','three','four')))", "ND-Root", "value-union(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testUnionFunction_WithNumberSequences() { + testExpressionTranslationWithContext("distinct-values(((1,2,3), (2,3,4)))", "ND-Root", "value-union((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testUnionFunction_WithDateSequences() { + testExpressionTranslationWithContext("distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", "ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testUnionFunction_WithTimeSequences() { + testExpressionTranslationWithContext("distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testUnionFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("distinct-values(((true(),false()), (false(),false())))", "ND-Root", "value-union((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testUnionFunction_WithFieldReferences() { + testExpressionTranslationWithContext("distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", "value-union(BT-00-Text, BT-00-Text)"); + } + + @Test + void testUnionFunction_WithTypeMismatch() { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "value-union(BT-00-Text, BT-00-Number)")); + } + + // #endregion: Union + + // #region: Intersect + + @Test + void testIntersectFunction_WithStringSequences() { + testExpressionTranslationWithContext("distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testIntersectFunction_WithNumberSequences() { + testExpressionTranslationWithContext("distinct-values((1,2,3)[.= (2,3,4)])", "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testIntersectFunction_WithDateSequences() { + testExpressionTranslationWithContext("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testIntersectFunction_WithTimeSequences() { + testExpressionTranslationWithContext("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testIntersectFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("distinct-values((true(),false())[.= (false(),false())])", "ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testIntersectFunction_WithFieldReferences() { + testExpressionTranslationWithContext("distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", "value-intersect(BT-00-Text, BT-00-Text)"); + } + + @Test + void testIntersectFunction_WithTypeMismatch() { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "value-intersect(BT-00-Text, BT-00-Number)")); + } + + // #endregion: Intersect + + // #region: Except + + @Test + void testExceptFunction_WithStringSequences() { + testExpressionTranslationWithContext("distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testExceptFunction_WithNumberSequences() { + testExpressionTranslationWithContext("distinct-values((1,2,3)[not(. = (2,3,4))])", "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testExceptFunction_WithDateSequences() { + testExpressionTranslationWithContext("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testExceptFunction_WithTimeSequences() { + testExpressionTranslationWithContext("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testExceptFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testExceptFunction_WithFieldReferences() { + testExpressionTranslationWithContext("distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", "value-except(BT-00-Text, BT-00-Text)"); + } + + @Test + void testExceptFunction_WithTypeMismatch() { + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "value-except(BT-00-Text, BT-00-Number)")); + } + + // #endregion: Except + + // #region: Compare sequences + + @Test + void testSequenceEqualFunction_WithStringSequences() { + testExpressionTranslationWithContext("deep-equal(sort(('one','two')), sort(('two','three','four')))", "ND-Root", "sequence-equal(('one', 'two'), ('two', 'three', 'four'))"); + } + + @Test + void testSequenceEqualFunction_WithNumberSequences() { + testExpressionTranslationWithContext("deep-equal(sort((1,2,3)), sort((2,3,4)))", "ND-Root", "sequence-equal((1, 2, 3), (2, 3, 4))"); + } + + @Test + void testSequenceEqualFunction_WithDateSequences() { + testExpressionTranslationWithContext("deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", "ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + } + + @Test + void testSequenceEqualFunction_WithTimeSequences() { + testExpressionTranslationWithContext("deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", "ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + } + + @Test + void testSequenceEqualFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("deep-equal(sort((true(),false())), sort((false(),false())))", "ND-Root", "sequence-equal((TRUE, FALSE), (FALSE, NEVER))"); + } + + @Test + void testSequenceEqualFunction_WithDurationSequences() { + testExpressionTranslationWithContext("deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", "ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))"); + } + + @Test + void testSequenceEqualFunction_WithFieldReferences() { + testExpressionTranslationWithContext("deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)"); + } + + @Test + void testParameterizedExpression_WithStringParameter() { + testExpressionTranslation("'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "'hello'", "'world'"); + } + + @Test + void testParameterizedExpression_WithUnquotedStringParameter() { + assertThrows(ParseCancellationException.class, () -> translateExpression("{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); + } + + @Test + void testParameterizedExpression_WithNumberParameter() { + testExpressionTranslation("1 = 2", "{ND-Root, number:$p1, number:$p2} ${$p1 == $p2}", "1", "2"); + } + + @Test + void testParameterizedExpression_WithDateParameter() { + testExpressionTranslation("xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); + } + + @Test + void testParameterizedExpression_WithTimeParameter() { + testExpressionTranslation("xs:time('12:00:00Z') = xs:time('13:00:00Z')", "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); + } + + @Test + void testParameterizedExpression_WithBooleanParameter() { + testExpressionTranslation("true() = false()", "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); + } + + @Test + void testParameterizedExpression_WithDurationParameter() { + testExpressionTranslation("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); + } + + // #endregion: Compare sequences + + // #endregion Sequence Functions + + // #region: Indexers -------------------------------------------------------- + + @Test + void testIndexer_WithFieldReference() { + testExpressionTranslationWithContext("PathNode/TextField[1]", "ND-Root", "text:BT-00-Text[1]"); + } + + @Test + void testIndexer_WithFieldReferenceAndPredicate() { + testExpressionTranslationWithContext("PathNode/TextField[./normalize-space(text()) = 'hello'][1]", "ND-Root", "text:BT-00-Text[BT-00-Text == 'hello'][1]"); + } + + @Test + void testIndexer_WithTextSequence() { + testExpressionTranslationWithContext("('a','b','c')[1]", "ND-Root", "('a', 'b','c')[1]"); + } + + // #endregion: Indexers +} diff --git a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java similarity index 79% rename from src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java rename to src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java index c57b32d2..4520e78e 100644 --- a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -1,25 +1,15 @@ -package eu.europa.ted.efx; +package eu.europa.ted.efx.sdk2; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.junit.jupiter.api.Test; -import eu.europa.ted.efx.mock.DependencyFactoryMock; +import eu.europa.ted.efx.EfxTestsBase; -class EfxTemplateTranslatorTest { - final private String SDK_VERSION = "eforms-sdk-2.0"; - - private String translate(final String template) { - try { - return EfxTranslator.translateTemplate(DependencyFactoryMock.INSTANCE, - SDK_VERSION, template + "\n"); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } - } - - private String lines(String... lines) { - return String.join("\n", lines); +class EfxTemplateTranslatorV2Test extends EfxTestsBase { + @Override + protected String getSdkVersion() { + return "eforms-sdk-2.0"; } /*** Template line ***/ @@ -28,7 +18,7 @@ private String lines(String... lines) { void testTemplateLineNoIdent() { assertEquals( "let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} foo")); + translateTemplate("{BT-00-Text} foo")); } /** @@ -42,7 +32,7 @@ void testTemplateLineOutline_Autogenerated() { "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", "for-each(/*/PathNode/TextField).call(block01())"), // - translate(lines("{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + translateTemplate(lines("{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } /** @@ -56,7 +46,8 @@ void testTemplateLineOutline_Explicit() { "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", "for-each(/*/PathNode/TextField).call(block01())"), // - translate(lines("2{BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + translateTemplate( + lines("2{BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } /** @@ -71,7 +62,7 @@ void testTemplateLineOutline_Mixed() { "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", "for-each(/*/PathNode/TextField).call(block01())"), // - translate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + translateTemplate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } /** @@ -86,7 +77,8 @@ void testTemplateLineOutline_Suppressed() { "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", "for-each(/*/PathNode/TextField).call(block01())"), // - translate(lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + translateTemplate( + lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } /** @@ -102,12 +94,12 @@ void testTemplateLineOutline_SuppressedAtParent() { "for-each(PathNode/NumberField).call(block010101()) }", "let block010101() -> { text('foo') }", "for-each(/*/PathNode/TextField).call(block01())"), // - translate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); + translateTemplate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo"))); } @Test void testTemplateLineFirstIndented() { - assertThrows(ParseCancellationException.class, () -> translate(" {BT-00-Text} foo")); + assertThrows(ParseCancellationException.class, () -> translateTemplate(" {BT-00-Text} foo")); } @Test @@ -116,7 +108,7 @@ void testTemplateLineIdentTab() { lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // "let block0101() -> { text('bar') }", // "for-each(/*/PathNode/TextField).call(block01())"), // - translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); + translateTemplate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar"))); } @Test @@ -125,19 +117,19 @@ void testTemplateLineIdentSpaces() { lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", // "let block0101() -> { text('bar') }", // "for-each(/*/PathNode/TextField).call(block01())"), // - translate(lines("{BT-00-Text} foo", " {BT-00-Text} bar"))); + translateTemplate(lines("{BT-00-Text} foo", " {BT-00-Text} bar"))); } @Test void testTemplateLineIdentMixed() { assertThrows(ParseCancellationException.class, - () -> translate(lines("{BT-00-Text} foo", "\t {BT-00-Text} bar"))); + () -> translateTemplate(lines("{BT-00-Text} foo", "\t {BT-00-Text} bar"))); } @Test void testTemplateLineIdentMixedSpaceThenTab() { assertThrows(ParseCancellationException.class, - () -> translate(lines("{BT-00-Text} foo", " \t{BT-00-Text} bar"))); + () -> translateTemplate(lines("{BT-00-Text} foo", " \t{BT-00-Text} bar"))); } @Test @@ -148,13 +140,13 @@ void testTemplateLineIdentLower() { "let block02() -> { text('code') }", "for-each(/*/PathNode/TextField).call(block01())", "for-each(/*/PathNode/CodeField).call(block02())"), - translate(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 testTemplateLineIdentUnexpected() { assertThrows(ParseCancellationException.class, - () -> translate(lines("{BT-00-Text} foo", "\t\t{BT-00-Text} bar"))); + () -> translateTemplate(lines("{BT-00-Text} foo", "\t\t{BT-00-Text} bar"))); } @Test @@ -165,7 +157,7 @@ void testTemplateLineJoining() { "let block02() -> { text('code') }", "for-each(/*/PathNode/TextField).call(block01())", "for-each(/*/PathNode/CodeField).call(block02())"), - translate(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"))); } @@ -176,7 +168,7 @@ void testTemplateLine_VariableScope() { "for-each(.).call(block0101()) }", // "let block0101() -> { eval(for $x in . return $x) }", // "for-each(/*/PathNode/TextField).call(block01())"), // - translate(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}"))); } @@ -194,7 +186,7 @@ void testTemplateLine_ContextVariable() { "let block010102(t, ctx, t2) -> { eval(for $z in . return concat($z, $t, $ctx)) }", // "let block0102(t, ctx, t2) -> { eval(for $z in . return concat($z, $t2, $ctx)) }", // "for-each(/*/PathNode/TextField).call(block01(ctx:., t:./normalize-space(text())))"), // - translate(lines( + translateTemplate(lines( "{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)}", " {BT-00-Text} ${for text:$z in BT-00-Text return concat($z, $t, $ctx)}", @@ -210,136 +202,140 @@ void testTemplateLine_ContextVariable() { void testStandardLabelReference() { assertEquals( "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{field|name|BT-00-Text}")); + 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())", - translate("{BT-00-Text} #{auxiliary|text|value}")); + translateTemplate("{BT-00-Text} #{auxiliary|text|value}")); } @Test void testShorthandBtLabelReference() { assertEquals( "let block01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{name|BT-00}")); + 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())", - translate("{BT-00-Text} #{name|BT-00-Text}")); + translateTemplate("{BT-00-Text} #{name|BT-00-Text}")); } @Test void testShorthandBtLabelReference_MissingLabelType() { - assertThrows(ParseCancellationException.class, () -> translate("{BT-00-Text} #{BT-01}")); + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{BT-01}")); } @Test void testShorthandIndirectLabelReferenceForIndicator() { assertEquals( "let block01() -> { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{BT-00-Indicator}")); + translateTemplate("{BT-00-Text} #{BT-00-Indicator}")); } @Test void testShorthandIndirectLabelReferenceForCode() { assertEquals( "let block01() -> { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{BT-00-Code}")); + translateTemplate("{BT-00-Text} #{BT-00-Code}")); } @Test void testShorthandIndirectLabelReferenceForInternalCode() { assertEquals( "let block01() -> { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{BT-00-Internal-Code}")); + translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute() { assertEquals( "let block01() -> { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{BT-00-CodeAttribute}")); + translateTemplate("{BT-00-Text} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameAttributeInContext() { assertEquals( "let block01() -> { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())", - translate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}")); + translateTemplate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() { assertEquals( "let block01() -> { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", - translate("{BT-00-Code} #{BT-00-CodeAttribute}")); + translateTemplate("{BT-00-Code} #{BT-00-CodeAttribute}")); } @Test void testShorthandIndirectLabelReferenceForText() { - assertThrows(ParseCancellationException.class, () -> translate("{BT-00-Text} #{BT-00-Text}")); + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{BT-00-Text}")); } @Test void testShorthandIndirectLabelReferenceForAttribute() { assertThrows(ParseCancellationException.class, - () -> translate("{BT-00-Text} #{BT-00-Attribute}")); + () -> translateTemplate("{BT-00-Text} #{BT-00-Attribute}")); } @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndIndicatorField() { assertEquals( "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/IndicatorField).call(block01())", - translate("{BT-00-Indicator} #{name}")); + 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())", - translate("{BT-00-Code} #{name}")); + translateTemplate("{BT-00-Code} #{name}")); } @Test void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndTextField() { - assertThrows(ParseCancellationException.class, () -> translate("{BT-00-Text} #{value}")); + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{value}")); } @Test void testShorthandLabelReferenceFromContext_WithOtherLabelType() { assertEquals( "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{name}")); + translateTemplate("{BT-00-Text} #{name}")); } @Test void testShorthandLabelReferenceFromContext_WithUnknownLabelType() { - assertThrows(ParseCancellationException.class, () -> translate("{BT-00-Text} #{whatever}")); + assertThrows(ParseCancellationException.class, + () -> translateTemplate("{BT-00-Text} #{whatever}")); } @Test void testShorthandLabelReferenceFromContext_WithNodeContext() { assertEquals( "let block01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01())", - translate("{ND-Root} #{name}")); + translateTemplate("{ND-Root} #{name}")); } @Test void testShorthandIndirectLabelReferenceFromContextField() { assertEquals( "let block01() -> { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", - translate("{BT-00-Code} #value")); + translateTemplate("{BT-00-Code} #value")); } @Test void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() { - assertThrows(ParseCancellationException.class, () -> translate("{ND-Root} #value")); + assertThrows(ParseCancellationException.class, () -> translateTemplate("{ND-Root} #value")); } @@ -348,19 +344,19 @@ void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() { @Test void testShorthandFieldValueReferenceFromContextField() { assertEquals("let block01() -> { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01())", - translate("{BT-00-Code} $value")); + translateTemplate("{BT-00-Code} $value")); } @Test void testShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", - translate("{BT-00-Code} blah #value blah $value blah")); + translateTemplate("{BT-00-Code} blah #value blah $value blah")); } @Test void testShorthandFieldValueReferenceFromContextField_WithNodeContext() { - assertThrows(ParseCancellationException.class, () -> translate("{ND-Root} $value")); + assertThrows(ParseCancellationException.class, () -> translateTemplate("{ND-Root} $value")); } @@ -370,13 +366,13 @@ void testShorthandFieldValueReferenceFromContextField_WithNodeContext() { void testNestedExpression() { assertEquals( "let block01() -> { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\nfor-each(/*/PathNode/TextField).call(block01())", - translate("{BT-00-Text} #{field|name|${BT-00-Text}}")); + 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())", - translate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); + translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); } } From ed9479fd3e95d40b6f677cfe38aedce66d85c816 Mon Sep 17 00:00:00 2001 From: Evangelos MELETIOU Date: Fri, 21 Apr 2023 19:16:23 +0200 Subject: [PATCH 19/57] [testing]: Fixed tests for SDK 1. --- .../efx/sdk1/EfxExpressionCombinedV1Test.java | 11 +- .../sdk1/EfxExpressionTranslatorV1Test.java | 374 ++++----- .../efx/sdk1/EfxTemplateTranslatorV1Test.java | 17 +- .../sdk2/EfxExpressionTranslatorV2Test.java | 745 +++++++++++++----- 4 files changed, 702 insertions(+), 445 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java index 5434aadb..322d78f0 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java @@ -26,21 +26,20 @@ void testNotPresentAndNotPresent() { @Test void testCountWithNodeContextOverride() { - testExpressionTranslationWithContext( - "count(../../PathNode/CodeField/normalize-space(text())) = 1", "BT-00-Text", + testExpressionTranslationWithContext("count(../../PathNode/CodeField) = 1", "BT-00-Text", "count(ND-Root::BT-00-Code) == 1"); } @Test void testCountWithAbsoluteFieldReference() { - testExpressionTranslationWithContext("count(/*/PathNode/CodeField/normalize-space(text())) = 1", - "BT-00-Text", "count(/BT-00-Code) == 1"); + testExpressionTranslationWithContext("count(/*/PathNode/CodeField) = 1", "BT-00-Text", + "count(/BT-00-Code) == 1"); } @Test void testCountWithAbsoluteFieldReferenceAndPredicate() { testExpressionTranslationWithContext( - "count(/*/PathNode/CodeField[../IndicatorField = true()]/normalize-space(text())) = 1", - "BT-00-Text", "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); + "count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1", "BT-00-Text", + "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); } } diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java index 78c7fe38..004b13ef 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -1,5 +1,6 @@ package eu.europa.ted.efx.sdk1; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.junit.jupiter.api.Test; @@ -11,7 +12,7 @@ protected String getSdkVersion() { return "eforms-sdk-1.0"; } - /*** Boolean expressions ***/ + // #region: Boolean expressions --------------------------------------------- @Test void testParenthesizedBooleanExpression() { @@ -122,9 +123,9 @@ void testFieldValueComparison_UsingTimeFields() { @Test void testFieldValueComparison_UsingMeasureFields() { - testExpressionTranslationWithContext( + assertEquals( "boolean(for $T in (current-date()) return ($T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) <= $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", - "ND-Root", "BT-00-Measure <= BT-00-Measure"); + translateExpressionWithContext("ND-Root", "BT-00-Measure <= BT-00-Measure")); } @Test @@ -265,9 +266,9 @@ void testNegativeDuration_ViaMultiplication() { @Test void testNegativeDuration_ViaMultiplicationWithField() { - testExpressionTranslationWithContext( + assertEquals( "(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", - "ND-Root", "2 * measure:BT-00-Measure * -3"); + translateExpressionWithContext("ND-Root", "2 * measure:BT-00-Measure * -3")); } @Test @@ -294,7 +295,9 @@ void testBooleanLiteralExpression_Never() { testExpressionTranslationWithContext("false()", "BT-00-Text", "NEVER"); } - /*** Quantified expressions ***/ + // #endregion: Boolean expressions + + // #region: Quantified expressions ------------------------------------------ @Test void testStringQuantifiedExpression_UsingLiterals() { @@ -304,9 +307,8 @@ void testStringQuantifiedExpression_UsingLiterals() { @Test void testStringQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext( - "every $x in PathNode/TextField/normalize-space(text()) satisfies $x <= 'a'", "ND-Root", - "every text:$x in BT-00-Text satisfies $x <= 'a'"); + testExpressionTranslationWithContext("every $x in PathNode/TextField satisfies $x <= 'a'", + "ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'"); } @Test @@ -329,9 +331,8 @@ void testNumericQuantifiedExpression_UsingLiterals() { @Test void testNumericQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext( - "every $x in PathNode/NumberField/number() satisfies $x <= 1", "ND-Root", - "every number:$x in BT-00-Number satisfies $x <= 1"); + testExpressionTranslationWithContext("every $x in PathNode/NumberField satisfies $x <= 1", + "ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1"); } @Test @@ -345,14 +346,14 @@ void testDateQuantifiedExpression_UsingLiterals() { @Test void testDateQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/StartDateField/xs:date(text()) satisfies $x <= xs:date('2012-01-01Z')", - "ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); + "every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", + "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); } @Test void testDateQuantifiedExpression_UsingMultipleIterators() { testExpressionTranslationWithContext( - "every $x in PathNode/StartDateField/xs:date(text()), $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", + "every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); } @@ -367,8 +368,8 @@ void testTimeQuantifiedExpression_UsingLiterals() { @Test void testTimeQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/StartTimeField/xs:time(text()) satisfies $x <= xs:time('00:00:00Z')", - "ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); + "every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", + "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); } @Test @@ -381,11 +382,13 @@ void testDurationQuantifiedExpression_UsingLiterals() { @Test void testDurationQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", + "every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); } - /*** Conditional expressions ***/ + // #endregion: Quantified expressions + + // #region: Conditional expressions ----------------------------------------- @Test void testConditionalExpression() { @@ -430,9 +433,8 @@ void testConditionalStringExpression_UsingFieldReference() { @Test void testConditionalStringExpression_UsingFieldReferences_TypeMismatch() { - assertThrows(ParseCancellationException.class, - () -> translateExpressionWithContext("ND-Root", - "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", + "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); } @Test @@ -463,12 +465,14 @@ void testConditionalTimeExpression() { @Test void testConditionalDurationExpression() { - testExpressionTranslationWithContext( + assertEquals( "(if boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P1D') > $T + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))) then xs:dayTimeDuration('P1D') else xs:dayTimeDuration('P2D'))", - "ND-Root", "if P1D > BT-00-Measure then P1D else P2D"); + translateExpressionWithContext("ND-Root", "if P1D > BT-00-Measure then P1D else P2D")); } - /*** Iteration expressions ***/ + // #endregion: Conditional expressions + + // #region: Iteration expressions ------------------------------------------- // Strings from iteration --------------------------------------------------- @@ -482,7 +486,7 @@ void testStringsFromStringIteration_UsingLiterals() { @Test void testStringsSequenceFromIteration_UsingMultipleIterators() { testExpressionTranslationWithContext( - "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0,##########'), 'text'))", + "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); } @@ -490,7 +494,7 @@ void testStringsSequenceFromIteration_UsingMultipleIterators() { @Test void testStringsSequenceFromIteration_UsingObjectVariable() { testExpressionTranslationWithContext( - "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField/xs:date(text()) return 'text'", + "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", "ND-Root", "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); } @@ -505,8 +509,8 @@ void testStringsSequenceFromIteration_UsingNodeContextVariable() { @Test void testStringsFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "'a' = (for $x in PathNode/TextField/normalize-space(text()) return concat($x, 'text'))", - "ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); + "'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", + "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); } @@ -531,9 +535,8 @@ void testStringsFromNumericIteration_UsingLiterals() { @Test void testStringsFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "'a' = (for $x in PathNode/NumberField/number() return 'y')", "ND-Root", - "'a' in (for number:$x in BT-00-Number return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField return 'y')", + "ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')"); } @Test @@ -545,9 +548,8 @@ void testStringsFromDateIteration_UsingLiterals() { @Test void testStringsFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "'a' = (for $x in PathNode/StartDateField/xs:date(text()) return 'y')", "ND-Root", - "'a' in (for date:$x in BT-00-StartDate return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField return 'y')", + "ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')"); } @Test @@ -559,9 +561,8 @@ void testStringsFromTimeIteration_UsingLiterals() { @Test void testStringsFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "'a' = (for $x in PathNode/StartTimeField/xs:time(text()) return 'y')", "ND-Root", - "'a' in (for time:$x in BT-00-StartTime return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField return 'y')", + "ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')"); } @Test @@ -574,8 +575,7 @@ void testStringsFromDurationIteration_UsingLiterals() { @Test void testStringsFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "'a' = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in PathNode/MeasureField return 'y')", "ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"); } @@ -589,9 +589,8 @@ void testNumbersFromStringIteration_UsingLiterals() { @Test void testNumbersFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "123 = (for $x in PathNode/TextField/normalize-space(text()) return number($x))", "ND-Root", - "123 in (for text:$x in BT-00-Text return number($x))"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField return number($x))", + "ND-Root", "123 in (for text:$x in BT-00-Text return number($x))"); } @@ -616,7 +615,7 @@ void testNumbersFromNumericIteration_UsingLiterals() { @Test void testNumbersFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField/number() return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField return 0)", "ND-Root", "123 in (for number:$x in BT-00-Number return 0)"); } @@ -629,9 +628,8 @@ void testNumbersFromDateIteration_UsingLiterals() { @Test void testNumbersFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "123 = (for $x in PathNode/StartDateField/xs:date(text()) return 0)", "ND-Root", - "123 in (for date:$x in BT-00-StartDate return 0)"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField return 0)", + "ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)"); } @Test @@ -643,9 +641,8 @@ void testNumbersFromTimeIteration_UsingLiterals() { @Test void testNumbersFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "123 = (for $x in PathNode/StartTimeField/xs:time(text()) return 0)", "ND-Root", - "123 in (for time:$x in BT-00-StartTime return 0)"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField return 0)", + "ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)"); } @Test @@ -658,8 +655,7 @@ void testNumbersFromDurationIteration_UsingLiterals() { @Test void testNumbersFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext( - "123 = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/MeasureField return 0)", "ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"); } @@ -675,8 +671,8 @@ void testDatesFromStringIteration_UsingLiterals() { @Test void testDatesFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:date($x))", - "ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); + "xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", + "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); } @@ -705,7 +701,7 @@ void testDatesFromNumericIteration_UsingLiterals() { @Test void testDatesFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField/number() return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); } @@ -720,7 +716,7 @@ void testDatesFromDateIteration_UsingLiterals() { @Test void testDatesFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); } @@ -735,7 +731,7 @@ void testDatesFromTimeIteration_UsingLiterals() { @Test void testDatesFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); } @@ -750,7 +746,7 @@ void testDatesFromDurationIteration_UsingLiterals() { @Test void testDatesFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); } @@ -766,8 +762,8 @@ void testTimesFromStringIteration_UsingLiterals() { @Test void testTimesFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:time($x))", - "ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); + "xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", + "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); } @@ -796,7 +792,7 @@ void testTimesFromNumericIteration_UsingLiterals() { @Test void testTimesFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/NumberField/number() return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); } @@ -811,7 +807,7 @@ void testTimesFromDateIteration_UsingLiterals() { @Test void testTimesFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); } @@ -826,7 +822,7 @@ void testTimesFromTimeIteration_UsingLiterals() { @Test void testTimesFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); } @@ -841,7 +837,7 @@ void testTimesFromDurationIteration_UsingLiterals() { @Test void testTimesFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); } @@ -857,7 +853,7 @@ void testDurationsFromStringIteration_UsingLiterals() { @Test void testDurationsFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField/normalize-space(text()) return xs:dayTimeDuration($x))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); } @@ -887,7 +883,7 @@ void testDurationsFromNumericIteration_UsingLiterals() { @Test void testDurationsFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField/number() return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); } @@ -901,7 +897,7 @@ void testDurationsFromDateIteration_UsingLiterals() { @Test void testDurationsFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); } @@ -915,7 +911,7 @@ void testDurationsFromTimeIteration_UsingLiterals() { @Test void testDurationsFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); } @@ -929,11 +925,13 @@ void testDurationsFromDurationIteration_UsingLiterals() { @Test void testDurationsFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); } - /*** Numeric expressions ***/ + // #endregion: Iteration expressions + + // #region: Numeric expressions --------------------------------------------- @Test void testMultiplicationExpression() { @@ -955,7 +953,9 @@ void testNumericLiteralExpression() { testExpressionTranslationWithContext("3.1415", "BT-00-Text", "3.1415"); } - /*** List ***/ + // #endregion: Numeric expressions + + // #region: Lists ----------------------------------------------------------- @Test void testStringList() { @@ -1007,11 +1007,13 @@ void testDurationList_UsingDurationLiterals() { "BT-00-Text", "P3M in (P1M, P3M, P6M)"); } + + @Test void testDurationList_UsingDurationField() { - testExpressionTranslationWithContext( + assertEquals( "(for $F in ../MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", - "BT-00-Text", "BT-00-Measure in (P1M, P3M, P6M)"); + translateExpressionWithContext("BT-00-Text", "BT-00-Measure in (P1M, P3M, P6M)")); } @Test @@ -1020,7 +1022,9 @@ void testCodeList() { "'a' in (accessibility)"); } - /*** References ***/ + // #endregion: Lists + + // #region: References ------------------------------------------------------ @Test void testFieldAttributeValueReference() { @@ -1113,7 +1117,9 @@ void testFieldReference_WithAxis() { "ND-Root::preceding::BT-00-Integer"); } - /*** Boolean functions ***/ + // #endregion: References + + // #region: Boolean functions ----------------------------------------------- @Test void testNotFunction() { @@ -1144,19 +1150,21 @@ void testEndsWithFunction() { "ends-with(BT-00-Text, 'abc')"); } - /*** Numeric functions ***/ + // #endregion: Boolean functions + + // #region: Numeric functions ----------------------------------------------- @Test void testCountFunction_UsingFieldReference() { - testExpressionTranslationWithContext("count(PathNode/TextField/normalize-space(text()))", - "ND-Root", "count(BT-00-Text)"); + testExpressionTranslationWithContext("count(PathNode/TextField)", "ND-Root", + "count(BT-00-Text)"); } @Test void testCountFunction_UsingSequenceFromIteration() { testExpressionTranslationWithContext( - "count(for $x in PathNode/TextField/normalize-space(text()) return concat($x, '-xyz'))", - "ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); + "count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", + "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); } @Test @@ -1167,15 +1175,14 @@ void testNumberFunction() { @Test void testSumFunction_UsingFieldReference() { - testExpressionTranslationWithContext("sum(PathNode/NumberField/number())", "ND-Root", + testExpressionTranslationWithContext("sum(PathNode/NumberField)", "ND-Root", "sum(BT-00-Number)"); } @Test void testSumFunction_UsingNumericSequenceFromIteration() { - testExpressionTranslationWithContext( - "sum(for $v in PathNode/NumberField/number() return $v + 1)", "ND-Root", - "sum(for number:$v in BT-00-Number return $v +1)"); + testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField return $v + 1)", + "ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"); } @Test @@ -1185,7 +1192,9 @@ void testStringLengthFunction() { "string-length(BT-00-Text)"); } - /*** String functions ***/ + // #endregion: Numeric functions + + // #region: String functions ------------------------------------------------ @Test void testSubstringFunction() { @@ -1198,7 +1207,7 @@ void testSubstringFunction() { @Test void testToStringFunction() { - testExpressionTranslationWithContext("format-number(123, '0,##########')", "ND-Root", + testExpressionTranslationWithContext("format-number(123, '0.##########')", "ND-Root", "string(123)"); } @@ -1209,12 +1218,13 @@ void testConcatFunction() { @Test void testFormatNumberFunction() { - testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '# ##0,00')", + testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '#,##0.00')", "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); } + // #endregion: String functions - /*** Date functions ***/ + // #region: Date functions -------------------------------------------------- @Test void testDateFromStringFunction() { @@ -1222,21 +1232,9 @@ void testDateFromStringFunction() { "ND-Root", "date(BT-00-Text)"); } - @Test - void testDatePlusMeasureFunction() { - testExpressionTranslationWithContext( - "(PathNode/StartDateField/xs:date(text()) + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))", - "ND-Root", "add-measure(BT-00-StartDate, BT-00-Measure)"); - } - - @Test - void testDateMinusMeasureFunction() { - testExpressionTranslationWithContext( - "(PathNode/StartDateField/xs:date(text()) - (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))", - "ND-Root", "subtract-measure(BT-00-StartDate, BT-00-Measure)"); - } + // #endregion: Date functions - /*** Time functions ***/ + // #region: Time functions -------------------------------------------------- @Test void testTimeFromStringFunction() { @@ -1244,23 +1242,9 @@ void testTimeFromStringFunction() { "ND-Root", "time(BT-00-Text)"); } - /*** Duration functions ***/ + // #endregion: Time functions - @Test - void testDayTimeDurationFromStringFunction() { - testExpressionTranslationWithContext( - "xs:yearMonthDuration(PathNode/TextField/normalize-space(text()))", "ND-Root", - "year-month-duration(BT-00-Text)"); - } - - @Test - void testYearMonthDurationFromStringFunction() { - testExpressionTranslationWithContext( - "xs:dayTimeDuration(PathNode/TextField/normalize-space(text()))", "ND-Root", - "day-time-duration(BT-00-Text)"); - } - - /*** Sequence Functions ***/ + // #region: Sequence Functions ---------------------------------------------- @Test void testDistinctValuesFunction_WithStringSequences() { @@ -1288,13 +1272,6 @@ void testDistinctValuesFunction_WithTimeSequences() { "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); } - @Test - void testDistinctValuesFunction_WithDurationSequences() { - testExpressionTranslationWithContext( - "distinct-values((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')))", - "ND-Root", "distinct-values((P1W, P2D, P2D, P5D))"); - } - @Test void testDistinctValuesFunction_WithBooleanSequences() { testExpressionTranslationWithContext("distinct-values((true(),false(),false(),false()))", @@ -1303,12 +1280,11 @@ void testDistinctValuesFunction_WithBooleanSequences() { @Test void testDistinctValuesFunction_WithFieldReferences() { - testExpressionTranslationWithContext( - "distinct-values(PathNode/TextField/normalize-space(text()))", "ND-Root", + testExpressionTranslationWithContext("distinct-values(PathNode/TextField)", "ND-Root", "distinct-values(BT-00-Text)"); } - /* Union */ + // #region: Union @Test void testUnionFunction_WithStringSequences() { @@ -1336,13 +1312,6 @@ void testUnionFunction_WithTimeSequences() { "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } - @Test - void testUnionFunction_WithDurationSequences() { - testExpressionTranslationWithContext( - "distinct-values(((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')), (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D'))))", - "ND-Root", "value-union((P1W, P2D), (P2D, P5D))"); - } - @Test void testUnionFunction_WithBooleanSequences() { testExpressionTranslationWithContext("distinct-values(((true(),false()), (false(),false())))", @@ -1352,8 +1321,8 @@ void testUnionFunction_WithBooleanSequences() { @Test void testUnionFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "distinct-values((PathNode/TextField/normalize-space(text()), PathNode/TextField/normalize-space(text())))", - "ND-Root", "value-union(BT-00-Text, BT-00-Text)"); + "distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", + "value-union(BT-00-Text, BT-00-Text)"); } @Test @@ -1362,148 +1331,99 @@ void testUnionFunction_WithTypeMismatch() { () -> translateExpressionWithContext("ND-Root", "value-union(BT-00-Text, BT-00-Number)")); } - /* Intersect */ + // #endregion: Union + + // #region: Intersect @Test void testIntersectFunction_WithStringSequences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in ('one','two') return if (some $L2 in ('two','three','four') satisfies $L1 = $L2) then $L1 else ())", - "ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); + "distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", + "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testIntersectFunction_WithNumberSequences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in (1,2,3) return if (some $L2 in (2,3,4) satisfies $L1 = $L2) then $L1 else ())", - "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); + testExpressionTranslationWithContext("distinct-values((1,2,3)[.= (2,3,4)])", "ND-Root", + "value-intersect((1, 2, 3), (2, 3, 4))"); } @Test void testIntersectFunction_WithDateSequences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (some $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 = $L2) then $L1 else ())", + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testIntersectFunction_WithTimeSequences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (some $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 = $L2) then $L1 else ())", + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } - @Test - void testIntersectFunction_WithDurationSequences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (some $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 = $L2) then $L1 else ())", - "ND-Root", "value-intersect((P1W, P2D), (P2D, P5D))"); - } - @Test void testIntersectFunction_WithBooleanSequences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in (true(),false()) return if (some $L2 in (false(),false()) satisfies $L1 = $L2) then $L1 else ())", + testExpressionTranslationWithContext("distinct-values((true(),false())[.= (false(),false())])", "ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); } @Test void testIntersectFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (some $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 = $L2) then $L1 else ())", - "ND-Root", "value-intersect(BT-00-Text, BT-00-Text)"); + "distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", + "value-intersect(BT-00-Text, BT-00-Text)"); } @Test void testIntersectFunction_WithTypeMismatch() { - assertThrows(ParseCancellationException.class, - () -> translateExpressionWithContext("ND-Root", - "value-intersect(BT-00-Text, BT-00-Number)")); + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", + "value-intersect(BT-00-Text, BT-00-Number)")); } - /* Except */ + // #endregion: Intersect + + // #region: Except @Test void testExceptFunction_WithStringSequences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in ('one','two') return if (every $L2 in ('two','three','four') satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))"); + "distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", + "value-except(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testExceptFunction_WithNumberSequences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in (1,2,3) return if (every $L2 in (2,3,4) satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); + testExpressionTranslationWithContext("distinct-values((1,2,3)[not(. = (2,3,4))])", "ND-Root", + "value-except((1, 2, 3), (2, 3, 4))"); } @Test void testExceptFunction_WithDateSequences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (every $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 != $L2) then $L1 else ())", + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testExceptFunction_WithTimeSequences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (every $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 != $L2) then $L1 else ())", + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } - @Test - void testExceptFunction_WithDurationSequences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (every $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except((P1W, P2D), (P2D, P5D))"); - } - @Test void testExceptFunction_WithBooleanSequences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in (true(),false()) return if (every $L2 in (false(),false()) satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))"); - } - - @Test - void testExceptFunction_WithTextFieldReferences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except(BT-00-Text, BT-00-Text)"); - } - - @Test - void testExceptFunction_WithNumberFieldReferences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in PathNode/IntegerField/number() return if (every $L2 in PathNode/IntegerField/number() satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except(BT-00-Integer, BT-00-Integer)"); - } - - @Test - void testExceptFunction_WithBooleanFieldReferences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in PathNode/IndicatorField return if (every $L2 in PathNode/IndicatorField satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except(BT-00-Indicator, BT-00-Indicator)"); + "distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", + "value-except((TRUE, FALSE), (FALSE, NEVER))"); } @Test - void testExceptFunction_WithDateFieldReferences() { + void testExceptFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "distinct-values(for $L1 in PathNode/StartDateField/xs:date(text()) return if (every $L2 in PathNode/StartDateField/xs:date(text()) satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except(BT-00-StartDate, BT-00-StartDate)"); - } - - @Test - void testExceptFunction_WithTimeFieldReferences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in PathNode/StartTimeField/xs:time(text()) return if (every $L2 in PathNode/StartTimeField/xs:time(text()) satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except(BT-00-StartTime, BT-00-StartTime)"); - } - - @Test - void testExceptFunction_WithDurationFieldReferences() { - testExpressionTranslationWithContext( - "distinct-values(for $L1 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return if (every $L2 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies $L1 != $L2) then $L1 else ())", - "ND-Root", "value-except(BT-00-Measure, BT-00-Measure)"); + "distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", + "value-except(BT-00-Text, BT-00-Text)"); } @Test @@ -1512,7 +1432,9 @@ void testExceptFunction_WithTypeMismatch() { () -> translateExpressionWithContext("ND-Root", "value-except(BT-00-Text, BT-00-Number)")); } - /* Compare sequences */ + // #endregion: Except + + // #region: Compare sequences @Test void testSequenceEqualFunction_WithStringSequences() { @@ -1558,49 +1480,53 @@ void testSequenceEqualFunction_WithDurationSequences() { @Test void testSequenceEqualFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "deep-equal(sort(PathNode/TextField/normalize-space(text())), sort(PathNode/TextField/normalize-space(text())))", - "ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)"); + "deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", + "sequence-equal(BT-00-Text, BT-00-Text)"); } @Test - void testParametrizedExpression_WithStringParameter() { + void testParameterizedExpression_WithStringParameter() { testExpressionTranslation("'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "'hello'", "'world'"); } @Test - void testParametrizedExpression_WithUnquotedStringParameter() { + void testParameterizedExpression_WithUnquotedStringParameter() { assertThrows(ParseCancellationException.class, () -> translateExpression("{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); } @Test - void testParametrizedExpression_WithNumberParameter() { + void testParameterizedExpression_WithNumberParameter() { testExpressionTranslation("1 = 2", "{ND-Root, number:$p1, number:$p2} ${$p1 == $p2}", "1", "2"); } @Test - void testParametrizedExpression_WithDateParameter() { + void testParameterizedExpression_WithDateParameter() { testExpressionTranslation("xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); } @Test - void testParametrizedExpression_WithTimeParameter() { + void testParameterizedExpression_WithTimeParameter() { testExpressionTranslation("xs:time('12:00:00Z') = xs:time('13:00:00Z')", "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); } @Test - void testParametrizedExpression_WithBooleanParameter() { + void testParameterizedExpression_WithBooleanParameter() { testExpressionTranslation("true() = false()", "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); } @Test - void testParametrizedExpression_WithDurationParameter() { + void testParameterizedExpression_WithDurationParameter() { testExpressionTranslation( "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); } + + // #endregion: Compare sequences + + // #endregion Sequence Functions } 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 5690dfe7..c51e8f28 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import eu.europa.ted.efx.EfxTestsBase; -class EfxTemplateTranslatorTestV1 extends EfxTestsBase { +class EfxTemplateTranslatorV1Test extends EfxTestsBase { @Override protected String getSdkVersion() { return "eforms-sdk-1.0"; @@ -122,14 +122,16 @@ void testTemplateLineIdentSpaces() { @Test void testTemplateLineIdentMixed() { + final String lines = lines("{BT-00-Text} foo", "\t {BT-00-Text} bar"); assertThrows(ParseCancellationException.class, - () -> translateTemplate(lines("{BT-00-Text} foo", "\t {BT-00-Text} bar"))); + () -> translateTemplate(lines)); } @Test void testTemplateLineIdentMixedSpaceThenTab() { + final String lines = lines("{BT-00-Text} foo", " \t{BT-00-Text} bar"); assertThrows(ParseCancellationException.class, - () -> translateTemplate(lines("{BT-00-Text} foo", " \t{BT-00-Text} bar"))); + () -> translateTemplate(lines)); } @Test @@ -145,23 +147,23 @@ void testTemplateLineIdentLower() { @Test void testTemplateLineIdentUnexpected() { + final String lines = lines("{BT-00-Text} foo", "\t\t{BT-00-Text} bar"); assertThrows(ParseCancellationException.class, - () -> translateTemplate(lines("{BT-00-Text} foo", "\t\t{BT-00-Text} bar"))); + () -> translateTemplate(lines)); } @Test void testTemplateLine_VariableScope() { assertEquals( - lines("let block01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // + lines("let block01() -> { #1: eval(for $x in . return $x)", // "for-each(.).call(block0101()) }", // - "let block0101() -> { eval(for $x in ./normalize-space(text()) return $x) }", // + "let block0101() -> { eval(for $x in . return $x) }", // "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}"))); } - /*** Labels ***/ @Test @@ -341,5 +343,4 @@ void testEndOfLineComments() { "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); } - } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 1c9ac4dd..0de7b05d 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -16,7 +16,8 @@ protected String getSdkVersion() { @Test void testParenthesizedBooleanExpression() { - testExpressionTranslationWithContext("(true() or true()) and false()", "BT-00-Text", "(ALWAYS or TRUE) and NEVER"); + testExpressionTranslationWithContext("(true() or true()) and false()", "BT-00-Text", + "(ALWAYS or TRUE) and NEVER"); } @Test @@ -26,22 +27,26 @@ void testLogicalOrCondition() { @Test void testLogicalAndCondition() { - testExpressionTranslationWithContext("true() and 1 + 1 = 2", "BT-00-Text", "ALWAYS and 1 + 1 == 2"); + testExpressionTranslationWithContext("true() and 1 + 1 = 2", "BT-00-Text", + "ALWAYS and 1 + 1 == 2"); } @Test void testInListCondition() { - testExpressionTranslationWithContext("not('x' = ('a','b','c'))", "BT-00-Text", "'x' not in ('a', 'b', 'c')"); + testExpressionTranslationWithContext("not('x' = ('a','b','c'))", "BT-00-Text", + "'x' not in ('a', 'b', 'c')"); } @Test void testEmptinessCondition() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = ''", "ND-Root", "BT-00-Text is empty"); + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = ''", + "ND-Root", "BT-00-Text is empty"); } @Test void testEmptinessCondition_WithNot() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) != ''", "ND-Root", "BT-00-Text is not empty"); + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) != ''", + "ND-Root", "BT-00-Text is not empty"); } @Test @@ -51,53 +56,69 @@ void testPresenceCondition() { @Test void testPresenceCondition_WithNot() { - testExpressionTranslationWithContext("not(PathNode/TextField)", "ND-Root", "BT-00-Text is not present"); + testExpressionTranslationWithContext("not(PathNode/TextField)", "ND-Root", + "BT-00-Text is not present"); } @Test void testUniqueValueCondition() { - testExpressionTranslationWithContext("count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", "ND-Root", "BT-00-Text is unique in /BT-00-Text"); + testExpressionTranslationWithContext( + "count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1", + "ND-Root", "BT-00-Text is unique in /BT-00-Text"); } @Test void testUniqueValueCondition_WithNot() { - testExpressionTranslationWithContext("not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", "ND-Root", "BT-00-Text is not unique in /BT-00-Text"); + testExpressionTranslationWithContext( + "not(count(for $x in PathNode/TextField, $y in /*/PathNode/TextField[. = $x] return $y) = 1)", + "ND-Root", "BT-00-Text is not unique in /BT-00-Text"); } @Test void testLikePatternCondition() { - testExpressionTranslationWithContext("fn:matches(normalize-space('123'), '[0-9]*')", "BT-00-Text", "'123' like '[0-9]*'"); + testExpressionTranslationWithContext("fn:matches(normalize-space('123'), '[0-9]*')", + "BT-00-Text", "'123' like '[0-9]*'"); } @Test void testLikePatternCondition_WithNot() { - testExpressionTranslationWithContext("not(fn:matches(normalize-space('123'), '[0-9]*'))", "BT-00-Text", "'123' not like '[0-9]*'"); + testExpressionTranslationWithContext("not(fn:matches(normalize-space('123'), '[0-9]*'))", + "BT-00-Text", "'123' not like '[0-9]*'"); } @Test void testFieldValueComparison_UsingTextFields() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", "Root", "text == textMultilingual"); + testExpressionTranslationWithContext( + "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", + "Root", "text == textMultilingual"); } @Test void testFieldValueComparison_UsingNumericFields() { - testExpressionTranslationWithContext("PathNode/NumberField/number() <= PathNode/IntegerField/number()", "ND-Root", "BT-00-Number <= integer"); + testExpressionTranslationWithContext( + "PathNode/NumberField/number() <= PathNode/IntegerField/number()", "ND-Root", + "BT-00-Number <= integer"); } @Test void testFieldValueComparison_UsingIndicatorFields() { - testExpressionTranslationWithContext("PathNode/IndicatorField != PathNode/IndicatorField", "ND-Root", "BT-00-Indicator != BT-00-Indicator"); + testExpressionTranslationWithContext("PathNode/IndicatorField != PathNode/IndicatorField", + "ND-Root", "BT-00-Indicator != BT-00-Indicator"); } @Test void testFieldValueComparison_UsingDateFields() { - testExpressionTranslationWithContext("PathNode/StartDateField/xs:date(text()) <= PathNode/EndDateField/xs:date(text())", "ND-Root", "BT-00-StartDate <= BT-00-EndDate"); + testExpressionTranslationWithContext( + "PathNode/StartDateField/xs:date(text()) <= PathNode/EndDateField/xs:date(text())", + "ND-Root", "BT-00-StartDate <= BT-00-EndDate"); } @Test void testFieldValueComparison_UsingTimeFields() { - testExpressionTranslationWithContext("PathNode/StartTimeField/xs:time(text()) <= PathNode/EndTimeField/xs:time(text())", "ND-Root", "BT-00-StartTime <= BT-00-EndTime"); + testExpressionTranslationWithContext( + "PathNode/StartTimeField/xs:time(text()) <= PathNode/EndTimeField/xs:time(text())", + "ND-Root", "BT-00-StartTime <= BT-00-EndTime"); } @Test @@ -109,27 +130,35 @@ void testFieldValueComparison_UsingMeasureFields() { @Test void testFieldValueComparison_WithStringLiteral() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = 'abc'", "ND-Root", "BT-00-Text == 'abc'"); + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = 'abc'", + "ND-Root", "BT-00-Text == 'abc'"); } @Test void testFieldValueComparison_WithNumericLiteral() { - testExpressionTranslationWithContext("PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", "ND-Root", "integer - BT-00-Number > 0"); + testExpressionTranslationWithContext( + "PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", "ND-Root", + "integer - BT-00-Number > 0"); } @Test void testFieldValueComparison_WithDateLiteral() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') > PathNode/StartDateField/xs:date(text())", "ND-Root", "2022-01-01Z > BT-00-StartDate"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') > PathNode/StartDateField/xs:date(text())", "ND-Root", + "2022-01-01Z > BT-00-StartDate"); } @Test void testFieldValueComparison_WithTimeLiteral() { - testExpressionTranslationWithContext("xs:time('00:01:00Z') > PathNode/EndTimeField/xs:time(text())", "ND-Root", "00:01:00Z > BT-00-EndTime"); + testExpressionTranslationWithContext( + "xs:time('00:01:00Z') > PathNode/EndTimeField/xs:time(text())", "ND-Root", + "00:01:00Z > BT-00-EndTime"); } @Test void testFieldValueComparison_TypeMismatch() { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "00:01:00 > BT-00-StartDate")); + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", "00:01:00 > BT-00-StartDate")); } @@ -140,12 +169,15 @@ void testBooleanComparison_UsingLiterals() { @Test void testBooleanComparison_UsingFieldReference() { - testExpressionTranslationWithContext("../IndicatorField != true()", "BT-00-Text", "BT-00-Indicator != ALWAYS"); + testExpressionTranslationWithContext("../IndicatorField != true()", "BT-00-Text", + "BT-00-Indicator != ALWAYS"); } @Test void testNumericComparison() { - testExpressionTranslationWithContext("2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer"); + testExpressionTranslationWithContext( + "2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", + "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer"); } @Test @@ -155,52 +187,69 @@ void testStringComparison() { @Test void testDateComparison_OfTwoDateLiterals() { - testExpressionTranslationWithContext("xs:date('2018-01-01Z') > xs:date('2018-01-01Z')", "BT-00-Text", "2018-01-01Z > 2018-01-01Z"); + testExpressionTranslationWithContext("xs:date('2018-01-01Z') > xs:date('2018-01-01Z')", + "BT-00-Text", "2018-01-01Z > 2018-01-01Z"); } @Test void testDateComparison_OfTwoDateReferences() { - testExpressionTranslationWithContext("PathNode/StartDateField/xs:date(text()) = PathNode/EndDateField/xs:date(text())", "ND-Root", "BT-00-StartDate == BT-00-EndDate"); + testExpressionTranslationWithContext( + "PathNode/StartDateField/xs:date(text()) = PathNode/EndDateField/xs:date(text())", + "ND-Root", "BT-00-StartDate == BT-00-EndDate"); } @Test void testDateComparison_OfDateReferenceAndDateFunction() { - testExpressionTranslationWithContext("PathNode/StartDateField/xs:date(text()) = xs:date(PathNode/TextField/normalize-space(text()))", "ND-Root", "BT-00-StartDate == date(BT-00-Text)"); + testExpressionTranslationWithContext( + "PathNode/StartDateField/xs:date(text()) = xs:date(PathNode/TextField/normalize-space(text()))", + "ND-Root", "BT-00-StartDate == date(BT-00-Text)"); } @Test void testTimeComparison_OfTwoTimeLiterals() { - testExpressionTranslationWithContext("xs:time('13:00:10Z') > xs:time('21:20:30Z')", "BT-00-Text", "13:00:10Z > 21:20:30Z"); + testExpressionTranslationWithContext("xs:time('13:00:10Z') > xs:time('21:20:30Z')", + "BT-00-Text", "13:00:10Z > 21:20:30Z"); } @Test void testZonedTimeComparison_OfTwoTimeLiterals() { - testExpressionTranslationWithContext("xs:time('13:00:10+01:00') > xs:time('21:20:30+02:00')", "BT-00-Text", "13:00:10+01:00 > 21:20:30+02:00"); + testExpressionTranslationWithContext("xs:time('13:00:10+01:00') > xs:time('21:20:30+02:00')", + "BT-00-Text", "13:00:10+01:00 > 21:20:30+02:00"); } @Test void testTimeComparison_OfTwoTimeReferences() { - testExpressionTranslationWithContext("PathNode/StartTimeField/xs:time(text()) = PathNode/EndTimeField/xs:time(text())", "ND-Root", "BT-00-StartTime == BT-00-EndTime"); + testExpressionTranslationWithContext( + "PathNode/StartTimeField/xs:time(text()) = PathNode/EndTimeField/xs:time(text())", + "ND-Root", "BT-00-StartTime == BT-00-EndTime"); } @Test void testTimeComparison_OfTimeReferenceAndTimeFunction() { - testExpressionTranslationWithContext("PathNode/StartTimeField/xs:time(text()) = xs:time(PathNode/TextField/normalize-space(text()))", "ND-Root", "BT-00-StartTime == time(BT-00-Text)"); + testExpressionTranslationWithContext( + "PathNode/StartTimeField/xs:time(text()) = xs:time(PathNode/TextField/normalize-space(text()))", + "ND-Root", "BT-00-StartTime == time(BT-00-Text)"); } @Test void testDurationComparison_UsingYearMOnthDurationLiterals() { - testExpressionTranslationWithContext("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P12M')))", "BT-00-Text", "P1Y == P12M"); + testExpressionTranslationWithContext( + "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P12M')))", + "BT-00-Text", "P1Y == P12M"); } @Test void testDurationComparison_UsingDayTimeDurationLiterals() { - testExpressionTranslationWithContext("boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P21D') > $T + xs:dayTimeDuration('P7D')))", "BT-00-Text", "P3W > P7D"); + testExpressionTranslationWithContext( + "boolean(for $T in (current-date()) return ($T + xs:dayTimeDuration('P21D') > $T + xs:dayTimeDuration('P7D')))", + "BT-00-Text", "P3W > P7D"); } @Test void testCalculatedDurationComparison() { - testExpressionTranslationWithContext("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P3M') > $T + xs:dayTimeDuration(PathNode/EndDateField/xs:date(text()) - PathNode/StartDateField/xs:date(text()))))", "ND-Root", "P3M > (BT-00-EndDate - BT-00-StartDate)"); + testExpressionTranslationWithContext( + "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P3M') > $T + xs:dayTimeDuration(PathNode/EndDateField/xs:date(text()) - PathNode/StartDateField/xs:date(text()))))", + "ND-Root", "P3M > (BT-00-EndDate - BT-00-StartDate)"); } @@ -211,23 +260,29 @@ void testNegativeDuration_Literal() { @Test void testNegativeDuration_ViaMultiplication() { - testExpressionTranslationWithContext("(-3 * (2 * xs:yearMonthDuration('-P3M')))", "ND-Root", "2 * -P3M * -3"); + testExpressionTranslationWithContext("(-3 * (2 * xs:yearMonthDuration('-P3M')))", "ND-Root", + "2 * -P3M * -3"); } @Test void testNegativeDuration_ViaMultiplicationWithField() { - assertEquals("(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", + assertEquals( + "(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", translateExpressionWithContext("ND-Root", "2 * measure:BT-00-Measure * -3")); } @Test void testDurationAddition() { - testExpressionTranslationWithContext("(xs:dayTimeDuration('P3D') + xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", "ND-Root", "P3D + (BT-00-StartDate - BT-00-EndDate)"); + testExpressionTranslationWithContext( + "(xs:dayTimeDuration('P3D') + xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", + "ND-Root", "P3D + (BT-00-StartDate - BT-00-EndDate)"); } @Test void testDurationSubtraction() { - testExpressionTranslationWithContext("(xs:dayTimeDuration('P3D') - xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", "ND-Root", "P3D - (BT-00-StartDate - BT-00-EndDate)"); + testExpressionTranslationWithContext( + "(xs:dayTimeDuration('P3D') - xs:dayTimeDuration(PathNode/StartDateField/xs:date(text()) - PathNode/EndDateField/xs:date(text())))", + "ND-Root", "P3D - (BT-00-StartDate - BT-00-EndDate)"); } @Test @@ -246,67 +301,89 @@ void testBooleanLiteralExpression_Never() { @Test void testStringQuantifiedExpression_UsingLiterals() { - testExpressionTranslationWithContext("every $x in ('a','b','c') satisfies $x <= 'a'", "ND-Root", "every text:$x in ('a', 'b', 'c') satisfies $x <= 'a'"); + testExpressionTranslationWithContext("every $x in ('a','b','c') satisfies $x <= 'a'", "ND-Root", + "every text:$x in ('a', 'b', 'c') satisfies $x <= 'a'"); } @Test void testStringQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/TextField satisfies $x <= 'a'", "ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'"); + testExpressionTranslationWithContext("every $x in PathNode/TextField satisfies $x <= 'a'", + "ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'"); } @Test void testBooleanQuantifiedExpression_UsingLiterals() { - testExpressionTranslationWithContext("every $x in (true(),false(),true()) satisfies $x", "ND-Root", "every indicator:$x in (TRUE, FALSE, ALWAYS) satisfies $x"); + testExpressionTranslationWithContext("every $x in (true(),false(),true()) satisfies $x", + "ND-Root", "every indicator:$x in (TRUE, FALSE, ALWAYS) satisfies $x"); } @Test void testBooleanQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/IndicatorField satisfies $x", "ND-Root", "every indicator:$x in BT-00-Indicator satisfies $x"); + testExpressionTranslationWithContext("every $x in PathNode/IndicatorField satisfies $x", + "ND-Root", "every indicator:$x in BT-00-Indicator satisfies $x"); } @Test void testNumericQuantifiedExpression_UsingLiterals() { - testExpressionTranslationWithContext("every $x in (1,2,3) satisfies $x <= 1", "ND-Root", "every number:$x in (1, 2, 3) satisfies $x <= 1"); + testExpressionTranslationWithContext("every $x in (1,2,3) satisfies $x <= 1", "ND-Root", + "every number:$x in (1, 2, 3) satisfies $x <= 1"); } @Test void testNumericQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/NumberField satisfies $x <= 1", "ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1"); + testExpressionTranslationWithContext("every $x in PathNode/NumberField satisfies $x <= 1", + "ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1"); } @Test void testDateQuantifiedExpression_UsingLiterals() { - testExpressionTranslationWithContext("every $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) satisfies $x <= 2012-01-01Z"); + testExpressionTranslationWithContext( + "every $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) satisfies $x <= xs:date('2012-01-01Z')", + "ND-Root", + "every date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) satisfies $x <= 2012-01-01Z"); } @Test void testDateQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); + testExpressionTranslationWithContext( + "every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", + "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); } @Test void testDateQuantifiedExpression_UsingMultipleIterators() { - testExpressionTranslationWithContext("every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); + testExpressionTranslationWithContext( + "every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", + "ND-Root", + "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); } @Test void testTimeQuantifiedExpression_UsingLiterals() { - testExpressionTranslationWithContext("every $x in (xs:time('00:00:00Z'),xs:time('00:00:01Z'),xs:time('00:00:02Z')) satisfies $x <= xs:time('00:00:00Z')", "ND-Root", "every time:$x in (00:00:00Z, 00:00:01Z, 00:00:02Z) satisfies $x <= 00:00:00Z"); + testExpressionTranslationWithContext( + "every $x in (xs:time('00:00:00Z'),xs:time('00:00:01Z'),xs:time('00:00:02Z')) satisfies $x <= xs:time('00:00:00Z')", + "ND-Root", "every time:$x in (00:00:00Z, 00:00:01Z, 00:00:02Z) satisfies $x <= 00:00:00Z"); } @Test void testTimeQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); + testExpressionTranslationWithContext( + "every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", + "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); } @Test void testDurationQuantifiedExpression_UsingLiterals() { - testExpressionTranslationWithContext("every $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P3D')) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", "ND-Root", "every measure:$x in (P1D, P2D, P3D) satisfies $x <= P1D"); + testExpressionTranslationWithContext( + "every $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P3D')) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", + "ND-Root", "every measure:$x in (P1D, P2D, P3D) satisfies $x <= P1D"); } @Test void testDurationQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); + testExpressionTranslationWithContext( + "every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", + "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); } // #endregion: Quantified expressions @@ -315,52 +392,75 @@ void testDurationQuantifiedExpression_UsingFieldReference() { @Test void testConditionalExpression() { - testExpressionTranslationWithContext("(if 1 > 2 then 'a' else 'b')", "ND-Root", "if 1 > 2 then 'a' else 'b'"); + testExpressionTranslationWithContext("(if 1 > 2 then 'a' else 'b')", "ND-Root", + "if 1 > 2 then 'a' else 'b'"); } @Test void testConditionalStringExpression_UsingLiterals() { - testExpressionTranslationWithContext("(if 'a' > 'b' then 'a' else 'b')", "ND-Root", "if 'a' > 'b' then 'a' else 'b'"); + testExpressionTranslationWithContext("(if 'a' > 'b' then 'a' else 'b')", "ND-Root", + "if 'a' > 'b' then 'a' else 'b'"); } @Test void testConditionalStringExpression_UsingFieldReferenceInCondition() { - testExpressionTranslationWithContext("(if 'a' > PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", "if 'a' > BT-00-Text then 'a' else 'b'"); - testExpressionTranslationWithContext("(if PathNode/TextField/normalize-space(text()) >= 'a' then 'a' else 'b')", "ND-Root", "if BT-00-Text >= 'a' then 'a' else 'b'"); - testExpressionTranslationWithContext("(if PathNode/TextField/normalize-space(text()) >= PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", "if BT-00-Text >= BT-00-Text then 'a' else 'b'"); - testExpressionTranslationWithContext("(if PathNode/StartDateField/xs:date(text()) >= PathNode/EndDateField/xs:date(text()) then 'a' else 'b')", "ND-Root", "if BT-00-StartDate >= BT-00-EndDate then 'a' else 'b'"); + testExpressionTranslationWithContext( + "(if 'a' > PathNode/TextField/normalize-space(text()) then 'a' else 'b')", "ND-Root", + "if 'a' > BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext( + "(if PathNode/TextField/normalize-space(text()) >= 'a' then 'a' else 'b')", "ND-Root", + "if BT-00-Text >= 'a' then 'a' else 'b'"); + testExpressionTranslationWithContext( + "(if PathNode/TextField/normalize-space(text()) >= PathNode/TextField/normalize-space(text()) then 'a' else 'b')", + "ND-Root", "if BT-00-Text >= BT-00-Text then 'a' else 'b'"); + testExpressionTranslationWithContext( + "(if PathNode/StartDateField/xs:date(text()) >= PathNode/EndDateField/xs:date(text()) then 'a' else 'b')", + "ND-Root", "if BT-00-StartDate >= BT-00-EndDate then 'a' else 'b'"); } @Test void testConditionalStringExpression_UsingFieldReference() { - testExpressionTranslationWithContext("(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else 'b')", "ND-Root", "if 'a' > 'b' then BT-00-Text else 'b'"); - testExpressionTranslationWithContext("(if 'a' > 'b' then 'a' else PathNode/TextField/normalize-space(text()))", "ND-Root", "if 'a' > 'b' then 'a' else BT-00-Text"); - testExpressionTranslationWithContext("(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else PathNode/TextField/normalize-space(text()))", "ND-Root", "if 'a' > 'b' then BT-00-Text else BT-00-Text"); + testExpressionTranslationWithContext( + "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else 'b')", "ND-Root", + "if 'a' > 'b' then BT-00-Text else 'b'"); + testExpressionTranslationWithContext( + "(if 'a' > 'b' then 'a' else PathNode/TextField/normalize-space(text()))", "ND-Root", + "if 'a' > 'b' then 'a' else BT-00-Text"); + testExpressionTranslationWithContext( + "(if 'a' > 'b' then PathNode/TextField/normalize-space(text()) else PathNode/TextField/normalize-space(text()))", + "ND-Root", "if 'a' > 'b' then BT-00-Text else BT-00-Text"); } @Test void testConditionalStringExpression_UsingFieldReferences_TypeMismatch() { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", + "if 'a' > 'b' then BT-00-StartDate else BT-00-Text")); } @Test void testConditionalBooleanExpression() { - testExpressionTranslationWithContext("(if PathNode/IndicatorField then true() else false())", "ND-Root", "if BT-00-Indicator then TRUE else FALSE"); + testExpressionTranslationWithContext("(if PathNode/IndicatorField then true() else false())", + "ND-Root", "if BT-00-Indicator then TRUE else FALSE"); } @Test void testConditionalNumericExpression() { - testExpressionTranslationWithContext("(if 1 > 2 then 1 else PathNode/NumberField/number())", "ND-Root", "if 1 > 2 then 1 else BT-00-Number"); + testExpressionTranslationWithContext("(if 1 > 2 then 1 else PathNode/NumberField/number())", + "ND-Root", "if 1 > 2 then 1 else BT-00-Number"); } @Test void testConditionalDateExpression() { - testExpressionTranslationWithContext("(if xs:date('2012-01-01Z') > PathNode/EndDateField/xs:date(text()) then PathNode/StartDateField/xs:date(text()) else xs:date('2012-01-02Z'))", "ND-Root", "if 2012-01-01Z > BT-00-EndDate then BT-00-StartDate else 2012-01-02Z"); + testExpressionTranslationWithContext( + "(if xs:date('2012-01-01Z') > PathNode/EndDateField/xs:date(text()) then PathNode/StartDateField/xs:date(text()) else xs:date('2012-01-02Z'))", + "ND-Root", "if 2012-01-01Z > BT-00-EndDate then BT-00-StartDate else 2012-01-02Z"); } @Test void testConditionalTimeExpression() { - testExpressionTranslationWithContext("(if PathNode/EndTimeField/xs:time(text()) > xs:time('00:00:01Z') then PathNode/StartTimeField/xs:time(text()) else xs:time('00:00:01Z'))", "ND-Root", "if BT-00-EndTime > 00:00:01Z then BT-00-StartTime else 00:00:01Z"); + testExpressionTranslationWithContext( + "(if PathNode/EndTimeField/xs:time(text()) > xs:time('00:00:01Z') then PathNode/StartTimeField/xs:time(text()) else xs:time('00:00:01Z'))", + "ND-Root", "if BT-00-EndTime > 00:00:01Z then BT-00-StartTime else 00:00:01Z"); } @Test @@ -378,339 +478,455 @@ void testConditionalDurationExpression() { @Test void testStringsFromStringIteration_UsingLiterals() { - testExpressionTranslationWithContext("'a' = (for $x in ('a','b','c') return concat($x, 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c') return concat($x, 'text'))"); + testExpressionTranslationWithContext( + "'a' = (for $x in ('a','b','c') return concat($x, 'text'))", "ND-Root", + "'a' in (for text:$x in ('a', 'b', 'c') return concat($x, 'text'))"); } @Test void testStringsSequenceFromIteration_UsingMultipleIterators() { - testExpressionTranslationWithContext("'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); + testExpressionTranslationWithContext( + "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", + "ND-Root", + "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); } @Test void testStringsSequenceFromIteration_UsingObjectVariable() { - testExpressionTranslationWithContext("for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", "ND-Root", "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); + testExpressionTranslationWithContext( + "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", + "ND-Root", + "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); } @Test void testStringsSequenceFromIteration_UsingNodeContextVariable() { - testExpressionTranslationWithContext("for $n in .[PathNode/TextField/normalize-space(text()) = 'a'] return 'text'", "ND-Root", "for context:$n in ND-Root[BT-00-Text == 'a'] return 'text'"); + testExpressionTranslationWithContext( + "for $n in .[PathNode/TextField/normalize-space(text()) = 'a'] return 'text'", "ND-Root", + "for context:$n in ND-Root[BT-00-Text == 'a'] return 'text'"); } @Test void testStringsFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); + testExpressionTranslationWithContext( + "'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", + "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); } @Test void testStringsFromBooleanIteration_UsingLiterals() { - testExpressionTranslationWithContext("'a' = (for $x in (true(),false()) return 'y')", "ND-Root", "'a' in (for indicator:$x in (TRUE, FALSE) return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in (true(),false()) return 'y')", "ND-Root", + "'a' in (for indicator:$x in (TRUE, FALSE) return 'y')"); } @Test void testStringsFromBooleanIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/IndicatorField return 'y')", "ND-Root", "'a' in (for indicator:$x in BT-00-Indicator return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/IndicatorField return 'y')", + "ND-Root", "'a' in (for indicator:$x in BT-00-Indicator return 'y')"); } @Test void testStringsFromNumericIteration_UsingLiterals() { - testExpressionTranslationWithContext("'a' = (for $x in (1,2,3) return 'y')", "ND-Root", "'a' in (for number:$x in (1, 2, 3) return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in (1,2,3) return 'y')", "ND-Root", + "'a' in (for number:$x in (1, 2, 3) return 'y')"); } @Test void testStringsFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField return 'y')", "ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField return 'y')", + "ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')"); } @Test void testStringsFromDateIteration_UsingLiterals() { - testExpressionTranslationWithContext("'a' = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 'y')", "ND-Root", "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')"); + testExpressionTranslationWithContext( + "'a' = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 'y')", + "ND-Root", "'a' in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 'y')"); } @Test void testStringsFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField return 'y')", "ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField return 'y')", + "ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')"); } @Test void testStringsFromTimeIteration_UsingLiterals() { - testExpressionTranslationWithContext("'a' = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 'y')", "ND-Root", "'a' in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 'y')"); + testExpressionTranslationWithContext( + "'a' = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 'y')", + "ND-Root", "'a' in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 'y')"); } @Test void testStringsFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField return 'y')", "ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField return 'y')", + "ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')"); } @Test void testStringsFromDurationIteration_UsingLiterals() { - testExpressionTranslationWithContext("'a' = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 'y')", "ND-Root", "'a' in (for measure:$x in (P1D, P1Y, P2M) return 'y')"); + testExpressionTranslationWithContext( + "'a' = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 'y')", + "ND-Root", "'a' in (for measure:$x in (P1D, P1Y, P2M) return 'y')"); } @Test void testStringsFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/MeasureField return 'y')", "ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"); + testExpressionTranslationWithContext("'a' = (for $x in PathNode/MeasureField return 'y')", + "ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"); } // Numbers from iteration --------------------------------------------------- @Test void testNumbersFromStringIteration_UsingLiterals() { - testExpressionTranslationWithContext("123 = (for $x in ('a','b','c') return number($x))", "ND-Root", "123 in (for text:$x in ('a', 'b', 'c') return number($x))"); + testExpressionTranslationWithContext("123 = (for $x in ('a','b','c') return number($x))", + "ND-Root", "123 in (for text:$x in ('a', 'b', 'c') return number($x))"); } @Test void testNumbersFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField return number($x))", "ND-Root", "123 in (for text:$x in BT-00-Text return number($x))"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField return number($x))", + "ND-Root", "123 in (for text:$x in BT-00-Text return number($x))"); } @Test void testNumbersFromBooleanIteration_UsingLiterals() { - testExpressionTranslationWithContext("123 = (for $x in (true(),false()) return 0)", "ND-Root", "123 in (for indicator:$x in (TRUE, FALSE) return 0)"); + testExpressionTranslationWithContext("123 = (for $x in (true(),false()) return 0)", "ND-Root", + "123 in (for indicator:$x in (TRUE, FALSE) return 0)"); } @Test void testNumbersFromBooleanIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/IndicatorField return 0)", "ND-Root", "123 in (for indicator:$x in BT-00-Indicator return 0)"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/IndicatorField return 0)", + "ND-Root", "123 in (for indicator:$x in BT-00-Indicator return 0)"); } @Test void testNumbersFromNumericIteration_UsingLiterals() { - testExpressionTranslationWithContext("123 = (for $x in (1,2,3) return 0)", "ND-Root", "123 in (for number:$x in (1, 2, 3) return 0)"); + testExpressionTranslationWithContext("123 = (for $x in (1,2,3) return 0)", "ND-Root", + "123 in (for number:$x in (1, 2, 3) return 0)"); } @Test void testNumbersFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField return 0)", "ND-Root", "123 in (for number:$x in BT-00-Number return 0)"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField return 0)", + "ND-Root", "123 in (for number:$x in BT-00-Number return 0)"); } @Test void testNumbersFromDateIteration_UsingLiterals() { - testExpressionTranslationWithContext("123 = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 0)", "ND-Root", "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)"); + testExpressionTranslationWithContext( + "123 = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return 0)", + "ND-Root", "123 in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 0)"); } @Test void testNumbersFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField return 0)", "ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField return 0)", + "ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)"); } @Test void testNumbersFromTimeIteration_UsingLiterals() { - testExpressionTranslationWithContext("123 = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 0)", "ND-Root", "123 in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 0)"); + testExpressionTranslationWithContext( + "123 = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return 0)", + "ND-Root", "123 in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 0)"); } @Test void testNumbersFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField return 0)", "ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField return 0)", + "ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)"); } @Test void testNumbersFromDurationIteration_UsingLiterals() { - testExpressionTranslationWithContext("123 = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 0)", "ND-Root", "123 in (for measure:$x in (P1D, P1Y, P2M) return 0)"); + testExpressionTranslationWithContext( + "123 = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return 0)", + "ND-Root", "123 in (for measure:$x in (P1D, P1Y, P2M) return 0)"); } @Test void testNumbersFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/MeasureField return 0)", "ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"); + testExpressionTranslationWithContext("123 = (for $x in PathNode/MeasureField return 0)", + "ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"); } // Dates from iteration --------------------------------------------------- @Test void testDatesFromStringIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in ('a','b','c') return xs:date($x))", "ND-Root", "2022-01-01Z in (for text:$x in ('a', 'b', 'c') return date($x))"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in ('a','b','c') return xs:date($x))", "ND-Root", + "2022-01-01Z in (for text:$x in ('a', 'b', 'c') return date($x))"); } @Test void testDatesFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", + "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); } @Test void testDatesFromBooleanIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (true(),false()) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for indicator:$x in (TRUE, FALSE) return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (true(),false()) return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for indicator:$x in (TRUE, FALSE) return 2022-01-01Z)"); } @Test void testDatesFromBooleanIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/IndicatorField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for indicator:$x in BT-00-Indicator return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/IndicatorField return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for indicator:$x in BT-00-Indicator return 2022-01-01Z)"); } @Test void testDatesFromNumericIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (1,2,3) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for number:$x in (1, 2, 3) return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (1,2,3) return xs:date('2022-01-01Z'))", "ND-Root", + "2022-01-01Z in (for number:$x in (1, 2, 3) return 2022-01-01Z)"); } @Test void testDatesFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); } @Test void testDatesFromDateIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:date('2022-01-01Z'))", + "ND-Root", + "2022-01-01Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 2022-01-01Z)"); } @Test void testDatesFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); } @Test void testDatesFromTimeIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:date('2022-01-01Z'))", + "ND-Root", + "2022-01-01Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 2022-01-01Z)"); } @Test void testDatesFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); } @Test void testDatesFromDurationIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for measure:$x in (P1D, P1Y, P2M) return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for measure:$x in (P1D, P1Y, P2M) return 2022-01-01Z)"); } @Test void testDatesFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", + "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); } // Times from iteration --------------------------------------------------- @Test void testTimesFromStringIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in ('a','b','c') return xs:time($x))", "ND-Root", "12:00:00Z in (for text:$x in ('a', 'b', 'c') return time($x))"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in ('a','b','c') return xs:time($x))", "ND-Root", + "12:00:00Z in (for text:$x in ('a', 'b', 'c') return time($x))"); } @Test void testTimesFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", + "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); } @Test void testTimesFromBooleanIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (true(),false()) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for indicator:$x in (TRUE, FALSE) return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (true(),false()) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for indicator:$x in (TRUE, FALSE) return 12:00:00Z)"); } @Test void testTimesFromBooleanIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/IndicatorField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for indicator:$x in BT-00-Indicator return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/IndicatorField return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for indicator:$x in BT-00-Indicator return 12:00:00Z)"); } @Test void testTimesFromNumericIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (1,2,3) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for number:$x in (1, 2, 3) return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (1,2,3) return xs:time('12:00:00Z'))", "ND-Root", + "12:00:00Z in (for number:$x in (1, 2, 3) return 12:00:00Z)"); } @Test void testTimesFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); } @Test void testTimesFromDateIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:time('12:00:00Z'))", + "ND-Root", + "12:00:00Z in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return 12:00:00Z)"); } @Test void testTimesFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); } @Test void testTimesFromTimeIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:time('12:00:00Z'))", + "ND-Root", + "12:00:00Z in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return 12:00:00Z)"); } @Test void testTimesFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); } @Test void testTimesFromDurationIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for measure:$x in (P1D, P1Y, P2M) return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for measure:$x in (P1D, P1Y, P2M) return 12:00:00Z)"); } @Test void testTimesFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", + "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); } // Durations from iteration --------------------------------------------------- @Test void testDurationsFromStringIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')) return $x)", "ND-Root", "P1D in (for measure:$x in (P1D, P2D, P1W) return $x)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')) return $x)", + "ND-Root", "P1D in (for measure:$x in (P1D, P2D, P1W) return $x)"); } @Test void testDurationsFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", + "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); } @Test void testDurationsFromBooleanIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (true(),false()) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for indicator:$x in (TRUE, FALSE) return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (true(),false()) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for indicator:$x in (TRUE, FALSE) return P1D)"); } @Test void testDurationsFromBooleanIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/IndicatorField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for indicator:$x in BT-00-Indicator return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/IndicatorField return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for indicator:$x in BT-00-Indicator return P1D)"); } @Test void testDurationsFromNumericIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (1,2,3) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for number:$x in (1, 2, 3) return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (1,2,3) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for number:$x in (1, 2, 3) return P1D)"); } @Test void testDurationsFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); } @Test void testDurationsFromDateIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:date('2012-01-01Z'),xs:date('2012-01-02Z'),xs:date('2012-01-03Z')) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for date:$x in (2012-01-01Z, 2012-01-02Z, 2012-01-03Z) return P1D)"); } @Test void testDurationsFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); } @Test void testDurationsFromTimeIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:time('12:00:00Z'),xs:time('12:00:01Z'),xs:time('12:00:02Z')) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for time:$x in (12:00:00Z, 12:00:01Z, 12:00:02Z) return P1D)"); } @Test void testDurationsFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); } @Test void testDurationsFromDurationIteration_UsingLiterals() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for measure:$x in (P1D, P1Y, P2M) return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in (xs:dayTimeDuration('P1D'),xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for measure:$x in (P1D, P1Y, P2M) return P1D)"); } @Test void testDurationsFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); + testExpressionTranslationWithContext( + "xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", + "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); } // #endregion: Iteration expressions @@ -743,7 +959,8 @@ void testNumericLiteralExpression() { @Test void testStringList() { - testExpressionTranslationWithContext("'a' = ('a','b','c')", "BT-00-Text", "'a' in ('a', 'b', 'c')"); + testExpressionTranslationWithContext("'a' = ('a','b','c')", "BT-00-Text", + "'a' in ('a', 'b', 'c')"); } @Test @@ -753,45 +970,56 @@ void testNumericList_UsingNumericLiterals() { @Test void testNumericList_UsingNumericField() { - testExpressionTranslationWithContext("4 = (1,../NumberField/number(),3)", "BT-00-Text", "4 in (1, BT-00-Number, 3)"); + testExpressionTranslationWithContext("4 = (1,../NumberField/number(),3)", "BT-00-Text", + "4 in (1, BT-00-Number, 3)"); } @Test void testNumericList_UsingTextField() { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("BT-00-Text", "4 in (1, BT-00-Text, 3)")); + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("BT-00-Text", "4 in (1, BT-00-Text, 3)")); } @Test void testBooleanList() { - testExpressionTranslationWithContext("false() = (true(),PathNode/IndicatorField,true())", "ND-Root", "NEVER in (TRUE, BT-00-Indicator, ALWAYS)"); + testExpressionTranslationWithContext("false() = (true(),PathNode/IndicatorField,true())", + "ND-Root", "NEVER in (TRUE, BT-00-Indicator, ALWAYS)"); } @Test void testDateList() { - testExpressionTranslationWithContext("xs:date('2022-01-01Z') = (xs:date('2022-01-02Z'),PathNode/StartDateField/xs:date(text()),xs:date('2022-02-02Z'))", "ND-Root", "2022-01-01Z in (2022-01-02Z, BT-00-StartDate, 2022-02-02Z)"); + testExpressionTranslationWithContext( + "xs:date('2022-01-01Z') = (xs:date('2022-01-02Z'),PathNode/StartDateField/xs:date(text()),xs:date('2022-02-02Z'))", + "ND-Root", "2022-01-01Z in (2022-01-02Z, BT-00-StartDate, 2022-02-02Z)"); } @Test void testTimeList() { - testExpressionTranslationWithContext("xs:time('12:20:21Z') = (xs:time('12:30:00Z'),PathNode/StartTimeField/xs:time(text()),xs:time('13:40:00Z'))", "ND-Root", "12:20:21Z in (12:30:00Z, BT-00-StartTime, 13:40:00Z)"); + testExpressionTranslationWithContext( + "xs:time('12:20:21Z') = (xs:time('12:30:00Z'),PathNode/StartTimeField/xs:time(text()),xs:time('13:40:00Z'))", + "ND-Root", "12:20:21Z in (12:30:00Z, BT-00-StartTime, 13:40:00Z)"); } @Test void testDurationList_UsingDurationLiterals() { - testExpressionTranslationWithContext("xs:yearMonthDuration('P3M') = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", "BT-00-Text", "P3M in (P1M, P3M, P6M)"); + testExpressionTranslationWithContext( + "xs:yearMonthDuration('P3M') = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", + "BT-00-Text", "P3M in (P1M, P3M, P6M)"); } @Test void testDurationList_UsingDurationField() { - assertEquals("(for $F in ../MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", + assertEquals( + "(for $F in ../MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) = (xs:yearMonthDuration('P1M'),xs:yearMonthDuration('P3M'),xs:yearMonthDuration('P6M'))", translateExpressionWithContext("BT-00-Text", "BT-00-Measure in (P1M, P3M, P6M)")); } @Test void testCodeList() { - testExpressionTranslationWithContext("'a' = ('code1','code2','code3')", "BT-00-Text", "'a' in codelist:accessibility"); + testExpressionTranslationWithContext("'a' = ('code1','code2','code3')", "BT-00-Text", + "'a' in codelist:accessibility"); } // #endregion: Lists @@ -800,17 +1028,20 @@ void testCodeList() { @Test void testFieldAttributeValueReference() { - testExpressionTranslationWithContext("PathNode/TextField/@Attribute = 'text'", "ND-Root", "BT-00-Attribute == 'text'"); + testExpressionTranslationWithContext("PathNode/TextField/@Attribute = 'text'", "ND-Root", + "BT-00-Attribute == 'text'"); } @Test void testFieldAttributeValueReference_SameElementContext() { - testExpressionTranslationWithContext("@Attribute = 'text'", "BT-00-Text", "BT-00-Attribute == 'text'"); + testExpressionTranslationWithContext("@Attribute = 'text'", "BT-00-Text", + "BT-00-Attribute == 'text'"); } @Test void testScalarFromAttributeReference() { - testExpressionTranslationWithContext("PathNode/CodeField/@listName", "ND-Root", "BT-00-Code/@listName"); + testExpressionTranslationWithContext("PathNode/CodeField/@listName", "ND-Root", + "BT-00-Code/@listName"); } @Test @@ -820,42 +1051,52 @@ void testScalarFromAttributeReference_SameElementContext() { @Test void testFieldReferenceWithPredicate() { - testExpressionTranslationWithContext("PathNode/IndicatorField['a' = 'a']", "ND-Root", "BT-00-Indicator['a' == 'a']"); + testExpressionTranslationWithContext("PathNode/IndicatorField['a' = 'a']", "ND-Root", + "BT-00-Indicator['a' == 'a']"); } @Test void testFieldReferenceWithPredicate_WithFieldReferenceInPredicate() { - testExpressionTranslationWithContext("PathNode/IndicatorField[../CodeField/normalize-space(text()) = 'a']", "ND-Root", "BT-00-Indicator[BT-00-Code == 'a']"); + testExpressionTranslationWithContext( + "PathNode/IndicatorField[../CodeField/normalize-space(text()) = 'a']", "ND-Root", + "BT-00-Indicator[BT-00-Code == 'a']"); } @Test void testFieldReferenceInOtherNotice() { - testExpressionTranslationWithContext("fn:doc(concat($urlPrefix, 'da4d46e9-490b-41ff-a2ae-8166d356a619'))/*/PathNode/TextField/normalize-space(text())", "ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text"); + testExpressionTranslationWithContext( + "fn:doc(concat($urlPrefix, 'da4d46e9-490b-41ff-a2ae-8166d356a619'))/*/PathNode/TextField/normalize-space(text())", + "ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text"); } @Test void testFieldReferenceWithFieldContextOverride() { - testExpressionTranslationWithContext("../TextField/normalize-space(text())", "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text"); + testExpressionTranslationWithContext("../TextField/normalize-space(text())", "BT-00-Code", + "BT-01-SubLevel-Text::BT-00-Text"); } @Test void testFieldReferenceWithFieldContextOverride_WithIntegerField() { - testExpressionTranslationWithContext("../IntegerField/number()", "BT-00-Code", "BT-01-SubLevel-Text::integer"); + testExpressionTranslationWithContext("../IntegerField/number()", "BT-00-Code", + "BT-01-SubLevel-Text::integer"); } @Test void testFieldReferenceWithNodeContextOverride() { - testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", "ND-Root::integer"); + testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", + "ND-Root::integer"); } @Test void testFieldReferenceWithNodeContextOverride_WithPredicate() { - testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::integer"); + testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", + "ND-Root[BT-00-Indicator == TRUE]::integer"); } @Test void testAbsoluteFieldReference() { - testExpressionTranslationWithContext("/*/PathNode/IndicatorField", "BT-00-Text", "/BT-00-Indicator"); + testExpressionTranslationWithContext("/*/PathNode/IndicatorField", "BT-00-Text", + "/BT-00-Indicator"); } @Test @@ -865,12 +1106,15 @@ void testSimpleFieldReference() { @Test void testFieldReference_ForDurationFields() { - testExpressionTranslationWithContext("(for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))", "ND-Root", "BT-00-Measure"); + testExpressionTranslationWithContext( + "(for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))", + "ND-Root", "BT-00-Measure"); } @Test void testFieldReference_WithAxis() { - testExpressionTranslationWithContext("./preceding::PathNode/IntegerField/number()", "ND-Root", "ND-Root::preceding::integer"); + testExpressionTranslationWithContext("./preceding::PathNode/IntegerField/number()", "ND-Root", + "ND-Root::preceding::integer"); } // #endregion: References @@ -881,22 +1125,29 @@ void testFieldReference_WithAxis() { void testNotFunction() { testExpressionTranslationWithContext("not(true())", "BT-00-Text", "not(ALWAYS)"); testExpressionTranslationWithContext("not(1 + 1 = 2)", "BT-00-Text", "not(1 + 1 == 2)"); - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("BT-00-Text", "not('text')")); + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("BT-00-Text", "not('text')")); } @Test void testContainsFunction() { - testExpressionTranslationWithContext("contains(PathNode/TextField/normalize-space(text()), 'xyz')", "ND-Root", "contains(BT-00-Text, 'xyz')"); + testExpressionTranslationWithContext( + "contains(PathNode/TextField/normalize-space(text()), 'xyz')", "ND-Root", + "contains(BT-00-Text, 'xyz')"); } @Test void testStartsWithFunction() { - testExpressionTranslationWithContext("starts-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", "starts-with(BT-00-Text, 'abc')"); + testExpressionTranslationWithContext( + "starts-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", + "starts-with(BT-00-Text, 'abc')"); } @Test void testEndsWithFunction() { - testExpressionTranslationWithContext("ends-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", "ends-with(BT-00-Text, 'abc')"); + testExpressionTranslationWithContext( + "ends-with(PathNode/TextField/normalize-space(text()), 'abc')", "ND-Root", + "ends-with(BT-00-Text, 'abc')"); } // #endregion: Boolean functions @@ -905,32 +1156,40 @@ void testEndsWithFunction() { @Test void testCountFunction_UsingFieldReference() { - testExpressionTranslationWithContext("count(PathNode/TextField)", "ND-Root", "count(BT-00-Text)"); + testExpressionTranslationWithContext("count(PathNode/TextField)", "ND-Root", + "count(BT-00-Text)"); } @Test void testCountFunction_UsingSequenceFromIteration() { - testExpressionTranslationWithContext("count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); + testExpressionTranslationWithContext( + "count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", + "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); } @Test void testNumberFunction() { - testExpressionTranslationWithContext("number(PathNode/TextField/normalize-space(text()))", "ND-Root", "number(BT-00-Text)"); + testExpressionTranslationWithContext("number(PathNode/TextField/normalize-space(text()))", + "ND-Root", "number(BT-00-Text)"); } @Test void testSumFunction_UsingFieldReference() { - testExpressionTranslationWithContext("sum(PathNode/NumberField)", "ND-Root", "sum(BT-00-Number)"); + testExpressionTranslationWithContext("sum(PathNode/NumberField)", "ND-Root", + "sum(BT-00-Number)"); } @Test void testSumFunction_UsingNumericSequenceFromIteration() { - testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField return $v + 1)", "ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"); + testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField return $v + 1)", + "ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"); } @Test void testStringLengthFunction() { - testExpressionTranslationWithContext("string-length(PathNode/TextField/normalize-space(text()))", "ND-Root", "string-length(BT-00-Text)"); + testExpressionTranslationWithContext( + "string-length(PathNode/TextField/normalize-space(text()))", "ND-Root", + "string-length(BT-00-Text)"); } // #endregion: Numeric functions @@ -939,13 +1198,17 @@ void testStringLengthFunction() { @Test void testSubstringFunction() { - testExpressionTranslationWithContext("substring(PathNode/TextField/normalize-space(text()), 1, 3)", "ND-Root", "substring(BT-00-Text, 1, 3)"); - testExpressionTranslationWithContext("substring(PathNode/TextField/normalize-space(text()), 4)", "ND-Root", "substring(BT-00-Text, 4)"); + testExpressionTranslationWithContext( + "substring(PathNode/TextField/normalize-space(text()), 1, 3)", "ND-Root", + "substring(BT-00-Text, 1, 3)"); + testExpressionTranslationWithContext("substring(PathNode/TextField/normalize-space(text()), 4)", + "ND-Root", "substring(BT-00-Text, 4)"); } @Test void testToStringFunction() { - testExpressionTranslationWithContext("format-number(123, '0.##########')", "ND-Root", "string(123)"); + testExpressionTranslationWithContext("format-number(123, '0.##########')", "ND-Root", + "string(123)"); } @Test @@ -955,17 +1218,20 @@ void testConcatFunction() { @Test void testStringJoinFunction_withLiterals() { - testExpressionTranslationWithContext("string-join(('abc','def'), ',')", "ND-Root", "string-join(('abc', 'def'), ',')"); + testExpressionTranslationWithContext("string-join(('abc','def'), ',')", "ND-Root", + "string-join(('abc', 'def'), ',')"); } @Test void testStringJoinFunction_withFieldReference() { - testExpressionTranslationWithContext("string-join(PathNode/TextField, ',')", "ND-Root", "string-join(BT-00-Text, ',')"); + testExpressionTranslationWithContext("string-join(PathNode/TextField, ',')", "ND-Root", + "string-join(BT-00-Text, ',')"); } @Test void testFormatNumberFunction() { - testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '#,##0.00')", "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); + testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '#,##0.00')", + "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); } // #endregion: String functions @@ -974,7 +1240,8 @@ void testFormatNumberFunction() { @Test void testDateFromStringFunction() { - testExpressionTranslationWithContext("xs:date(PathNode/TextField/normalize-space(text()))", "ND-Root", "date(BT-00-Text)"); + testExpressionTranslationWithContext("xs:date(PathNode/TextField/normalize-space(text()))", + "ND-Root", "date(BT-00-Text)"); } // #endregion: Date functions @@ -983,7 +1250,8 @@ void testDateFromStringFunction() { @Test void testTimeFromStringFunction() { - testExpressionTranslationWithContext("xs:time(PathNode/TextField/normalize-space(text()))", "ND-Root", "time(BT-00-Text)"); + testExpressionTranslationWithContext("xs:time(PathNode/TextField/normalize-space(text()))", + "ND-Root", "time(BT-00-Text)"); } // #endregion: Time functions @@ -992,69 +1260,87 @@ void testTimeFromStringFunction() { @Test void testDistinctValuesFunction_WithStringSequences() { - testExpressionTranslationWithContext("distinct-values(('one','two','one'))", "ND-Root", "distinct-values(('one', 'two', 'one'))"); + testExpressionTranslationWithContext("distinct-values(('one','two','one'))", "ND-Root", + "distinct-values(('one', 'two', 'one'))"); } @Test void testDistinctValuesFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values((1,2,3,2,3,4))", "ND-Root", "distinct-values((1, 2, 3, 2, 3, 4))"); + testExpressionTranslationWithContext("distinct-values((1,2,3,2,3,4))", "ND-Root", + "distinct-values((1, 2, 3, 2, 3, 4))"); } @Test void testDistinctValuesFunction_WithDateSequences() { - testExpressionTranslationWithContext("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", "ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))"); + testExpressionTranslationWithContext( + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'),xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))", + "ND-Root", "distinct-values((2018-01-01Z, 2020-01-01Z, 2018-01-01Z, 2022-01-02Z))"); } @Test void testDistinctValuesFunction_WithTimeSequences() { - testExpressionTranslationWithContext("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); + testExpressionTranslationWithContext( + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')))", + "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); } @Test void testDistinctValuesFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("distinct-values((true(),false(),false(),false()))", "ND-Root", "distinct-values((TRUE, FALSE, FALSE, NEVER))"); + testExpressionTranslationWithContext("distinct-values((true(),false(),false(),false()))", + "ND-Root", "distinct-values((TRUE, FALSE, FALSE, NEVER))"); } @Test void testDistinctValuesFunction_WithFieldReferences() { - testExpressionTranslationWithContext("distinct-values(PathNode/TextField)", "ND-Root", "distinct-values(BT-00-Text)"); + testExpressionTranslationWithContext("distinct-values(PathNode/TextField)", "ND-Root", + "distinct-values(BT-00-Text)"); } // #region: Union @Test void testUnionFunction_WithStringSequences() { - testExpressionTranslationWithContext("distinct-values((('one','two'), ('two','three','four')))", "ND-Root", "value-union(('one', 'two'), ('two', 'three', 'four'))"); + testExpressionTranslationWithContext("distinct-values((('one','two'), ('two','three','four')))", + "ND-Root", "value-union(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testUnionFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values(((1,2,3), (2,3,4)))", "ND-Root", "value-union((1, 2, 3), (2, 3, 4))"); + testExpressionTranslationWithContext("distinct-values(((1,2,3), (2,3,4)))", "ND-Root", + "value-union((1, 2, 3), (2, 3, 4))"); } @Test void testUnionFunction_WithDateSequences() { - testExpressionTranslationWithContext("distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", "ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + testExpressionTranslationWithContext( + "distinct-values(((xs:date('2018-01-01Z'),xs:date('2020-01-01Z')), (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", + "ND-Root", "value-union((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testUnionFunction_WithTimeSequences() { - testExpressionTranslationWithContext("distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + testExpressionTranslationWithContext( + "distinct-values(((xs:time('12:00:00Z'),xs:time('13:00:00Z')), (xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", + "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } @Test void testUnionFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("distinct-values(((true(),false()), (false(),false())))", "ND-Root", "value-union((TRUE, FALSE), (FALSE, NEVER))"); + testExpressionTranslationWithContext("distinct-values(((true(),false()), (false(),false())))", + "ND-Root", "value-union((TRUE, FALSE), (FALSE, NEVER))"); } @Test void testUnionFunction_WithFieldReferences() { - testExpressionTranslationWithContext("distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", "value-union(BT-00-Text, BT-00-Text)"); + testExpressionTranslationWithContext( + "distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", + "value-union(BT-00-Text, BT-00-Text)"); } @Test void testUnionFunction_WithTypeMismatch() { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "value-union(BT-00-Text, BT-00-Number)")); + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", "value-union(BT-00-Text, BT-00-Number)")); } // #endregion: Union @@ -1063,37 +1349,48 @@ void testUnionFunction_WithTypeMismatch() { @Test void testIntersectFunction_WithStringSequences() { - testExpressionTranslationWithContext("distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); + testExpressionTranslationWithContext( + "distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", + "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testIntersectFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values((1,2,3)[.= (2,3,4)])", "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); + testExpressionTranslationWithContext("distinct-values((1,2,3)[.= (2,3,4)])", "ND-Root", + "value-intersect((1, 2, 3), (2, 3, 4))"); } @Test void testIntersectFunction_WithDateSequences() { - testExpressionTranslationWithContext("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + testExpressionTranslationWithContext( + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", + "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testIntersectFunction_WithTimeSequences() { - testExpressionTranslationWithContext("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + testExpressionTranslationWithContext( + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", + "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } @Test void testIntersectFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("distinct-values((true(),false())[.= (false(),false())])", "ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); + testExpressionTranslationWithContext("distinct-values((true(),false())[.= (false(),false())])", + "ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); } @Test void testIntersectFunction_WithFieldReferences() { - testExpressionTranslationWithContext("distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", "value-intersect(BT-00-Text, BT-00-Text)"); + testExpressionTranslationWithContext( + "distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", + "value-intersect(BT-00-Text, BT-00-Text)"); } @Test void testIntersectFunction_WithTypeMismatch() { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "value-intersect(BT-00-Text, BT-00-Number)")); + assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", + "value-intersect(BT-00-Text, BT-00-Number)")); } // #endregion: Intersect @@ -1102,37 +1399,49 @@ void testIntersectFunction_WithTypeMismatch() { @Test void testExceptFunction_WithStringSequences() { - testExpressionTranslationWithContext("distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))"); + testExpressionTranslationWithContext( + "distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", + "value-except(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testExceptFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values((1,2,3)[not(. = (2,3,4))])", "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); + testExpressionTranslationWithContext("distinct-values((1,2,3)[not(. = (2,3,4))])", "ND-Root", + "value-except((1, 2, 3), (2, 3, 4))"); } @Test void testExceptFunction_WithDateSequences() { - testExpressionTranslationWithContext("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + testExpressionTranslationWithContext( + "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", + "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testExceptFunction_WithTimeSequences() { - testExpressionTranslationWithContext("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + testExpressionTranslationWithContext( + "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", + "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } @Test void testExceptFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))"); + testExpressionTranslationWithContext( + "distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", + "value-except((TRUE, FALSE), (FALSE, NEVER))"); } @Test void testExceptFunction_WithFieldReferences() { - testExpressionTranslationWithContext("distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", "value-except(BT-00-Text, BT-00-Text)"); + testExpressionTranslationWithContext( + "distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", + "value-except(BT-00-Text, BT-00-Text)"); } @Test void testExceptFunction_WithTypeMismatch() { - assertThrows(ParseCancellationException.class, () -> translateExpressionWithContext("ND-Root", "value-except(BT-00-Text, BT-00-Number)")); + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", "value-except(BT-00-Text, BT-00-Number)")); } // #endregion: Except @@ -1141,47 +1450,62 @@ void testExceptFunction_WithTypeMismatch() { @Test void testSequenceEqualFunction_WithStringSequences() { - testExpressionTranslationWithContext("deep-equal(sort(('one','two')), sort(('two','three','four')))", "ND-Root", "sequence-equal(('one', 'two'), ('two', 'three', 'four'))"); + testExpressionTranslationWithContext( + "deep-equal(sort(('one','two')), sort(('two','three','four')))", "ND-Root", + "sequence-equal(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testSequenceEqualFunction_WithNumberSequences() { - testExpressionTranslationWithContext("deep-equal(sort((1,2,3)), sort((2,3,4)))", "ND-Root", "sequence-equal((1, 2, 3), (2, 3, 4))"); + testExpressionTranslationWithContext("deep-equal(sort((1,2,3)), sort((2,3,4)))", "ND-Root", + "sequence-equal((1, 2, 3), (2, 3, 4))"); } @Test void testSequenceEqualFunction_WithDateSequences() { - testExpressionTranslationWithContext("deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", "ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); + testExpressionTranslationWithContext( + "deep-equal(sort((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))), sort((xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))))", + "ND-Root", "sequence-equal((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testSequenceEqualFunction_WithTimeSequences() { - testExpressionTranslationWithContext("deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", "ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); + testExpressionTranslationWithContext( + "deep-equal(sort((xs:time('12:00:00Z'),xs:time('13:00:00Z'))), sort((xs:time('12:00:00Z'),xs:time('14:00:00Z'))))", + "ND-Root", "sequence-equal((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } @Test void testSequenceEqualFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("deep-equal(sort((true(),false())), sort((false(),false())))", "ND-Root", "sequence-equal((TRUE, FALSE), (FALSE, NEVER))"); + testExpressionTranslationWithContext( + "deep-equal(sort((true(),false())), sort((false(),false())))", "ND-Root", + "sequence-equal((TRUE, FALSE), (FALSE, NEVER))"); } @Test void testSequenceEqualFunction_WithDurationSequences() { - testExpressionTranslationWithContext("deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", "ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))"); + testExpressionTranslationWithContext( + "deep-equal(sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))), sort((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P3Y'))))", + "ND-Root", "sequence-equal((P1Y, P2Y), (P1Y, P3Y))"); } @Test void testSequenceEqualFunction_WithFieldReferences() { - testExpressionTranslationWithContext("deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)"); + testExpressionTranslationWithContext( + "deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", + "sequence-equal(BT-00-Text, BT-00-Text)"); } @Test void testParameterizedExpression_WithStringParameter() { - testExpressionTranslation("'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "'hello'", "'world'"); + testExpressionTranslation("'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", + "'hello'", "'world'"); } @Test void testParameterizedExpression_WithUnquotedStringParameter() { - assertThrows(ParseCancellationException.class, () -> translateExpression("{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); + assertThrows(ParseCancellationException.class, + () -> translateExpression("{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", "hello", "world")); } @Test @@ -1191,22 +1515,27 @@ void testParameterizedExpression_WithNumberParameter() { @Test void testParameterizedExpression_WithDateParameter() { - testExpressionTranslation("xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); + testExpressionTranslation("xs:date('2018-01-01Z') = xs:date('2020-01-01Z')", + "{ND-Root, date:$p1, date:$p2} ${$p1 == $p2}", "2018-01-01Z", "2020-01-01Z"); } @Test void testParameterizedExpression_WithTimeParameter() { - testExpressionTranslation("xs:time('12:00:00Z') = xs:time('13:00:00Z')", "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); + testExpressionTranslation("xs:time('12:00:00Z') = xs:time('13:00:00Z')", + "{ND-Root, time:$p1, time:$p2} ${$p1 == $p2}", "12:00:00Z", "13:00:00Z"); } @Test void testParameterizedExpression_WithBooleanParameter() { - testExpressionTranslation("true() = false()", "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); + testExpressionTranslation("true() = false()", + "{ND-Root, indicator:$p1, indicator:$p2} ${$p1 == $p2}", "ALWAYS", "FALSE"); } @Test void testParameterizedExpression_WithDurationParameter() { - testExpressionTranslation("boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); + testExpressionTranslation( + "boolean(for $T in (current-date()) return ($T + xs:yearMonthDuration('P1Y') = $T + xs:yearMonthDuration('P2Y')))", + "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); } // #endregion: Compare sequences @@ -1222,7 +1551,9 @@ void testIndexer_WithFieldReference() { @Test void testIndexer_WithFieldReferenceAndPredicate() { - testExpressionTranslationWithContext("PathNode/TextField[./normalize-space(text()) = 'hello'][1]", "ND-Root", "text:BT-00-Text[BT-00-Text == 'hello'][1]"); + testExpressionTranslationWithContext( + "PathNode/TextField[./normalize-space(text()) = 'hello'][1]", "ND-Root", + "text:BT-00-Text[BT-00-Text == 'hello'][1]"); } @Test From 880938077f17a0aec54cd50441fe8af4f25d05ba Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sat, 22 Apr 2023 09:30:11 +0200 Subject: [PATCH 20/57] Fixed imports and updated pom.xml --- pom.xml | 2 +- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 18 +----------- .../efx/sdk2/EfxExpressionTranslatorV2.java | 2 -- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 29 +------------------ 4 files changed, 3 insertions(+), 48 deletions(-) diff --git a/pom.xml b/pom.xml index e359fe1f..a4f63aec 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ ${project.build.directory}/eforms-sdk/antlr4 - 1.0.2-SNAPSHOT + 1.0.4-SNAPSHOT 4.9.3 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 b056341c..0f5b7523 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -31,23 +31,7 @@ import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Markup; import eu.europa.ted.efx.model.VariableList; -import eu.europa.ted.efx.sdk1.EfxParser.AssetIdContext; -import eu.europa.ted.efx.sdk1.EfxParser.AssetTypeContext; -import eu.europa.ted.efx.sdk1.EfxParser.ContextDeclarationBlockContext; -import eu.europa.ted.efx.sdk1.EfxParser.ExpressionTemplateContext; -import eu.europa.ted.efx.sdk1.EfxParser.LabelTemplateContext; -import eu.europa.ted.efx.sdk1.EfxParser.LabelTypeContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandBtLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldValueReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandLabelReferenceFromContextContext; -import eu.europa.ted.efx.sdk1.EfxParser.StandardExpressionBlockContext; -import eu.europa.ted.efx.sdk1.EfxParser.StandardLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.TemplateFileContext; -import eu.europa.ted.efx.sdk1.EfxParser.TemplateLineContext; -import eu.europa.ted.efx.sdk1.EfxParser.TextTemplateContext; +import eu.europa.ted.efx.sdk1.EfxParser.*; import eu.europa.ted.efx.xpath.XPathAttributeLocator; /** diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 5d6ce1b8..4e905962 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -45,8 +45,6 @@ import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Expression.TimeExpression; import eu.europa.ted.efx.model.Expression.TimeListExpression; -import eu.europa.ted.efx.sdk1.EfxExpressionTranslatorV1; -import eu.europa.ted.efx.sdk1.EfxTemplateTranslatorV1; import eu.europa.ted.efx.sdk2.EfxParser.*; import eu.europa.ted.efx.xpath.XPathAttributeLocator; 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 fb86daab..17773081 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -44,33 +44,7 @@ import eu.europa.ted.efx.model.TimeVariable; import eu.europa.ted.efx.model.Variable; import eu.europa.ted.efx.model.VariableList; -import eu.europa.ted.efx.sdk1.EfxExpressionTranslatorV1; -import eu.europa.ted.efx.sdk2.EfxParser.AssetIdContext; -import eu.europa.ted.efx.sdk2.EfxParser.AssetTypeContext; -import eu.europa.ted.efx.sdk2.EfxParser.BooleanVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationBlockContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.DateVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.DurationVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.ExpressionTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.LabelTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.LabelTypeContext; -import eu.europa.ted.efx.sdk2.EfxParser.NumericVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandBtLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandFieldValueReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandIndirectLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandIndirectLabelReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandLabelReferenceFromContextContext; -import eu.europa.ted.efx.sdk2.EfxParser.StandardExpressionBlockContext; -import eu.europa.ted.efx.sdk2.EfxParser.StandardLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.StringVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateFileContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateLineContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateVariableListContext; -import eu.europa.ted.efx.sdk2.EfxParser.TextTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.TimeVariableInitializerContext; +import eu.europa.ted.efx.sdk2.EfxParser.*; import eu.europa.ted.efx.xpath.XPathAttributeLocator; import eu.europa.ted.efx.xpath.XPathContextualizer; @@ -330,7 +304,6 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.composeVariableReference("item", StringExpression.class); switch (fieldType) { case "indicator": - this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( List.of( From 81f595ca30047a4f93b509fd349debb436bd84c8 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sun, 23 Apr 2023 02:30:13 +0200 Subject: [PATCH 21/57] Resolved merge final issues --- .github/workflows/build.yml | 21 ++ .github/workflows/publish.yml | 1 + .../antlr4/eu/europa/ted/efx/xpath/XPath20.g4 | 11 +- .../ted/eforms/sdk/ComponentFactory.java | 10 +- .../java/eu/europa/ted/efx/EfxTranslator.java | 55 +++-- .../europa/ted/efx/EfxTranslatorOptions.java | 22 ++ .../efx/component/EfxTranslatorFactory.java | 11 +- .../TranslatorDependencyFactory.java | 4 +- .../ted/efx/interfaces/TranslatorOptions.java | 7 + .../europa/ted/efx/model/DecimalFormat.java | 69 +++++++ .../eu/europa/ted/efx/model/Expression.java | 32 +++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 2 +- .../ted/efx/xpath/XPathContextualizer.java | 156 ++++++++++++-- .../ted/efx/xpath/XPathScriptGenerator.java | 78 +++---- .../java/eu/europa/ted/efx/EfxTestsBase.java | 3 +- .../ted/efx/mock/DependencyFactoryMock.java | 8 +- .../efx/sdk1/EfxExpressionCombinedV1Test.java | 6 +- .../sdk1/EfxExpressionTranslatorV1Test.java | 194 +++++++++++++----- .../efx/sdk1/EfxTemplateTranslatorV1Test.java | 4 +- .../efx/sdk2/EfxExpressionCombinedV2Test.java | 6 +- .../sdk2/EfxExpressionTranslatorV2Test.java | 190 +++++++++++------ .../efx/sdk2/EfxTemplateTranslatorV2Test.java | 14 +- 22 files changed, 685 insertions(+), 219 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java create mode 100644 src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java create mode 100644 src/main/java/eu/europa/ted/efx/model/DecimalFormat.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..1ea3c903 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,21 @@ +name: Build the project +on: + push: + + # Allows to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'adopt' + - name: Build package + run: mvn --batch-mode clean install diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f56a6a71..7752c8ab 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ on: push: branches: - 'develop' + - 'main' release: types: [created] diff --git a/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4 b/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4 index 50e996ed..893ef349 100644 --- a/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4 +++ b/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4 @@ -4,13 +4,9 @@ // // This is a faithful implementation of the XPath version 2.0 grammar // from the spec at https://www.w3.org/TR/xpath20/ -// -// Note: Some minor adoptations were done -// to simplify the translator using this grammar. grammar XPath20; - // [1] xpath : expr EOF ; expr : exprsingle ( COMMA exprsingle)* ; @@ -43,8 +39,8 @@ nodecomp : KW_IS | LL | GG ; // [25] pathexpr : ( SLASH relativepathexpr?) | ( SS relativepathexpr) | relativepathexpr ; relativepathexpr : stepexpr (( SLASH | SS) stepexpr)* ; -stepexpr : step predicatelist; -step: primaryexpr | reversestep | forwardstep; +stepexpr : filterexpr | axisstep ; +axisstep : (reversestep | forwardstep) predicatelist ; forwardstep : (forwardaxis nodetest) | abbrevforwardstep ; // [30] forwardaxis : ( KW_CHILD COLONCOLON) | ( KW_DESCENDANT COLONCOLON) | ( KW_ATTRIBUTE COLONCOLON) | ( KW_SELF COLONCOLON) | ( KW_DESCENDANT_OR_SELF COLONCOLON) | ( KW_FOLLOWING_SIBLING COLONCOLON) | ( KW_FOLLOWING COLONCOLON) | ( KW_NAMESPACE COLONCOLON) ; @@ -56,6 +52,7 @@ abbrevreversestep : DD ; nodetest : kindtest | nametest ; nametest : qname | wildcard ; wildcard : STAR | (NCName CS) | ( SC NCName) ; +filterexpr : primaryexpr predicatelist ; predicatelist : predicate* ; // [40] predicate : OB expr CB ; @@ -343,4 +340,4 @@ fragment FragChar : '\u0009' | '\u000a' | '\u000d' Whitespace : ('\u000d' | '\u000a' | '\u0020' | '\u0009')+ -> skip ; // Not per spec. Specified for testing. -SEMI : ';' ; +SEMI : ';' ; \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java index 736fc563..a88dd0fc 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java @@ -4,11 +4,13 @@ import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; + import eu.europa.ted.eforms.sdk.component.SdkComponentFactory; import eu.europa.ted.eforms.sdk.component.SdkComponentType; 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.TranslatorOptions; public class ComponentFactory extends SdkComponentFactory { public static final ComponentFactory INSTANCE = new ComponentFactory(); @@ -43,15 +45,15 @@ public static SymbolResolver getSymbolResolver(final String sdkVersion, final Pa }); } - public static MarkupGenerator getMarkupGenerator(final String sdkVersion) + public static MarkupGenerator getMarkupGenerator(final String sdkVersion, TranslatorOptions options) throws InstantiationException { return ComponentFactory.INSTANCE.getComponentImpl(sdkVersion, - SdkComponentType.MARKUP_GENERATOR, MarkupGenerator.class); + SdkComponentType.MARKUP_GENERATOR, MarkupGenerator.class, options); } - public static ScriptGenerator getScriptGenerator(final String sdkVersion) + public static ScriptGenerator getScriptGenerator(final String sdkVersion, TranslatorOptions options) throws InstantiationException { return ComponentFactory.INSTANCE.getComponentImpl(sdkVersion, - SdkComponentType.SCRIPT_GENERATOR, ScriptGenerator.class); + SdkComponentType.SCRIPT_GENERATOR, ScriptGenerator.class, options); } } diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslator.java b/src/main/java/eu/europa/ted/efx/EfxTranslator.java index e69c1df0..c7a86a70 100644 --- a/src/main/java/eu/europa/ted/efx/EfxTranslator.java +++ b/src/main/java/eu/europa/ted/efx/EfxTranslator.java @@ -18,12 +18,15 @@ import java.nio.file.Path; import eu.europa.ted.efx.component.EfxTranslatorFactory; import eu.europa.ted.efx.interfaces.TranslatorDependencyFactory; +import eu.europa.ted.efx.interfaces.TranslatorOptions; /** * Provided for convenience, this class exposes static methods that allow you to quickly instantiate * an EFX translator to translate EFX expressions and templates. */ public class EfxTranslator { + + private static TranslatorOptions defaultOptions = EfxTranslatorOptions.DEFAULT; /** * Instantiates an EFX expression translator and translates a given expression. @@ -38,13 +41,18 @@ public class EfxTranslator { * {@link TranslatorDependencyFactory}. * @throws InstantiationException */ - public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, - final String sdkVersion, - final String expression, final String... expressionParameters) throws InstantiationException { - return EfxTranslatorFactory.getEfxExpressionTranslator(sdkVersion, dependencyFactory) + public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final String expression, TranslatorOptions options, final String... expressionParameters) + throws InstantiationException { + return EfxTranslatorFactory.getEfxExpressionTranslator(sdkVersion, dependencyFactory, options) .translateExpression(expression, expressionParameters); } + public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final String expression, final String... expressionParameters) throws InstantiationException { + return translateExpression(dependencyFactory, sdkVersion, expression, defaultOptions, expressionParameters); + } + /** * Instantiates an EFX template translator and translates the EFX template contained in the given * file. @@ -59,14 +67,19 @@ public static String translateExpression(final TranslatorDependencyFactory depen * @throws IOException * @throws InstantiationException */ - public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, - final String sdkVersion, - final Path pathname) + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final Path pathname, TranslatorOptions options) throws IOException, InstantiationException { - return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory) + return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory, options) .renderTemplate(pathname); } + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final Path pathname) + throws IOException, InstantiationException { + return translateTemplate(dependencyFactory, sdkVersion, pathname, defaultOptions); + } + /** * Instantiates an EFX template translator and translates the given EFX template. * @@ -79,14 +92,19 @@ public static String translateTemplate(final TranslatorDependencyFactory depende * {@link TranslatorDependencyFactory}. * @throws InstantiationException */ - public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, - final String sdkVersion, - final String template) + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final String template, TranslatorOptions options) throws InstantiationException { - return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory) + return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory, options) .renderTemplate(template); } + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final String template) + throws InstantiationException { + return translateTemplate(dependencyFactory, sdkVersion, template, defaultOptions); + } + /** * Instantiates an EFX template translator and translates the EFX template contained in the given * InputStream. @@ -101,11 +119,16 @@ public static String translateTemplate(final TranslatorDependencyFactory depende * @throws IOException * @throws InstantiationException */ - public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, - final String sdkVersion, - final InputStream stream) + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final InputStream stream, TranslatorOptions options) throws IOException, InstantiationException { - return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory) + return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory, options) .renderTemplate(stream); } + + public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, + final InputStream stream) + throws IOException, InstantiationException { + return translateTemplate(dependencyFactory, sdkVersion, stream, defaultOptions); + } } diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java new file mode 100644 index 00000000..61aafbf1 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java @@ -0,0 +1,22 @@ +package eu.europa.ted.efx; + +import eu.europa.ted.efx.interfaces.TranslatorOptions; +import eu.europa.ted.efx.model.DecimalFormat; + +public class EfxTranslatorOptions implements TranslatorOptions { + + // Change to EfxDecimalFormatSymbols.EFX_DEFAULT to use the decimal format + // preferred by OP (space as thousands separator and comma as decimal separator). + public static final EfxTranslatorOptions DEFAULT = new EfxTranslatorOptions(DecimalFormat.XSL_DEFAULT); + + private final DecimalFormat symbols; + + public EfxTranslatorOptions(DecimalFormat symbols) { + this.symbols = symbols; + } + + @Override + public DecimalFormat getDecimalFormat() { + return this.symbols; + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java b/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java index f3e7313b..e40ae6bb 100644 --- a/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java +++ b/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java @@ -5,6 +5,7 @@ import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; import eu.europa.ted.efx.interfaces.EfxTemplateTranslator; import eu.europa.ted.efx.interfaces.TranslatorDependencyFactory; +import eu.europa.ted.efx.interfaces.TranslatorOptions; public class EfxTranslatorFactory extends SdkComponentFactory { public static final EfxTranslatorFactory INSTANCE = new EfxTranslatorFactory(); @@ -14,18 +15,18 @@ private EfxTranslatorFactory() { } public static EfxExpressionTranslator getEfxExpressionTranslator(final String sdkVersion, - final TranslatorDependencyFactory factory) throws InstantiationException { + final TranslatorDependencyFactory factory, TranslatorOptions options) throws InstantiationException { return EfxTranslatorFactory.INSTANCE.getComponentImpl(sdkVersion, SdkComponentType.EFX_EXPRESSION_TRANSLATOR, EfxExpressionTranslator.class, - factory.createSymbolResolver(sdkVersion), factory.createScriptGenerator(sdkVersion), + factory.createSymbolResolver(sdkVersion), factory.createScriptGenerator(sdkVersion, options), factory.createErrorListener()); } public static EfxTemplateTranslator getEfxTemplateTranslator(final String sdkVersion, - final TranslatorDependencyFactory factory) throws InstantiationException { + final TranslatorDependencyFactory factory, TranslatorOptions options) throws InstantiationException { return EfxTranslatorFactory.INSTANCE.getComponentImpl(sdkVersion, SdkComponentType.EFX_TEMPLATE_TRANSLATOR, EfxTemplateTranslator.class, - factory.createMarkupGenerator(sdkVersion), factory.createSymbolResolver(sdkVersion), - factory.createScriptGenerator(sdkVersion), factory.createErrorListener()); + factory.createMarkupGenerator(sdkVersion, options), factory.createSymbolResolver(sdkVersion), + factory.createScriptGenerator(sdkVersion, options), factory.createErrorListener()); } } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java index f7b27407..fa55feb7 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java @@ -52,7 +52,7 @@ public interface TranslatorDependencyFactory { * language features that ScriptGenerator instance should be able to handle. * @return An instance of ScriptGenerator to be used by the EFX translator. */ - public ScriptGenerator createScriptGenerator(String sdkVersion); + public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options); /** * Creates a MarkupGenerator instance. @@ -65,7 +65,7 @@ public interface TranslatorDependencyFactory { * language features that MarkupGenerator instance should be able to handle. * @return The instance of MarkupGenerator to be used by the EFX translator. */ - public MarkupGenerator createMarkupGenerator(String sdkVersion); + public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options); /** * Creates an error listener instance. diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java new file mode 100644 index 00000000..b0074b52 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java @@ -0,0 +1,7 @@ +package eu.europa.ted.efx.interfaces; + +import eu.europa.ted.efx.model.DecimalFormat; + +public interface TranslatorOptions { + public DecimalFormat getDecimalFormat(); +} diff --git a/src/main/java/eu/europa/ted/efx/model/DecimalFormat.java b/src/main/java/eu/europa/ted/efx/model/DecimalFormat.java new file mode 100644 index 00000000..001060f1 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/DecimalFormat.java @@ -0,0 +1,69 @@ +package eu.europa.ted.efx.model; + +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +public class DecimalFormat extends DecimalFormatSymbols { + + public final static DecimalFormat XSL_DEFAULT = DecimalFormat.getXslDefault(); + public final static DecimalFormat EFX_DEFAULT = DecimalFormat.getEfxDefault(); + + DecimalFormat(Locale locale) { + super(locale); + } + + private static DecimalFormat getXslDefault() { + DecimalFormat symbols = new DecimalFormat(Locale.US); + symbols.setDecimalSeparator('.'); + symbols.setGroupingSeparator(','); + symbols.setMinusSign('-'); + symbols.setPercent('%'); + symbols.setPerMill('‰'); + symbols.setZeroDigit('0'); + symbols.setDigit('#'); + symbols.setPatternSeparator(';'); + symbols.setInfinity("Infinity"); + symbols.setNaN("NaN"); + return symbols; + } + + private static DecimalFormat getEfxDefault() { + DecimalFormat symbols = DecimalFormat.getXslDefault(); + symbols.setDecimalSeparator(','); + symbols.setGroupingSeparator(' '); + return symbols; + } + + public String adaptFormatString(final String originalString) { + + final char decimalSeparatorPlaceholder = '\uE000'; + final char groupingSeparatorPlaceholder = '\uE001'; + final char minusSignPlaceholder = '\uE002'; + final char percentPlaceholder = '\uE003'; + final char perMillePlaceholder = '\uE004'; + final char zeroDigitPlaceholder = '\uE005'; + final char digitPlaceholder = '\uE006'; + final char patternSeparatorPlaceholder = '\uE007'; + + String adaptedString = originalString; + adaptedString = adaptedString.replace(XSL_DEFAULT.getDecimalSeparator(), decimalSeparatorPlaceholder); + adaptedString = adaptedString.replace(XSL_DEFAULT.getGroupingSeparator(), groupingSeparatorPlaceholder); + adaptedString = adaptedString.replace(XSL_DEFAULT.getMinusSign(), minusSignPlaceholder); + adaptedString = adaptedString.replace(XSL_DEFAULT.getPercent(), percentPlaceholder); + adaptedString = adaptedString.replace(XSL_DEFAULT.getPerMill(), perMillePlaceholder); + adaptedString = adaptedString.replace(XSL_DEFAULT.getZeroDigit(), zeroDigitPlaceholder); + adaptedString = adaptedString.replace(XSL_DEFAULT.getDigit(), digitPlaceholder); + adaptedString = adaptedString.replace(XSL_DEFAULT.getPatternSeparator(), patternSeparatorPlaceholder); + + adaptedString = adaptedString.replace(decimalSeparatorPlaceholder, this.getDecimalSeparator()); + adaptedString = adaptedString.replace(groupingSeparatorPlaceholder, this.getGroupingSeparator()); + adaptedString = adaptedString.replace(minusSignPlaceholder, this.getMinusSign()); + adaptedString = adaptedString.replace(percentPlaceholder, this.getPercent()); + adaptedString = adaptedString.replace(perMillePlaceholder, this.getPerMill()); + adaptedString = adaptedString.replace(zeroDigitPlaceholder, this.getZeroDigit()); + adaptedString = adaptedString.replace(digitPlaceholder, this.getDigit()); + adaptedString = adaptedString.replace(patternSeparatorPlaceholder, this.getPatternSeparator()); + + return adaptedString; + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java index 20782a0d..4f30fdaf 100644 --- a/src/main/java/eu/europa/ted/efx/model/Expression.java +++ b/src/main/java/eu/europa/ted/efx/model/Expression.java @@ -67,8 +67,16 @@ public class Expression extends CallStackObject { */ public final String script; + public final Boolean isLiteral; + public Expression(final String script) { this.script = script; + this.isLiteral = false; + } + + public Expression(final String script, final Boolean isLiteral) { + this.script = script; + this.isLiteral = isLiteral; } public static T instantiate(String script, Class type) { @@ -125,6 +133,10 @@ public static class BooleanExpression extends Expression { public BooleanExpression(final String script) { super(script); } + + public BooleanExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral); + } } /** @@ -135,6 +147,10 @@ public static class NumericExpression extends Expression { public NumericExpression(final String script) { super(script); } + + public NumericExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral); + } } /** @@ -145,6 +161,10 @@ public static class StringExpression extends Expression { public StringExpression(final String script) { super(script); } + + public StringExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral); + } } /** @@ -155,6 +175,10 @@ public static class DateExpression extends Expression { public DateExpression(final String script) { super(script); } + + public DateExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral); + } } /** @@ -165,6 +189,10 @@ public static class TimeExpression extends Expression { public TimeExpression(final String script) { super(script); } + + public TimeExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral); + } } /** @@ -175,6 +203,10 @@ public static class DurationExpression extends Expression { public DurationExpression(final String script) { super(script); } + + public DurationExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral); + } } /** diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 4e905962..2a6b03a4 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1344,7 +1344,7 @@ public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { @Override public void exitCountFunction(CountFunctionContext ctx) { - ListExpression expression = this.stack.pop(ListExpression.class); + final ListExpression expression = this.stack.pop(ListExpression.class); this.stack.push(this.script.composeCountOperation(expression)); } diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java index 0cd6c8f6..7e15923d 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -9,6 +9,7 @@ import java.util.Queue; import java.util.function.Function; import java.util.stream.Collectors; + import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -16,22 +17,37 @@ import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; + import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.xpath.XPath20Parser.AxisstepContext; +import eu.europa.ted.efx.xpath.XPath20Parser.FilterexprContext; import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext; -import eu.europa.ted.efx.xpath.XPath20Parser.StepexprContext; public class XPathContextualizer extends XPath20BaseListener { private final CharStream inputStream; - private final Queue steps = new LinkedList<>(); + private final LinkedList steps = new LinkedList<>(); public XPathContextualizer(CharStream inputStream) { this.inputStream = inputStream; } + /** + * Parses the XPath represented by th e given {@link PathExpression}} and + * returns a queue containing a {@link StepInfo} object for each step that the + * XPath is comprised of. + */ private static Queue getSteps(PathExpression xpath) { + return getSteps(xpath.script); + } + + /** + * Parses the given xpath and returns a queue containing a {@link StepInfo} for + * each step that the XPath is comprised of. + */ + private static Queue getSteps(String xpath) { - final CharStream inputStream = CharStreams.fromString(xpath.script); + final CharStream inputStream = CharStreams.fromString(xpath); final XPath20Lexer lexer = new XPath20Lexer(inputStream); final CommonTokenStream tokens = new CommonTokenStream(lexer); final XPath20Parser parser = new XPath20Parser(tokens); @@ -44,7 +60,12 @@ private static Queue getSteps(PathExpression xpath) { return contextualizer.steps; } - + /** + * Makes the given xpath relative to the given context xpath. + * @param contextXpath + * @param xpath + * @return + */ public static PathExpression contextualize(final PathExpression contextXpath, final PathExpression xpath) { @@ -60,6 +81,57 @@ public static PathExpression contextualize(final PathExpression contextXpath, return getContextualizedXpath(contextSteps, pathSteps); } + public static PathExpression addPredicate(final PathExpression pathExpression, final String predicate) { + return new PathExpression(addPredicate(pathExpression.script, predicate)); + } + + /** + * Attempts to add a predicate to the given xpath. + * It will add the predicate to the last axis-step in the xpath. + * If there is no axis-step in the xpath then it will add the predicate to the last step. + * If the xpath is empty then it will still return a PathExpression but with an empty xpath. + */ + public static String addPredicate(final String xpath, final String predicate) { + if (predicate == null) { + return xpath; + } + + String _predicate = predicate.trim(); + + if (_predicate.isEmpty()) { + return xpath; + } + + if (!_predicate.startsWith("[")) { + _predicate = "[" + _predicate; + } + + if (!_predicate.endsWith("]")) { + _predicate = _predicate + "]"; + } + + LinkedList steps = new LinkedList<>(getSteps(xpath)); + + StepInfo lastAxisStep = getLastAxisStep(steps); + if (lastAxisStep != null) { + lastAxisStep.predicates.add(_predicate); + } else if (steps.size() > 0) { + steps.getLast().predicates.add(_predicate); + } + return steps.stream().map(s -> s.stepText + s.getPredicateText()).collect(Collectors.joining("/")); + } + + private static StepInfo getLastAxisStep(LinkedList steps) { + int i = steps.size() - 1; + while (i >= 0 && !AxisStepInfo.class.isInstance(steps.get(i))) { + i--; + } + if (i < 0) { + return null; + } + return steps.get(i); + } + public static PathExpression join(final PathExpression first, final PathExpression second) { if (first == null || first.script.trim().isEmpty()) { @@ -174,9 +246,44 @@ private Boolean inPredicateMode() { } @Override - public void exitStepexpr(StepexprContext ctx) { - if (!inPredicateMode()) { - this.steps.offer(new StepInfo(ctx, this::getInputText)); + public void exitAxisstep(AxisstepContext ctx) { + if (inPredicateMode()) { + return; + } + + // When we recognize a step, we add it to the queue if is is empty. + // If the queue is not empty, and the depth of the new step is not smaller than + // the depth of the last step in the queue, then this step needs to be added to + // the queue too. + // Otherwise, the last step in the queue is a sub-expression of the new step, + // and we need to + // replace it in the queue with the new step. + if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) { + this.steps.offer(new AxisStepInfo(ctx, this::getInputText)); + } else { + Interval removedInterval = ctx.getSourceInterval(); + while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) { + this.steps.removeLast(); + } + this.steps.offer(new AxisStepInfo(ctx, this::getInputText)); + } + } + + @Override + public void exitFilterexpr(FilterexprContext ctx) { + if (inPredicateMode()) { + return; + } + + // Same logic as for axis steps here (sse exitAxisstep). + if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) { + this.steps.offer(new FilterStepInfo(ctx, this::getInputText)); + } else { + Interval removedInterval = ctx.getSourceInterval(); + while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) { + this.steps.removeLast(); + } + this.steps.offer(new FilterStepInfo(ctx, this::getInputText)); } } @@ -190,14 +297,33 @@ public void exitPredicate(PredicateContext ctx) { this.predicateMode--; } - private class StepInfo { + public class AxisStepInfo extends StepInfo { + + public AxisStepInfo(AxisstepContext ctx, Function getInputText) { + super(ctx.reversestep() != null? getInputText.apply(ctx.reversestep()) : getInputText.apply(ctx.forwardstep()), + ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval()); + } + } + + public class FilterStepInfo extends StepInfo { + + public FilterStepInfo(FilterexprContext ctx, Function getInputText) { + super(getInputText.apply(ctx.primaryexpr()), + ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval()); + } + } + + public class StepInfo { String stepText; List predicates; - - public StepInfo(StepexprContext ctx, Function getInputText) { - this.stepText = getInputText.apply(ctx.step()); - this.predicates = - ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()); + int a; + int b; + + protected StepInfo(String stepText, List predicates, Interval interval) { + this.stepText = stepText; + this.predicates = predicates; + this.a = interval.a; + this.b = interval.b; } public Boolean isVariableStep() { @@ -241,5 +367,9 @@ public Boolean isTheSameAs(final StepInfo contextStep) { Collections.sort(contextPredicates); return pathPredicates.equals(contextPredicates); } + + public Boolean isPartOf(Interval interval) { + return this.a >= interval.a && this.b <= interval.b; + } } } diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index ea11a30c..6cfbb7b0 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -12,10 +12,13 @@ import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.ScriptGenerator; +import eu.europa.ted.efx.interfaces.TranslatorOptions; import eu.europa.ted.efx.model.Expression; import eu.europa.ted.efx.model.Expression.BooleanExpression; import eu.europa.ted.efx.model.Expression.DateExpression; +import eu.europa.ted.efx.model.Expression.DateListExpression; import eu.europa.ted.efx.model.Expression.DurationExpression; +import eu.europa.ted.efx.model.Expression.DurationListExpression; import eu.europa.ted.efx.model.Expression.IteratorExpression; import eu.europa.ted.efx.model.Expression.IteratorListExpression; import eu.europa.ted.efx.model.Expression.ListExpression; @@ -25,6 +28,7 @@ import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Expression.StringListExpression; import eu.europa.ted.efx.model.Expression.TimeExpression; +import eu.europa.ted.efx.model.Expression.TimeListExpression; @SdkComponent(versions = {"0.6", "0.7", "1", "2"}, componentType = SdkComponentType.SCRIPT_GENERATOR) @@ -45,6 +49,11 @@ public class XPathScriptGenerator implements ScriptGenerator { entry(">", ">"), // entry(">=", ">=")); + protected TranslatorOptions translatorOptions; + + public XPathScriptGenerator(TranslatorOptions translatorOptions) { + this.translatorOptions = translatorOptions; + } @Override public T composeNodeReferenceWithPredicate(PathExpression nodeReference, @@ -68,31 +77,30 @@ public T composeFieldReferenceWithAxis(final PathExpressi public T composeFieldValueReference(PathExpression fieldReference, Class type) { - if (StringExpression.class.isAssignableFrom(type)) { + if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) { return Expression.instantiate(fieldReference.script + "/normalize-space(text())", type); } - if (NumericExpression.class.isAssignableFrom(type)) { + if (NumericExpression.class.isAssignableFrom(type) || NumericListExpression.class.isAssignableFrom(type)) { return Expression.instantiate(fieldReference.script + "/number()", type); } - if (DateExpression.class.isAssignableFrom(type)) { + if (DateExpression.class.isAssignableFrom(type) || DateListExpression.class.isAssignableFrom(type)) { return Expression.instantiate(fieldReference.script + "/xs:date(text())", type); } - if (TimeExpression.class.isAssignableFrom(type)) { + if (TimeExpression.class.isAssignableFrom(type) || TimeListExpression.class.isAssignableFrom(type)) { return Expression.instantiate(fieldReference.script + "/xs:time(text())", type); } - if (DurationExpression.class.isAssignableFrom(type)) { - return Expression - .instantiate("(for $F in " + fieldReference.script + " return (if ($F/@unitCode='WEEK')" + // - " then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D'))" + // - " else if ($F/@unitCode='DAY')" + // - " then xs:dayTimeDuration(concat('P', $F/number(), 'D'))" + // - " else if ($F/@unitCode='YEAR')" + // - " then xs:yearMonthDuration(concat('P', $F/number(), 'Y'))" + // - " else if ($F/@unitCode='MONTH')" + // - " then xs:yearMonthDuration(concat('P', $F/number(), 'M'))" + // - // " else if (" + fieldReference.script + ")" + // - // " then fn:error('Invalid @unitCode')" + // - " else ()))", type); + if (DurationExpression.class.isAssignableFrom(type) || DurationListExpression.class.isAssignableFrom(type)) { + return Expression.instantiate("(for $F in " + fieldReference.script + " return (if ($F/@unitCode='WEEK')" + // + " then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D'))" + // + " else if ($F/@unitCode='DAY')" + // + " then xs:dayTimeDuration(concat('P', $F/number(), 'D'))" + // + " else if ($F/@unitCode='YEAR')" + // + " then xs:yearMonthDuration(concat('P', $F/number(), 'Y'))" + // + " else if ($F/@unitCode='MONTH')" + // + " then xs:yearMonthDuration(concat('P', $F/number(), 'M'))" + // + // " else if (" + fieldReference.script + ")" + // + // " then fn:error('Invalid @unitCode')" + // + " else ()))", type); } return Expression.instantiate(fieldReference.script, type); @@ -138,40 +146,40 @@ public > L composeList(List @Override public NumericExpression getNumericLiteralEquivalent(String literal) { - return new NumericExpression(literal); + return new NumericExpression(literal, true); } @Override public StringExpression getStringLiteralEquivalent(String literal) { - return new StringExpression(literal); + return new StringExpression(literal, true); } @Override public BooleanExpression getBooleanEquivalent(boolean value) { - return new BooleanExpression(value ? "true()" : "false()"); + return new BooleanExpression(value ? "true()" : "false()", true); } @Override public DateExpression getDateLiteralEquivalent(String literal) { - return new DateExpression("xs:date(" + quoted(literal) + ")"); + return new DateExpression("xs:date(" + quoted(literal) + ")", true); } @Override public TimeExpression getTimeLiteralEquivalent(String literal) { - return new TimeExpression("xs:time(" + quoted(literal) + ")"); + return new TimeExpression("xs:time(" + quoted(literal) + ")", true); } @Override public DurationExpression getDurationLiteralEquivalent(final String literal) { if (literal.contains("M") || literal.contains("Y")) { - return new DurationExpression("xs:yearMonthDuration(" + quoted(literal) + ")"); + return new DurationExpression("xs:yearMonthDuration(" + quoted(literal) + ")", true); } if (literal.contains("W")) { final int weeks = this.getWeeksFromDurationLiteral(literal); return new DurationExpression( - "xs:dayTimeDuration(" + quoted(String.format("P%dD", weeks * 7)) + ")"); + "xs:dayTimeDuration(" + quoted(String.format("P%dD", weeks * 7)) + ")", true); } - return new DurationExpression("xs:dayTimeDuration(" + quoted(literal) + ")"); + return new DurationExpression("xs:dayTimeDuration(" + quoted(literal) + ")", true); } @Override @@ -425,7 +433,8 @@ public StringExpression composeSubstringExtraction(StringExpression text, @Override public StringExpression composeToStringConversion(NumericExpression number) { - return new StringExpression("format-number(" + number.script + ", '0.##########')"); + String formatString = this.translatorOptions.getDecimalFormat().adaptFormatString("0.##########"); + return new StringExpression("format-number(" + number.script + ", '" + formatString + "')"); } @Override @@ -443,12 +452,13 @@ public StringExpression composeStringJoin(StringListExpression list, StringExpre @Override public StringExpression composeNumberFormatting(NumericExpression number, StringExpression format) { - return new StringExpression("format-number(" + number.script + ", " + format.script + ")"); + String formatString = format.isLiteral ? this.translatorOptions.getDecimalFormat().adaptFormatString(format.script) : format.script; + return new StringExpression("format-number(" + number.script + ", " + formatString + ")"); } @Override public StringExpression getStringLiteralFromUnquotedString(String value) { - return new StringExpression("'" + value + "'"); + return new StringExpression("'" + value + "'", true); } @@ -526,17 +536,13 @@ public > L composeUnionFunctio } @Override - public > L composeIntersectFunction(L listOne, - L listTwo, Class listType) { - return Expression.instantiate( - "distinct-values(" + listOne.script + "[.= " + listTwo.script + "])", listType); + public > L composeIntersectFunction(L listOne, L listTwo, Class listType) { + return Expression.instantiate("distinct-values(for $L1 in " + listOne.script + " return if (some $L2 in " + listTwo.script + " satisfies $L1 = $L2) then $L1 else ())", listType); } @Override - public > L composeExceptFunction(L listOne, - L listTwo, Class listType) { - return Expression.instantiate( - "distinct-values(" + listOne.script + "[not(. = " + listTwo.script + ")])", listType); + public > L composeExceptFunction(L listOne, L listTwo, Class listType) { + return Expression.instantiate("distinct-values(for $L1 in " + listOne.script + " return if (every $L2 in " + listTwo.script + " satisfies $L1 != $L2) then $L1 else ())", listType); } diff --git a/src/test/java/eu/europa/ted/efx/EfxTestsBase.java b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java index 992c8fec..f5f10827 100644 --- a/src/test/java/eu/europa/ted/efx/EfxTestsBase.java +++ b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import eu.europa.ted.efx.mock.DependencyFactoryMock; +import eu.europa.ted.efx.model.DecimalFormat; public abstract class EfxTestsBase { protected abstract String getSdkVersion(); @@ -23,7 +24,7 @@ protected String translateExpressionWithContext(final String context, final Stri protected String translateExpression(final String expression, final String... params) { try { return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, getSdkVersion(), - expression, params); + expression, new EfxTranslatorOptions(DecimalFormat.EFX_DEFAULT), params); } catch (InstantiationException e) { throw new RuntimeException(e); } diff --git a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java index 9c0c5690..cbdf59fe 100644 --- a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java @@ -1,11 +1,13 @@ package eu.europa.ted.efx.mock; import org.antlr.v4.runtime.BaseErrorListener; + import eu.europa.ted.efx.exceptions.ThrowingErrorListener; 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.TranslatorDependencyFactory; +import eu.europa.ted.efx.interfaces.TranslatorOptions; import eu.europa.ted.efx.xpath.XPathScriptGenerator; /** @@ -26,15 +28,15 @@ public SymbolResolver createSymbolResolver(String sdkVersion) { } @Override - public ScriptGenerator createScriptGenerator(String sdkVersion) { + public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options) { if (scriptGenerator == null) { - this.scriptGenerator = new XPathScriptGenerator(); + this.scriptGenerator = new XPathScriptGenerator(options); } return this.scriptGenerator; } @Override - public MarkupGenerator createMarkupGenerator(String sdkVersion) { + public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options) { if (this.markupGenerator == null) { this.markupGenerator = new MarkupGeneratorMock(); } diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java index 322d78f0..16052c63 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionCombinedV1Test.java @@ -26,20 +26,20 @@ void testNotPresentAndNotPresent() { @Test void testCountWithNodeContextOverride() { - testExpressionTranslationWithContext("count(../../PathNode/CodeField) = 1", "BT-00-Text", + testExpressionTranslationWithContext("count(../../PathNode/CodeField/normalize-space(text())) = 1", "BT-00-Text", "count(ND-Root::BT-00-Code) == 1"); } @Test void testCountWithAbsoluteFieldReference() { - testExpressionTranslationWithContext("count(/*/PathNode/CodeField) = 1", "BT-00-Text", + testExpressionTranslationWithContext("count(/*/PathNode/CodeField/normalize-space(text())) = 1", "BT-00-Text", "count(/BT-00-Code) == 1"); } @Test void testCountWithAbsoluteFieldReferenceAndPredicate() { testExpressionTranslationWithContext( - "count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1", "BT-00-Text", + "count(/*/PathNode/CodeField[../IndicatorField = true()]/normalize-space(text())) = 1", "BT-00-Text", "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); } } diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java index 004b13ef..51dfbd68 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -307,7 +307,7 @@ void testStringQuantifiedExpression_UsingLiterals() { @Test void testStringQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/TextField satisfies $x <= 'a'", + testExpressionTranslationWithContext("every $x in PathNode/TextField/normalize-space(text()) satisfies $x <= 'a'", "ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'"); } @@ -331,7 +331,7 @@ void testNumericQuantifiedExpression_UsingLiterals() { @Test void testNumericQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/NumberField satisfies $x <= 1", + testExpressionTranslationWithContext("every $x in PathNode/NumberField/number() satisfies $x <= 1", "ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1"); } @@ -346,14 +346,14 @@ void testDateQuantifiedExpression_UsingLiterals() { @Test void testDateQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", + "every $x in PathNode/StartDateField/xs:date(text()) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); } @Test void testDateQuantifiedExpression_UsingMultipleIterators() { testExpressionTranslationWithContext( - "every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", + "every $x in PathNode/StartDateField/xs:date(text()), $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); } @@ -368,7 +368,7 @@ void testTimeQuantifiedExpression_UsingLiterals() { @Test void testTimeQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", + "every $x in PathNode/StartTimeField/xs:time(text()) satisfies $x <= xs:time('00:00:00Z')", "ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); } @@ -382,7 +382,7 @@ void testDurationQuantifiedExpression_UsingLiterals() { @Test void testDurationQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", + "every $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); } @@ -486,7 +486,7 @@ void testStringsFromStringIteration_UsingLiterals() { @Test void testStringsSequenceFromIteration_UsingMultipleIterators() { testExpressionTranslationWithContext( - "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", + "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0,##########'), 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); } @@ -494,7 +494,7 @@ void testStringsSequenceFromIteration_UsingMultipleIterators() { @Test void testStringsSequenceFromIteration_UsingObjectVariable() { testExpressionTranslationWithContext( - "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", + "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField/xs:date(text()) return 'text'", "ND-Root", "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); } @@ -509,7 +509,7 @@ void testStringsSequenceFromIteration_UsingNodeContextVariable() { @Test void testStringsFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", + "'a' = (for $x in PathNode/TextField/normalize-space(text()) return concat($x, 'text'))", "ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); } @@ -535,7 +535,7 @@ void testStringsFromNumericIteration_UsingLiterals() { @Test void testStringsFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField/number() return 'y')", "ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')"); } @@ -548,7 +548,7 @@ void testStringsFromDateIteration_UsingLiterals() { @Test void testStringsFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField/xs:date(text()) return 'y')", "ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')"); } @@ -561,7 +561,7 @@ void testStringsFromTimeIteration_UsingLiterals() { @Test void testStringsFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField/xs:time(text()) return 'y')", "ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')"); } @@ -575,7 +575,7 @@ void testStringsFromDurationIteration_UsingLiterals() { @Test void testStringsFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/MeasureField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 'y')", "ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"); } @@ -589,7 +589,7 @@ void testNumbersFromStringIteration_UsingLiterals() { @Test void testNumbersFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField return number($x))", + testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField/normalize-space(text()) return number($x))", "ND-Root", "123 in (for text:$x in BT-00-Text return number($x))"); } @@ -615,7 +615,7 @@ void testNumbersFromNumericIteration_UsingLiterals() { @Test void testNumbersFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField/number() return 0)", "ND-Root", "123 in (for number:$x in BT-00-Number return 0)"); } @@ -628,7 +628,7 @@ void testNumbersFromDateIteration_UsingLiterals() { @Test void testNumbersFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField/xs:date(text()) return 0)", "ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)"); } @@ -641,7 +641,7 @@ void testNumbersFromTimeIteration_UsingLiterals() { @Test void testNumbersFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField/xs:time(text()) return 0)", "ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)"); } @@ -655,7 +655,7 @@ void testNumbersFromDurationIteration_UsingLiterals() { @Test void testNumbersFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/MeasureField return 0)", + testExpressionTranslationWithContext("123 = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 0)", "ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"); } @@ -671,7 +671,7 @@ void testDatesFromStringIteration_UsingLiterals() { @Test void testDatesFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", + "xs:date('2022-01-01Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:date($x))", "ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); } @@ -701,7 +701,7 @@ void testDatesFromNumericIteration_UsingLiterals() { @Test void testDatesFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField/number() return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); } @@ -716,7 +716,7 @@ void testDatesFromDateIteration_UsingLiterals() { @Test void testDatesFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); } @@ -731,7 +731,7 @@ void testDatesFromTimeIteration_UsingLiterals() { @Test void testDatesFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); } @@ -746,7 +746,7 @@ void testDatesFromDurationIteration_UsingLiterals() { @Test void testDatesFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); } @@ -762,7 +762,7 @@ void testTimesFromStringIteration_UsingLiterals() { @Test void testTimesFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", + "xs:time('12:00:00Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:time($x))", "ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); } @@ -792,7 +792,7 @@ void testTimesFromNumericIteration_UsingLiterals() { @Test void testTimesFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/NumberField/number() return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); } @@ -807,7 +807,7 @@ void testTimesFromDateIteration_UsingLiterals() { @Test void testTimesFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); } @@ -822,7 +822,7 @@ void testTimesFromTimeIteration_UsingLiterals() { @Test void testTimesFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); } @@ -837,7 +837,7 @@ void testTimesFromDurationIteration_UsingLiterals() { @Test void testTimesFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); } @@ -853,7 +853,7 @@ void testDurationsFromStringIteration_UsingLiterals() { @Test void testDurationsFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField/normalize-space(text()) return xs:dayTimeDuration($x))", "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); } @@ -883,7 +883,7 @@ void testDurationsFromNumericIteration_UsingLiterals() { @Test void testDurationsFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField/number() return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); } @@ -897,7 +897,7 @@ void testDurationsFromDateIteration_UsingLiterals() { @Test void testDurationsFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); } @@ -911,7 +911,7 @@ void testDurationsFromTimeIteration_UsingLiterals() { @Test void testDurationsFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); } @@ -925,7 +925,7 @@ void testDurationsFromDurationIteration_UsingLiterals() { @Test void testDurationsFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); } @@ -1156,14 +1156,14 @@ void testEndsWithFunction() { @Test void testCountFunction_UsingFieldReference() { - testExpressionTranslationWithContext("count(PathNode/TextField)", "ND-Root", + testExpressionTranslationWithContext("count(PathNode/TextField/normalize-space(text()))", "ND-Root", "count(BT-00-Text)"); } @Test void testCountFunction_UsingSequenceFromIteration() { testExpressionTranslationWithContext( - "count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", + "count(for $x in PathNode/TextField/normalize-space(text()) return concat($x, '-xyz'))", "ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); } @@ -1175,13 +1175,13 @@ void testNumberFunction() { @Test void testSumFunction_UsingFieldReference() { - testExpressionTranslationWithContext("sum(PathNode/NumberField)", "ND-Root", + testExpressionTranslationWithContext("sum(PathNode/NumberField/number())", "ND-Root", "sum(BT-00-Number)"); } @Test void testSumFunction_UsingNumericSequenceFromIteration() { - testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField return $v + 1)", + testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField/number() return $v + 1)", "ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"); } @@ -1207,7 +1207,7 @@ void testSubstringFunction() { @Test void testToStringFunction() { - testExpressionTranslationWithContext("format-number(123, '0.##########')", "ND-Root", + testExpressionTranslationWithContext("format-number(123, '0,##########')", "ND-Root", "string(123)"); } @@ -1218,7 +1218,7 @@ void testConcatFunction() { @Test void testFormatNumberFunction() { - testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '#,##0.00')", + testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '# ##0,00')", "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); } @@ -1232,6 +1232,18 @@ void testDateFromStringFunction() { "ND-Root", "date(BT-00-Text)"); } + @Test + void testDatePlusMeasureFunction() { + testExpressionTranslationWithContext("(PathNode/StartDateField/xs:date(text()) + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))", + "ND-Root", "add-measure(BT-00-StartDate, BT-00-Measure)"); + } + + @Test + void testDateMinusMeasureFunction() { + testExpressionTranslationWithContext("(PathNode/StartDateField/xs:date(text()) - (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))", + "ND-Root", "subtract-measure(BT-00-StartDate, BT-00-Measure)"); + } + // #endregion: Date functions // #region: Time functions -------------------------------------------------- @@ -1244,6 +1256,22 @@ void testTimeFromStringFunction() { // #endregion: Time functions + // #region Duration functions + + @Test + void testDayTimeDurationFromStringFunction() { + testExpressionTranslationWithContext("xs:yearMonthDuration(PathNode/TextField/normalize-space(text()))", + "ND-Root", "year-month-duration(BT-00-Text)"); + } + + @Test + void testYearMonthDurationFromStringFunction() { + testExpressionTranslationWithContext("xs:dayTimeDuration(PathNode/TextField/normalize-space(text()))", + "ND-Root", "day-time-duration(BT-00-Text)"); + } + + // #endregion Duration functions + // #region: Sequence Functions ---------------------------------------------- @Test @@ -1272,6 +1300,12 @@ void testDistinctValuesFunction_WithTimeSequences() { "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); } + @Test + void testDistinctValuesFunction_WithDurationSequences() { + testExpressionTranslationWithContext("distinct-values((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')))", + "ND-Root", "distinct-values((P1W, P2D, P2D, P5D))"); + } + @Test void testDistinctValuesFunction_WithBooleanSequences() { testExpressionTranslationWithContext("distinct-values((true(),false(),false(),false()))", @@ -1280,7 +1314,7 @@ void testDistinctValuesFunction_WithBooleanSequences() { @Test void testDistinctValuesFunction_WithFieldReferences() { - testExpressionTranslationWithContext("distinct-values(PathNode/TextField)", "ND-Root", + testExpressionTranslationWithContext("distinct-values(PathNode/TextField/normalize-space(text()))", "ND-Root", "distinct-values(BT-00-Text)"); } @@ -1312,6 +1346,12 @@ void testUnionFunction_WithTimeSequences() { "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } + @Test + void testUnionFunction_WithDurationSequences() { + testExpressionTranslationWithContext("distinct-values(((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')), (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D'))))", + "ND-Root", "value-union((P1W, P2D), (P2D, P5D))"); + } + @Test void testUnionFunction_WithBooleanSequences() { testExpressionTranslationWithContext("distinct-values(((true(),false()), (false(),false())))", @@ -1321,7 +1361,7 @@ void testUnionFunction_WithBooleanSequences() { @Test void testUnionFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", + "distinct-values((PathNode/TextField/normalize-space(text()), PathNode/TextField/normalize-space(text())))", "ND-Root", "value-union(BT-00-Text, BT-00-Text)"); } @@ -1338,40 +1378,46 @@ void testUnionFunction_WithTypeMismatch() { @Test void testIntersectFunction_WithStringSequences() { testExpressionTranslationWithContext( - "distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", + "distinct-values(for $L1 in ('one','two') return if (some $L2 in ('two','three','four') satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testIntersectFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values((1,2,3)[.= (2,3,4)])", "ND-Root", + testExpressionTranslationWithContext("distinct-values(for $L1 in (1,2,3) return if (some $L2 in (2,3,4) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); } @Test void testIntersectFunction_WithDateSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", + "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (some $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testIntersectFunction_WithTimeSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", + "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (some $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } + @Test + void testIntersectFunction_WithDurationSequences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (some $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect((P1W, P2D), (P2D, P5D))"); + } + @Test void testIntersectFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("distinct-values((true(),false())[.= (false(),false())])", + testExpressionTranslationWithContext("distinct-values(for $L1 in (true(),false()) return if (some $L2 in (false(),false()) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); } @Test void testIntersectFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", + "distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (some $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect(BT-00-Text, BT-00-Text)"); } @@ -1388,42 +1434,78 @@ void testIntersectFunction_WithTypeMismatch() { @Test void testExceptFunction_WithStringSequences() { testExpressionTranslationWithContext( - "distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", + "distinct-values(for $L1 in ('one','two') return if (every $L2 in ('two','three','four') satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testExceptFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values((1,2,3)[not(. = (2,3,4))])", "ND-Root", + testExpressionTranslationWithContext("distinct-values(for $L1 in (1,2,3) return if (every $L2 in (2,3,4) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); } @Test void testExceptFunction_WithDateSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", + "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (every $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testExceptFunction_WithTimeSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", + "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (every $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } + @Test + void testExceptFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (every $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except((P1W, P2D), (P2D, P5D))"); + } + @Test void testExceptFunction_WithBooleanSequences() { testExpressionTranslationWithContext( - "distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", + "distinct-values(for $L1 in (true(),false()) return if (every $L2 in (false(),false()) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))"); } @Test - void testExceptFunction_WithFieldReferences() { + void testExceptFunction_WithTextFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Text, BT-00-Text)"); + } + + @Test + void testExceptFunction_WithNumberFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/IntegerField/number() return if (every $L2 in PathNode/IntegerField/number() satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Integer, BT-00-Integer)"); + } + + @Test + void testExceptFunction_WithBooleanFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/IndicatorField return if (every $L2 in PathNode/IndicatorField satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Indicator, BT-00-Indicator)"); + } + + @Test + void testExceptFunction_WithDateFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/StartDateField/xs:date(text()) return if (every $L2 in PathNode/StartDateField/xs:date(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-StartDate, BT-00-StartDate)"); + } + + @Test + void testExceptFunction_WithTimeFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/StartTimeField/xs:time(text()) return if (every $L2 in PathNode/StartTimeField/xs:time(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-StartTime, BT-00-StartTime)"); + } + + @Test + void testExceptFunction_WithDurationFieldReferences() { testExpressionTranslationWithContext( - "distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", - "value-except(BT-00-Text, BT-00-Text)"); + "distinct-values(for $L1 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return if (every $L2 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except(BT-00-Measure, BT-00-Measure)"); } @Test @@ -1480,7 +1562,7 @@ void testSequenceEqualFunction_WithDurationSequences() { @Test void testSequenceEqualFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", + "deep-equal(sort(PathNode/TextField/normalize-space(text())), sort(PathNode/TextField/normalize-space(text())))", "ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)"); } 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 c51e8f28..67a83e13 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java @@ -155,9 +155,9 @@ void testTemplateLineIdentUnexpected() { @Test void testTemplateLine_VariableScope() { assertEquals( - lines("let block01() -> { #1: eval(for $x in . return $x)", // + lines("let block01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // "for-each(.).call(block0101()) }", // - "let block0101() -> { eval(for $x in . return $x) }", // + "let block0101() -> { eval(for $x in ./normalize-space(text()) return $x) }", // "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}"))); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java index 4d458d4c..28b70e57 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionCombinedV2Test.java @@ -26,20 +26,20 @@ void testNotPresentAndNotPresent() { @Test void testCountWithNodeContextOverride() { - testExpressionTranslationWithContext("count(../../PathNode/CodeField) = 1", "BT-00-Text", + testExpressionTranslationWithContext("count(../../PathNode/CodeField/normalize-space(text())) = 1", "BT-00-Text", "count(ND-Root::BT-00-Code) == 1"); } @Test void testCountWithAbsoluteFieldReference() { - testExpressionTranslationWithContext("count(/*/PathNode/CodeField) = 1", "BT-00-Text", + testExpressionTranslationWithContext("count(/*/PathNode/CodeField/normalize-space(text())) = 1", "BT-00-Text", "count(/BT-00-Code) == 1"); } @Test void testCountWithAbsoluteFieldReferenceAndPredicate() { testExpressionTranslationWithContext( - "count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1", "BT-00-Text", + "count(/*/PathNode/CodeField[../IndicatorField = true()]/normalize-space(text())) = 1", "BT-00-Text", "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"); } } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 0de7b05d..70f7e1a4 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -177,7 +177,7 @@ void testBooleanComparison_UsingFieldReference() { void testNumericComparison() { testExpressionTranslationWithContext( "2 > 1 and 3 >= 1 and 1 = 1 and 4 < 5 and 5 <= 5 and ../NumberField/number() > ../IntegerField/number()", - "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > integer"); + "BT-00-Text", "2 > 1 and 3>=1 and 1==1 and 4<5 and 5<=5 and BT-00-Number > BT-00-Integer"); } @Test @@ -307,7 +307,7 @@ void testStringQuantifiedExpression_UsingLiterals() { @Test void testStringQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/TextField satisfies $x <= 'a'", + testExpressionTranslationWithContext("every $x in PathNode/TextField/normalize-space(text()) satisfies $x <= 'a'", "ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'"); } @@ -331,7 +331,7 @@ void testNumericQuantifiedExpression_UsingLiterals() { @Test void testNumericQuantifiedExpression_UsingFieldReference() { - testExpressionTranslationWithContext("every $x in PathNode/NumberField satisfies $x <= 1", + testExpressionTranslationWithContext("every $x in PathNode/NumberField/number() satisfies $x <= 1", "ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1"); } @@ -346,14 +346,14 @@ void testDateQuantifiedExpression_UsingLiterals() { @Test void testDateQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", + "every $x in PathNode/StartDateField/xs:date(text()) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"); } @Test void testDateQuantifiedExpression_UsingMultipleIterators() { testExpressionTranslationWithContext( - "every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", + "every $x in PathNode/StartDateField/xs:date(text()), $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')", "ND-Root", "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"); } @@ -368,7 +368,7 @@ void testTimeQuantifiedExpression_UsingLiterals() { @Test void testTimeQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')", "ND-Root", + "every $x in PathNode/StartTimeField/xs:time(text()) satisfies $x <= xs:time('00:00:00Z')", "ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"); } @@ -382,7 +382,7 @@ void testDurationQuantifiedExpression_UsingLiterals() { @Test void testDurationQuantifiedExpression_UsingFieldReference() { testExpressionTranslationWithContext( - "every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", + "every $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))", "ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"); } @@ -486,7 +486,7 @@ void testStringsFromStringIteration_UsingLiterals() { @Test void testStringsSequenceFromIteration_UsingMultipleIterators() { testExpressionTranslationWithContext( - "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))", + "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0,##########'), 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); } @@ -494,7 +494,7 @@ void testStringsSequenceFromIteration_UsingMultipleIterators() { @Test void testStringsSequenceFromIteration_UsingObjectVariable() { testExpressionTranslationWithContext( - "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'", + "for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField/xs:date(text()) return 'text'", "ND-Root", "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"); } @@ -509,7 +509,7 @@ void testStringsSequenceFromIteration_UsingNodeContextVariable() { @Test void testStringsFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "'a' = (for $x in PathNode/TextField return concat($x, 'text'))", "ND-Root", + "'a' = (for $x in PathNode/TextField/normalize-space(text()) return concat($x, 'text'))", "ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); } @@ -535,7 +535,7 @@ void testStringsFromNumericIteration_UsingLiterals() { @Test void testStringsFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in PathNode/NumberField/number() return 'y')", "ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')"); } @@ -548,7 +548,7 @@ void testStringsFromDateIteration_UsingLiterals() { @Test void testStringsFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartDateField/xs:date(text()) return 'y')", "ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')"); } @@ -561,7 +561,7 @@ void testStringsFromTimeIteration_UsingLiterals() { @Test void testStringsFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in PathNode/StartTimeField/xs:time(text()) return 'y')", "ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')"); } @@ -575,7 +575,7 @@ void testStringsFromDurationIteration_UsingLiterals() { @Test void testStringsFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("'a' = (for $x in PathNode/MeasureField return 'y')", + testExpressionTranslationWithContext("'a' = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 'y')", "ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"); } @@ -589,7 +589,7 @@ void testNumbersFromStringIteration_UsingLiterals() { @Test void testNumbersFromStringIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField return number($x))", + testExpressionTranslationWithContext("123 = (for $x in PathNode/TextField/normalize-space(text()) return number($x))", "ND-Root", "123 in (for text:$x in BT-00-Text return number($x))"); } @@ -615,7 +615,7 @@ void testNumbersFromNumericIteration_UsingLiterals() { @Test void testNumbersFromNumericIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/NumberField/number() return 0)", "ND-Root", "123 in (for number:$x in BT-00-Number return 0)"); } @@ -628,7 +628,7 @@ void testNumbersFromDateIteration_UsingLiterals() { @Test void testNumbersFromDateIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartDateField/xs:date(text()) return 0)", "ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)"); } @@ -641,7 +641,7 @@ void testNumbersFromTimeIteration_UsingLiterals() { @Test void testNumbersFromTimeIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField return 0)", + testExpressionTranslationWithContext("123 = (for $x in PathNode/StartTimeField/xs:time(text()) return 0)", "ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)"); } @@ -655,7 +655,7 @@ void testNumbersFromDurationIteration_UsingLiterals() { @Test void testNumbersFromDurationIteration_UsingFieldReference() { - testExpressionTranslationWithContext("123 = (for $x in PathNode/MeasureField return 0)", + testExpressionTranslationWithContext("123 = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 0)", "ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"); } @@ -671,7 +671,7 @@ void testDatesFromStringIteration_UsingLiterals() { @Test void testDatesFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))", "ND-Root", + "xs:date('2022-01-01Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:date($x))", "ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"); } @@ -701,7 +701,7 @@ void testDatesFromNumericIteration_UsingLiterals() { @Test void testDatesFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField/number() return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"); } @@ -716,7 +716,7 @@ void testDatesFromDateIteration_UsingLiterals() { @Test void testDatesFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"); } @@ -731,7 +731,7 @@ void testDatesFromTimeIteration_UsingLiterals() { @Test void testDatesFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"); } @@ -746,7 +746,7 @@ void testDatesFromDurationIteration_UsingLiterals() { @Test void testDatesFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))", + "xs:date('2022-01-01Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:date('2022-01-01Z'))", "ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"); } @@ -762,7 +762,7 @@ void testTimesFromStringIteration_UsingLiterals() { @Test void testTimesFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))", "ND-Root", + "xs:time('12:00:00Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:time($x))", "ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))"); } @@ -792,7 +792,7 @@ void testTimesFromNumericIteration_UsingLiterals() { @Test void testTimesFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/NumberField/number() return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"); } @@ -807,7 +807,7 @@ void testTimesFromDateIteration_UsingLiterals() { @Test void testTimesFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"); } @@ -822,7 +822,7 @@ void testTimesFromTimeIteration_UsingLiterals() { @Test void testTimesFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"); } @@ -837,7 +837,7 @@ void testTimesFromDurationIteration_UsingLiterals() { @Test void testTimesFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))", + "xs:time('12:00:00Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:time('12:00:00Z'))", "ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"); } @@ -853,7 +853,7 @@ void testDurationsFromStringIteration_UsingLiterals() { @Test void testDurationsFromStringIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField/normalize-space(text()) return xs:dayTimeDuration($x))", "ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"); } @@ -883,7 +883,7 @@ void testDurationsFromNumericIteration_UsingLiterals() { @Test void testDurationsFromNumericIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField/number() return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"); } @@ -897,7 +897,7 @@ void testDurationsFromDateIteration_UsingLiterals() { @Test void testDurationsFromDateIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"); } @@ -911,7 +911,7 @@ void testDurationsFromTimeIteration_UsingLiterals() { @Test void testDurationsFromTimeIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"); } @@ -925,7 +925,7 @@ void testDurationsFromDurationIteration_UsingLiterals() { @Test void testDurationsFromDurationIteration_UsingFieldReference() { testExpressionTranslationWithContext( - "xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))", + "xs:dayTimeDuration('P1D') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:dayTimeDuration('P1D'))", "ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"); } @@ -1156,14 +1156,14 @@ void testEndsWithFunction() { @Test void testCountFunction_UsingFieldReference() { - testExpressionTranslationWithContext("count(PathNode/TextField)", "ND-Root", + testExpressionTranslationWithContext("count(PathNode/TextField/normalize-space(text()))", "ND-Root", "count(BT-00-Text)"); } @Test void testCountFunction_UsingSequenceFromIteration() { testExpressionTranslationWithContext( - "count(for $x in PathNode/TextField return concat($x, '-xyz'))", "ND-Root", + "count(for $x in PathNode/TextField/normalize-space(text()) return concat($x, '-xyz'))", "ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"); } @@ -1175,13 +1175,13 @@ void testNumberFunction() { @Test void testSumFunction_UsingFieldReference() { - testExpressionTranslationWithContext("sum(PathNode/NumberField)", "ND-Root", + testExpressionTranslationWithContext("sum(PathNode/NumberField/number())", "ND-Root", "sum(BT-00-Number)"); } @Test void testSumFunction_UsingNumericSequenceFromIteration() { - testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField return $v + 1)", + testExpressionTranslationWithContext("sum(for $v in PathNode/NumberField/number() return $v + 1)", "ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"); } @@ -1207,7 +1207,7 @@ void testSubstringFunction() { @Test void testToStringFunction() { - testExpressionTranslationWithContext("format-number(123, '0.##########')", "ND-Root", + testExpressionTranslationWithContext("format-number(123, '0,##########')", "ND-Root", "string(123)"); } @@ -1224,13 +1224,13 @@ void testStringJoinFunction_withLiterals() { @Test void testStringJoinFunction_withFieldReference() { - testExpressionTranslationWithContext("string-join(PathNode/TextField, ',')", "ND-Root", + testExpressionTranslationWithContext("string-join(PathNode/TextField/normalize-space(text()), ',')", "ND-Root", "string-join(BT-00-Text, ',')"); } @Test void testFormatNumberFunction() { - testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '#,##0.00')", + testExpressionTranslationWithContext("format-number(PathNode/NumberField/number(), '# ##0,00')", "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); } @@ -1256,6 +1256,22 @@ void testTimeFromStringFunction() { // #endregion: Time functions + // #region Duration functions + + @Test + void testDayTimeDurationFromStringFunction() { + testExpressionTranslationWithContext("xs:yearMonthDuration(PathNode/TextField/normalize-space(text()))", + "ND-Root", "year-month-duration(BT-00-Text)"); + } + + @Test + void testYearMonthDurationFromStringFunction() { + testExpressionTranslationWithContext("xs:dayTimeDuration(PathNode/TextField/normalize-space(text()))", + "ND-Root", "day-time-duration(BT-00-Text)"); + } + + // #endregion Duration functions + // #region: Sequence Functions ---------------------------------------------- @Test @@ -1284,6 +1300,12 @@ void testDistinctValuesFunction_WithTimeSequences() { "ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"); } + @Test + void testDistinctValuesFunction_WithDurationSequences() { + testExpressionTranslationWithContext("distinct-values((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')))", + "ND-Root", "distinct-values((P1W, P2D, P2D, P5D))"); + } + @Test void testDistinctValuesFunction_WithBooleanSequences() { testExpressionTranslationWithContext("distinct-values((true(),false(),false(),false()))", @@ -1292,7 +1314,7 @@ void testDistinctValuesFunction_WithBooleanSequences() { @Test void testDistinctValuesFunction_WithFieldReferences() { - testExpressionTranslationWithContext("distinct-values(PathNode/TextField)", "ND-Root", + testExpressionTranslationWithContext("distinct-values(PathNode/TextField/normalize-space(text()))", "ND-Root", "distinct-values(BT-00-Text)"); } @@ -1324,6 +1346,12 @@ void testUnionFunction_WithTimeSequences() { "ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } + @Test + void testUnionFunction_WithDurationSequences() { + testExpressionTranslationWithContext("distinct-values(((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')), (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D'))))", + "ND-Root", "value-union((P1W, P2D), (P2D, P5D))"); + } + @Test void testUnionFunction_WithBooleanSequences() { testExpressionTranslationWithContext("distinct-values(((true(),false()), (false(),false())))", @@ -1333,7 +1361,7 @@ void testUnionFunction_WithBooleanSequences() { @Test void testUnionFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "distinct-values((PathNode/TextField, PathNode/TextField))", "ND-Root", + "distinct-values((PathNode/TextField/normalize-space(text()), PathNode/TextField/normalize-space(text())))", "ND-Root", "value-union(BT-00-Text, BT-00-Text)"); } @@ -1350,40 +1378,46 @@ void testUnionFunction_WithTypeMismatch() { @Test void testIntersectFunction_WithStringSequences() { testExpressionTranslationWithContext( - "distinct-values(('one','two')[.= ('two','three','four')])", "ND-Root", + "distinct-values(for $L1 in ('one','two') return if (some $L2 in ('two','three','four') satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testIntersectFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values((1,2,3)[.= (2,3,4)])", "ND-Root", + testExpressionTranslationWithContext("distinct-values(for $L1 in (1,2,3) return if (some $L2 in (2,3,4) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"); } @Test void testIntersectFunction_WithDateSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])", + "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (some $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testIntersectFunction_WithTimeSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])", + "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (some $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } + @Test + void testIntersectFunction_WithDurationSequences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (some $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 = $L2) then $L1 else ())", + "ND-Root", "value-intersect((P1W, P2D), (P2D, P5D))"); + } + @Test void testIntersectFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("distinct-values((true(),false())[.= (false(),false())])", + testExpressionTranslationWithContext("distinct-values(for $L1 in (true(),false()) return if (some $L2 in (false(),false()) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"); } @Test void testIntersectFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "distinct-values(PathNode/TextField[.= PathNode/TextField])", "ND-Root", + "distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (some $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 = $L2) then $L1 else ())", "ND-Root", "value-intersect(BT-00-Text, BT-00-Text)"); } @@ -1400,42 +1434,78 @@ void testIntersectFunction_WithTypeMismatch() { @Test void testExceptFunction_WithStringSequences() { testExpressionTranslationWithContext( - "distinct-values(('one','two')[not(. = ('two','three','four'))])", "ND-Root", + "distinct-values(for $L1 in ('one','two') return if (every $L2 in ('two','three','four') satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))"); } @Test void testExceptFunction_WithNumberSequences() { - testExpressionTranslationWithContext("distinct-values((1,2,3)[not(. = (2,3,4))])", "ND-Root", + testExpressionTranslationWithContext("distinct-values(for $L1 in (1,2,3) return if (every $L2 in (2,3,4) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((1, 2, 3), (2, 3, 4))"); } @Test void testExceptFunction_WithDateSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])", + "distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (every $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"); } @Test void testExceptFunction_WithTimeSequences() { testExpressionTranslationWithContext( - "distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])", + "distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (every $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"); } + @Test + void testExceptFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (every $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except((P1W, P2D), (P2D, P5D))"); + } + @Test void testExceptFunction_WithBooleanSequences() { testExpressionTranslationWithContext( - "distinct-values((true(),false())[not(. = (false(),false()))])", "ND-Root", + "distinct-values(for $L1 in (true(),false()) return if (every $L2 in (false(),false()) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))"); } @Test - void testExceptFunction_WithFieldReferences() { + void testExceptFunction_WithTextFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Text, BT-00-Text)"); + } + + @Test + void testExceptFunction_WithNumberFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/IntegerField/number() return if (every $L2 in PathNode/IntegerField/number() satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Integer, BT-00-Integer)"); + } + + @Test + void testExceptFunction_WithBooleanFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/IndicatorField return if (every $L2 in PathNode/IndicatorField satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-Indicator, BT-00-Indicator)"); + } + + @Test + void testExceptFunction_WithDateFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/StartDateField/xs:date(text()) return if (every $L2 in PathNode/StartDateField/xs:date(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-StartDate, BT-00-StartDate)"); + } + + @Test + void testExceptFunction_WithTimeFieldReferences() { + testExpressionTranslationWithContext("distinct-values(for $L1 in PathNode/StartTimeField/xs:time(text()) return if (every $L2 in PathNode/StartTimeField/xs:time(text()) satisfies $L1 != $L2) then $L1 else ())", + "ND-Root", "value-except(BT-00-StartTime, BT-00-StartTime)"); + } + + @Test + void testExceptFunction_WithDurationFieldReferences() { testExpressionTranslationWithContext( - "distinct-values(PathNode/TextField[not(. = PathNode/TextField)])", "ND-Root", - "value-except(BT-00-Text, BT-00-Text)"); + "distinct-values(for $L1 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return if (every $L2 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies $L1 != $L2) then $L1 else ())", "ND-Root", "value-except(BT-00-Measure, BT-00-Measure)"); } @Test @@ -1492,7 +1562,7 @@ void testSequenceEqualFunction_WithDurationSequences() { @Test void testSequenceEqualFunction_WithFieldReferences() { testExpressionTranslationWithContext( - "deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))", "ND-Root", + "deep-equal(sort(PathNode/TextField/normalize-space(text())), sort(PathNode/TextField/normalize-space(text())))", "ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)"); } @@ -1546,13 +1616,13 @@ void testParameterizedExpression_WithDurationParameter() { @Test void testIndexer_WithFieldReference() { - testExpressionTranslationWithContext("PathNode/TextField[1]", "ND-Root", "text:BT-00-Text[1]"); + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text())[1]", "ND-Root", "text:BT-00-Text[1]"); } @Test void testIndexer_WithFieldReferenceAndPredicate() { testExpressionTranslationWithContext( - "PathNode/TextField[./normalize-space(text()) = 'hello'][1]", "ND-Root", + "PathNode/TextField[./normalize-space(text()) = 'hello']/normalize-space(text())[1]", "ND-Root", "text:BT-00-Text[BT-00-Text == 'hello'][1]"); } 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 4520e78e..a361c2df 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -164,9 +164,9 @@ void testTemplateLineJoining() { @Test void testTemplateLine_VariableScope() { assertEquals( - lines("let block01() -> { #1: eval(for $x in . return $x)", // + lines("let block01() -> { #1: eval(for $x in ./normalize-space(text()) return $x)", // "for-each(.).call(block0101()) }", // - "let block0101() -> { eval(for $x in . return $x) }", // + "let block0101() -> { eval(for $x in ./normalize-space(text()) return $x) }", // "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}"))); @@ -176,15 +176,15 @@ void testTemplateLine_VariableScope() { @Test void testTemplateLine_ContextVariable() { assertEquals( - lines("let block01(t, ctx) -> { #1: eval(for $x in . return concat($x, $t))", // + lines("let block01(t, ctx) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", // "for-each(.).call(block0101(ctx:$ctx, t:$t, t2:'test'))", // "for-each(.).call(block0102(ctx:$ctx, t:$t, t2:'test3')) }", // - "let block0101(t, ctx, t2) -> { #1.1: eval(for $y in . return concat($y, $t, $t2))", // + "let block0101(t, ctx, t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", // "for-each(.).call(block010101(ctx:$ctx, t:$t, t2:$t2))", // "for-each(.).call(block010102(ctx:$ctx, t:$t, t2:$t2)) }", // - "let block010101(t, ctx, t2) -> { eval(for $z in . return concat($z, $t, $ctx)) }", // - "let block010102(t, ctx, t2) -> { eval(for $z in . return concat($z, $t, $ctx)) }", // - "let block0102(t, ctx, t2) -> { eval(for $z in . return concat($z, $t2, $ctx)) }", // + "let block010101(t, ctx, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // + "let block010102(t, ctx, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // + "let block0102(t, ctx, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t2, $ctx)) }", // "for-each(/*/PathNode/TextField).call(block01(ctx:., t:./normalize-space(text())))"), // translateTemplate(lines( "{context:$ctx = BT-00-Text, text:$t = BT-00-Text} ${for text:$x in BT-00-Text return concat($x, $t)}", From 0c44f4e62e83fa57131b374ed5f85ba66cf7a811 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sat, 6 May 2023 01:56:36 +0200 Subject: [PATCH 22/57] Fixed JavaDoc errors and warnings --- .../ted/eforms/sdk/ComponentFactory.java | 4 + .../ted/eforms/sdk/SdkSymbolResolver.java | 3 +- .../java/eu/europa/ted/efx/EfxTranslator.java | 16 ++-- .../interfaces/EfxExpressionTranslator.java | 2 +- .../efx/interfaces/EfxTemplateTranslator.java | 4 +- .../ted/efx/interfaces/MarkupGenerator.java | 38 +++++++- .../ted/efx/interfaces/ScriptGenerator.java | 88 ++++++++++++++++++- .../TranslatorDependencyFactory.java | 2 + .../eu/europa/ted/efx/model/CallStack.java | 59 ++++++++++++- .../ted/efx/model/ContentBlockStack.java | 15 ++++ .../java/eu/europa/ted/efx/model/Context.java | 10 ++- .../eu/europa/ted/efx/model/ContextStack.java | 18 +++- .../java/eu/europa/ted/efx/model/Markup.java | 3 + .../sdk0/v6/EfxExpressionTranslator06.java | 21 ++++- .../sdk0/v7/EfxExpressionTranslator07.java | 6 ++ .../efx/sdk1/EfxExpressionTranslatorV1.java | 8 ++ .../ted/efx/xpath/XPathContextualizer.java | 11 ++- 17 files changed, 281 insertions(+), 27 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java index a88dd0fc..65134e81 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java @@ -30,6 +30,10 @@ private ComponentFactory() { * Gets the single instance containing the symbols defined in the given version of the eForms SDK. * * @param sdkVersion Version of the SDK + * @param sdkRootPath Path to the root of the SDK + * @return The single instance containing the symbols defined in the given version of the eForms + * SDK. + * @throws InstantiationException If the SDK version is not supported. */ public static SymbolResolver getSymbolResolver(final String sdkVersion, final Path sdkRootPath) throws InstantiationException { diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java index db4c427b..809761c5 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -46,7 +46,8 @@ public final List expandCodelist(final String codelistId) { * Private, use getInstance method instead. * * @param sdkVersion The version of the SDK. - * @throws InstantiationException + * @param sdkRootPath The path to the root of the SDK. + * @throws InstantiationException If the SDK version is not supported. */ public SdkSymbolResolver(final String sdkVersion, final Path sdkRootPath) throws InstantiationException { diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslator.java b/src/main/java/eu/europa/ted/efx/EfxTranslator.java index c7a86a70..22b9d504 100644 --- a/src/main/java/eu/europa/ted/efx/EfxTranslator.java +++ b/src/main/java/eu/europa/ted/efx/EfxTranslator.java @@ -37,9 +37,10 @@ public class EfxTranslator { * expression to be translated. * @param expression The EFX expression to translate. * @param expressionParameters The values of any parameters that the EFX expression requires. + * @param options The options to be used by the EFX expression translator. * @return The translated expression in the target script language supported by the given * {@link TranslatorDependencyFactory}. - * @throws InstantiationException + * @throws InstantiationException If the EFX expression translator cannot be instantiated. */ public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, final String expression, TranslatorOptions options, final String... expressionParameters) @@ -64,8 +65,9 @@ public static String translateExpression(final TranslatorDependencyFactory depen * @param pathname The path to the file containing the EFX template to translate. * @return The translated template in the target markup language supported by the given * {@link TranslatorDependencyFactory}. - * @throws IOException - * @throws InstantiationException + * @param options The options to be used by the EFX template translator. + * @throws IOException If the file cannot be read. + * @throws InstantiationException If the EFX template translator cannot be instantiated. */ public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, final Path pathname, TranslatorOptions options) @@ -90,7 +92,8 @@ public static String translateTemplate(final TranslatorDependencyFactory depende * @param template A string containing the EFX template to translate. * @return The translated template in the target markup language supported by the given * {@link TranslatorDependencyFactory}. - * @throws InstantiationException + * @param options The options to be used by the EFX template translator. + * @throws InstantiationException If the EFX template translator cannot be instantiated. */ public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, final String template, TranslatorOptions options) @@ -116,8 +119,9 @@ public static String translateTemplate(final TranslatorDependencyFactory depende * @param stream An InputStream containing the EFX template to be translated. * @return The translated template in the target markup language supported by the given * {@link TranslatorDependencyFactory}. - * @throws IOException - * @throws InstantiationException + * @param options The options to be used by the EFX template translator. + * @throws IOException If the InputStream cannot be read. + * @throws InstantiationException If the EFX template translator cannot be instantiated. */ public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, final InputStream stream, TranslatorOptions options) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/EfxExpressionTranslator.java b/src/main/java/eu/europa/ted/efx/interfaces/EfxExpressionTranslator.java index b4de7edc..8e2dc0d6 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/EfxExpressionTranslator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/EfxExpressionTranslator.java @@ -27,7 +27,7 @@ public interface EfxExpressionTranslator { * * @param expression A string containing the EFX expression to be translated. * @param expressionParameters The values of any parameters that the given expression expects. - * @return + * @return The translated expression in the target script language. */ String translateExpression(final String expression, final String... expressionParameters); } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/EfxTemplateTranslator.java b/src/main/java/eu/europa/ted/efx/interfaces/EfxTemplateTranslator.java index 7be8bba3..430eccdd 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/EfxTemplateTranslator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/EfxTemplateTranslator.java @@ -30,7 +30,7 @@ public interface EfxTemplateTranslator extends EfxExpressionTranslator { * * @param pathname The path and filename of the EFX template file to translate. * @return A string containing the translated template. - * @throws IOException + * @throws IOException If the file cannot be read. */ String renderTemplate(Path pathname) throws IOException; @@ -47,7 +47,7 @@ public interface EfxTemplateTranslator extends EfxExpressionTranslator { * * @param stream An InputStream with the EFX template to be translated. * @return A string containing the translated template. - * @throws IOException + * @throws IOException If the InputStream cannot be read. */ String renderTemplate(InputStream stream) throws IOException; } 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 9b9a2c0a..30f2f1a2 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -35,6 +35,10 @@ public interface MarkupGenerator { /** * Given a body (main content) and a set of fragments, this method returns the full content of the * target template file. + * + * @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(final List content, final List fragments); @@ -42,12 +46,18 @@ public interface MarkupGenerator { * Given an expression (which will eventually, at runtime, evaluate to the value of a field), this * method returns the template code that dereferences it (retrieves the value) in the target * template. + * + * @param variableExpression the expression to be evaluated and rendered. + * @return the template code that dereferences the expression in the target template. */ Markup renderVariableExpression(final Expression variableExpression); /** * Given a label key (which will eventually, at runtime, be dereferenced to a label text), this * 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. */ Markup renderLabelFromKey(final StringExpression key); @@ -55,30 +65,51 @@ public interface MarkupGenerator { * Given an expression (which will eventually, at runtime, be evaluated to a label key and * subsequently dereferenced to a label text), this method returns the template code that renders * this label in the target template language. + * + * @param expression the expression that returns the label key. + * @return the template code that renders the label in the target template language. */ Markup renderLabelFromExpression(final Expression expression); /** * Given a string of free text, this method returns the template code that adds this text in the * target template. + * + * @param freeText the free text to be rendered. + * @return the template code that adds this text in the target template. */ Markup renderFreeText(final String freeText); /** * @deprecated Use {@link #composeFragmentDefinition(String, String, Markup, Set)} instead. + * + * @param name the name of the fragment. + * @param number the outline number of the fragment. + * @param content the content of the fragment. + * @return the code that encapsulates the fragment in the target template. */ @Deprecated(since = "2.0.0", forRemoval = true) Markup composeFragmentDefinition(final String name, String number, Markup content); /** * Given a fragment name (identifier) and some pre-rendered content, this method returns the code - * that encapsulates it in the target template. + * that encapsulates it in the target template + * + * @param name the name of the fragment. + * @param number the outline number of the fragment. + * @param content the content of the fragment. + * @param parameters the parameters of the fragment. + * @return the code that encapsulates the fragment in the target template. */ Markup composeFragmentDefinition(final String name, String number, Markup content, Set parameters); /** * @deprecated Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead. + * + * @param name the name of the fragment. + * @param context the context of the fragment. + * @return the code that invokes (uses) the fragment. */ @Deprecated(since = "2.0.0", forRemoval = true) Markup renderFragmentInvocation(final String name, final PathExpression context); @@ -86,6 +117,11 @@ Markup composeFragmentDefinition(final String name, String number, Markup conten /** * Given a fragment name (identifier), and an evaluation context, this method returns the code * that invokes (uses) the fragment. + * + * @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. */ Markup renderFragmentInvocation(final String name, final PathExpression context, final Set> variables); diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 1738f96b..ae1373c6 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -48,6 +48,12 @@ public interface ScriptGenerator { * Similar to {@link #composeFieldReferenceWithPredicate} but for nodes. Quick reminder: the * difference between fields and nodes is that fields contain values, while nodes contain other * nodes and/or fields. + * + * @param The type of the returned Expression. + * @param nodeReference The PathExpression that points to the node. + * @param predicate The predicate that should be used to match the subset of nodes. + * @param type The type of the returned Expression. + * @return The target language script that matches the subset of nodes. */ public T composeNodeReferenceWithPredicate( final PathExpression nodeReference, final BooleanExpression predicate, Class type); @@ -59,6 +65,12 @@ public T composeNodeReferenceWithPredicate( * Similar to {@link #composeNodeReferenceWithPredicate} but for fields. Quick reminder: the * difference between fields and nodes is that fields contain values, while nodes contain other * nodes and/or fields. + * + * @param The type of the returned Expression. + * @param fieldReference The PathExpression that points to the field. + * @param predicate The predicate that should be used to match the subset of fields. + * @param type The type of the returned Expression. + * @return The target language script that matches the subset of fields. */ public T composeFieldReferenceWithPredicate( final PathExpression fieldReference, final BooleanExpression predicate, Class type); @@ -69,6 +81,11 @@ public T composeFieldReferenceWithAxis(final PathExpressi /** * Given a PathExpression, this method should return the target language script for retrieving the * value of the field. + * + * @param The type of the returned Expression. + * @param fieldReference The PathExpression that points to the field. + * @param type The type of the returned Expression. + * @return The target language script that retrieves the value of the field. */ public T composeFieldValueReference(final PathExpression fieldReference, Class type); @@ -76,6 +93,12 @@ public T composeFieldValueReference(final PathExpression /** * Given a PathExpression and an attribute name, this method should return the target language * script for retrieving the value of the attribute. + * + * @param The type of the returned Expression. + * @param fieldReference The PathExpression that points to the field. + * @param attribute The name of the attribute. + * @param type The type of the returned Expression. + * @return The target language script that retrieves the value of the attribute. */ public T composeFieldAttributeReference( final PathExpression fieldReference, String attribute, Class type); @@ -83,6 +106,11 @@ public T composeFieldAttributeReference( /** * Given a variable name this method should return script to dereference the variable. The * returned Expression should be of the indicated type. + * + * @param The type of the returned Expression. + * @param variableName The name of the variable. + * @param type The type of the returned Expression. + * @return The target language script that dereferences the variable. */ public T composeVariableReference(String variableName, Class type); @@ -93,18 +121,31 @@ public T composeFieldAttributeReference( /** * Takes a list of string expressions and returns the target language script that corresponds to a * list of string expressions. + * + * @param The type of the returned Expression. + * @param The type of the returned ListExpression. + * @param list The list of string expressions. + * @param type The type of the returned Expression. + * @return The target language script that corresponds to a list of string expressions. */ public > L composeList(List list, Class type); /** * Takes a Java Boolean value and returns the corresponding target language script. + * + * @param value The Java Boolean value. + * @return The target language script that corresponds to the given Java Boolean value. */ public BooleanExpression getBooleanEquivalent(boolean value); /** * Returns the target language script for performing a logical AND operation on the two given * operands. + * + * @param leftOperand The left operand of the logical AND operation. + * @param rightOperand The right operand of the logical AND operation. + * @return The target language script for performing a logical AND operation on the two given */ public BooleanExpression composeLogicalAnd(final BooleanExpression leftOperand, final BooleanExpression rightOperand); @@ -112,6 +153,10 @@ public BooleanExpression composeLogicalAnd(final BooleanExpression leftOperand, /** * Returns the target language script for performing a logical OR operation on the two given * operands. + * + * @param leftOperand The left operand of the logical OR operation. + * @param rightOperand The right operand of the logical OR operation. + * @return The target language script for performing a logical OR operation on the two given */ public BooleanExpression composeLogicalOr(final BooleanExpression leftOperand, final BooleanExpression rightOperand); @@ -119,12 +164,21 @@ public BooleanExpression composeLogicalOr(final BooleanExpression leftOperand, /** * Returns the target language script for performing a logical NOT operation on the given boolean * expression. + * + * @param condition The boolean expression to be negated. + * @return The target language script for performing a logical NOT operation on the given boolean */ public BooleanExpression composeLogicalNot(BooleanExpression condition); /** * Returns the target language script that checks whether a given list of values (haystack) * contains a given value (needle). + * + * @param The type of the returned Expression. + * @param The type of the returned ListExpression. + * @param needle The value to be searched for. + * @param haystack The list of values to be searched. + * @return The target language script that checks whether a given list of values (haystack) */ public > BooleanExpression composeContainsCondition( final T needle, final L haystack); @@ -132,12 +186,21 @@ public > BooleanExpression com /** * Returns the target language script that checks whether a given string matches the given RegEx * pattern. + * + * @param expression The string expression to be matched. + * @param regexPattern The RegEx pattern to be used for matching. + * @return The target language script that checks whether a given string matches the given RegEx */ public BooleanExpression composePatternMatchCondition(final StringExpression expression, final String regexPattern); /** * Returns the given expression parenthesized in the target language. + * + * @param The type of the returned Expression. + * @param expression The expression to be parenthesized. + * @param type The type of the returned Expression. + * @return The given expression parenthesized in the target language. */ public T composeParenthesizedExpression(T expression, Class type); @@ -179,11 +242,18 @@ public IteratorExpression composeIteratorExpression( * data is a two-step process: a) we need to access the data source, b) we need to get the actual * data from the data source. This method should return the target language script that connects * to the data source and permits us to subsequently get the data by using a PathExpression. + * + * @param externalReference The PathExpression that points to the external data source. + * @return a PathExpression with the target language script that retrieves the external data source. */ public PathExpression composeExternalReference(final StringExpression externalReference); /** * See {@link #composeExternalReference} for more details. + * + * @param externalReference The PathExpression that points to the external data source. + * @param fieldReference The PathExpression that points to the field in the external data source. + * @return a PathExpression with the target language script that retrieves the external data. */ public PathExpression composeFieldInExternalReference(final PathExpression externalReference, final PathExpression fieldReference); @@ -195,23 +265,26 @@ public PathExpression composeFieldInExternalReference(final PathExpression exter * * @param first The part of the path that goes before the delimiter. * @param second The part of the path that goes after the delimiter. - * @return + * @return The joined path expression. */ public PathExpression joinPaths(PathExpression first, PathExpression second); /** * Gets a piece of text and returns it inside quotes as expected by the target language. * - * @param value - * @return + * @param value The text to be quoted. + * @return The quoted text. */ public StringExpression getStringLiteralFromUnquotedString(String value); /** * Returns the target language script that compares the two operands (for equality etc.). * + * @param leftOperand The left operand of the comparison. * @param operator The EFX operator that is used to compare the two operands. Do not forget to * translate the operator to the target language equivalent. + * @param rightOperand The right operand of the comparison. + * @return The target language script that performs the comparison. */ public BooleanExpression composeComparisonOperation(Expression leftOperand, String operator, Expression rightOperand); @@ -220,20 +293,29 @@ public BooleanExpression composeComparisonOperation(Expression leftOperand, Stri * Given a numeric operation, this method should return the target language script that performs * the operation. * + * @param leftOperand The left operand of the numeric operation. * @param operator The EFX intended operator. Do not forget to translate the operator to the * target language equivalent. + * @param rightOperand The right operand of the numeric operation. + * @return The target language script that performs the numeric operation. */ public NumericExpression composeNumericOperation(NumericExpression leftOperand, String operator, NumericExpression rightOperand); /** * Returns the numeric literal passed in target language script. The passed literal is in EFX. + * + * @param efxLiteral The numeric literal in EFX. + * @return The numeric literal in the target language. */ public NumericExpression getNumericLiteralEquivalent(final String efxLiteral); /** * Returns the string literal in the target language. Note that the string literal passed as a * parameter is already between quotes in EFX. + * + * @param efxLiteral The string literal in EFX. + * @return The string literal in the target language. */ public StringExpression getStringLiteralEquivalent(final String efxLiteral); diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java index fa55feb7..69320ab2 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java @@ -50,6 +50,7 @@ public interface TranslatorDependencyFactory { * @param sdkVersion The version of the SDK that contains the version of the EFX grammar that the * EFX translator will attempt to translate. This is important as it defines the EFX * language features that ScriptGenerator instance should be able to handle. + * @param options The options to be used by the ScriptGenerator. * @return An instance of ScriptGenerator to be used by the EFX translator. */ public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options); @@ -63,6 +64,7 @@ public interface TranslatorDependencyFactory { * @param sdkVersion The version of the SDK that contains the version of the EFX grammar that the * EFX translator will attempt to translate. This is important as it defines the EFX * language features that MarkupGenerator instance should be able to handle. + * @param options The options to be used by the MarkupGenerator. * @return The instance of MarkupGenerator to be used by the EFX translator. */ public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options); diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java index 7f19bf28..ee23f3d9 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -39,6 +39,9 @@ class StackFrame extends Stack { /** * Registers a parameter identifier and pushes a parameter declaration on the current stack * frame. Also stores the parameter value. + * @param parameterName The name of the parameter. + * @param parameterDeclarationExpression The expression used to declare the parameter. + * @param parameterValue The value passed to the parameter. */ void pushParameterDeclaration(String parameterName, Expression parameterDeclarationExpression, Expression parameterValue) { @@ -49,6 +52,8 @@ void pushParameterDeclaration(String parameterName, Expression parameterDeclarat /** * Registers a variable identifier and pushes a variable declaration on the current stack frame. + * @param variableName The name of the variable. + * @param variableDeclarationExpression The expression used to declare the variable. */ void pushVariableDeclaration(String variableName, Expression variableDeclarationExpression) { this.declareIdentifier(variableName, variableDeclarationExpression.getClass()); @@ -58,6 +63,8 @@ void pushVariableDeclaration(String variableName, Expression variableDeclaration /** * Registers an identifier in the current scope. This registration is later used to check if an * identifier is declared in the current scope. + * @param identifier The identifier to register. + * @param type The type of the identifier. */ void declareIdentifier(String identifier, Class type) { this.typeRegister.put(identifier, type); @@ -65,6 +72,8 @@ void declareIdentifier(String identifier, Class type) { /** * Used to store parameter values. + * @param identifier The identifier of the parameter. + * @param value The value of the parameter. */ void storeValue(String identifier, Expression value) { this.valueRegister.put(identifier, value); @@ -73,6 +82,8 @@ void storeValue(String identifier, Expression value) { /** * Returns the object at the top of the stack and removes it from the stack. The object must be * of the expected type. + * @param expectedType The type that the returned object is expected to have. + * @return The object removed from the top of the stack. */ synchronized T pop(Class expectedType) { Class actualType = peek().getClass(); @@ -139,6 +150,12 @@ public void popStackFrame() { /** * Pushes a parameter declaration on the current stack frame. Checks if another identifier with * the same name is already declared in the current scope. + * + * @param parameterName The name of the parameter. + * @param parameterDeclaration The expression used to declare the parameter. + * @param parameterValue The value passed to the parameter. + * @throws ParseCancellationException if another identifier with the same name is already + * declared in the current scope. */ public void pushParameterDeclaration(String parameterName, Expression parameterDeclaration, Expression parameterValue) { @@ -153,6 +170,9 @@ public void pushParameterDeclaration(String parameterName, Expression parameterD /** * Pushes a variable declaration on the current stack frame. Checks if another identifier with the * same name is already declared in the current scope. + * + * @param variableName The name of the variable. + * @param variableDeclaration The expression used to declare the variable. */ public void pushVariableDeclaration(String variableName, Expression variableDeclaration) { if (this.inScope(variableName)) { @@ -166,6 +186,9 @@ public void pushVariableDeclaration(String variableName, Expression variableDecl * Declares a template variable. Template variables are tracked to ensure proper scoping. However, * their declaration is not pushed on the stack as they are declared at the template level (in * Markup) and not at the expression level (not in the target language script). + * + * @param variableName The name of the variable. + * @param variableType The type of the variable. */ public void declareTemplateVariable(String variableName, Class variableType) { @@ -177,6 +200,9 @@ public void declareTemplateVariable(String variableName, /** * Checks if an identifier is declared in the current scope. + * + * @param identifier The identifier to check. + * @return True if the identifier is declared in the current scope. */ boolean inScope(String identifier) { return this.frames.stream().anyMatch( @@ -185,6 +211,9 @@ boolean inScope(String identifier) { /** * Returns the stack frame containing the given identifier. + * + * @param identifier The identifier to look for. + * @return The stack frame containing the given identifier or null if no such stack frame exists. */ StackFrame findFrameContaining(String identifier) { return this.frames.stream() @@ -195,6 +224,9 @@ StackFrame findFrameContaining(String identifier) { /** * Gets the value of a parameter. + * + * @param identifier The identifier of the parameter. + * @return The value of the parameter. */ Optional getParameter(String identifier) { return this.frames.stream().filter(f -> f.valueRegister.containsKey(identifier)).findFirst() @@ -203,6 +235,9 @@ Optional getParameter(String identifier) { /** * Gets the type of a variable. + * + * @param identifier The identifier of the variable. + * @return The type of the variable. */ Optional> getVariable(String identifier) { return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier)).findFirst() @@ -212,6 +247,10 @@ Optional> getVariable(String identifier) { /** * Pushes a variable reference on the current stack frame. Makes sure there is no name collision * with other identifiers already in scope. + * + * @param variableName The name of the variable. + * @param variableReference The variable to push on the stack. + * @throws ParseCancellationException if the variable is not declared in the current scope. */ public void pushVariableReference(String variableName, Expression variableReference) { getParameter(variableName).ifPresentOrElse(parameterValue -> this.push(parameterValue), @@ -225,6 +264,8 @@ public void pushVariableReference(String variableName, Expression variableRefere /** * Pushes a variable reference on the current stack frame. This method is private because it is * only used for to improve the readability of its public counterpart. + * @param variableReference The variable to push on the stack. + * @param variableType The type of the variable. */ private void pushVariableReference(Expression variableReference, Class variableType) { @@ -233,36 +274,46 @@ private void pushVariableReference(Expression variableReference, /** * Pushes an object on the current stack frame. No checks, no questions asked. + * + * @param item The object to push on the stack. */ public void push(CallStackObject item) { this.frames.peek().push(item); } /** - * Returns the object at the top of the current stack frame and removes it from the stack. + * Gets the object at the top of the current stack frame and removes it from the stack. * + * @param The type of the object at the top of the current stack frame. * @param expectedType The that the returned object is expected to have. + * @return The object at the top of the current stack frame. */ public synchronized T pop(Class expectedType) { return this.frames.peek().pop(expectedType); } /** - * Returns the object at the top of the current stack frame without removing it from the stack. + * Gets the object at the top of the current stack frame without removing it from the stack. + * + * @return The object at the top of the current stack frame. */ public synchronized CallStackObject peek() { return this.frames.peek().peek(); } /** - * Returns the number of elements in the current stack frame. + * Gets the number of elements in the current stack frame. + * + * @return The number of elements in the current stack frame. */ public int size() { return this.frames.peek().size(); } /** - * Returns true if the current stack frame is empty. + * Checks if the current stack frame is empty. + * + * @return True if the current stack frame is empty. */ public boolean empty() { return this.frames.peek().empty(); diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java index d22da077..7d1ce21f 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java +++ b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java @@ -7,6 +7,11 @@ public class ContentBlockStack extends Stack { /** * Adds a new child block to the top of the stack. When the child is later removed, its parent * will return to the top of the stack again. + * + * @param number the outline number of the child block. + * @param content the content of the child block. + * @param context the context of the child block. + * @param variables the variables of the child block. */ public void pushChild(final int number, final Markup content, final Context context, final VariableList variables) { @@ -16,6 +21,11 @@ public void pushChild(final int number, final Markup content, final Context cont /** * Removes the block at the top of the stack and replaces it by a new sibling block. When the last * sibling is later removed, their parent block will return to the top of the stack again. + * + * @param number the outline number of the sibling block. + * @param content the content of the sibling block. + * @param context the context of the sibling block. + * @param variables the variables of the sibling block. */ public void pushSibling(final int number, final Markup content, Context context, final VariableList variables) { @@ -25,6 +35,9 @@ public void pushSibling(final int number, final Markup content, Context context, /** * Finds the block in the stack that has the given indentation level. Works from the top of the * stack to the bottom. + * + * @param indentationLevel the indentation level to look for. + * @return the block with the given indentation level or null if no such block exists. */ public ContentBlock blockAtLevel(final int indentationLevel) { if (this.isEmpty()) { @@ -36,6 +49,8 @@ public ContentBlock blockAtLevel(final int indentationLevel) { /** * Returns the indentation level of the block at the top of the stack or zero if the stack is * empty. Works from the bottom of the stack to the top. + * + * @return the indentation level of the block at the top of the stack or zero if the stack is */ public int currentIndentationLevel() { if (this.isEmpty()) { 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 ccd372f2..a32dd397 100644 --- a/src/main/java/eu/europa/ted/efx/model/Context.java +++ b/src/main/java/eu/europa/ted/efx/model/Context.java @@ -94,7 +94,9 @@ public Variable variable() { } /** - * Returns the [field or node] identifier that was used to create this context. + * Gets the [field or node] identifier that was used to create this context. + * + * @return the [field or node] identifier that was used to create this context. */ public String symbol() { return symbol; @@ -103,13 +105,17 @@ public String symbol() { /** * The absolute path of the context is needed when we want to create a new context relative to * this one. + * + * @return the absolute path of the context. */ public PathExpression absolutePath() { return absolutePath; } /** - * Returns the relative path of the context. + * Gets the relative path of the context. + * + * @return the relative path of the context. */ public PathExpression relativePath() { return relativePath; diff --git a/src/main/java/eu/europa/ted/efx/model/ContextStack.java b/src/main/java/eu/europa/ted/efx/model/ContextStack.java index dc9bbf19..d789c9be 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContextStack.java +++ b/src/main/java/eu/europa/ted/efx/model/ContextStack.java @@ -9,7 +9,7 @@ import eu.europa.ted.efx.model.Expression.PathExpression; /** - * Used to keep trak of the current evaluation context. Extends Stack<Context> to provide + * Used to keep track of the current evaluation context. Extends Stack<Context> to provide * helper methods for pushing directly using a fieldId or a nodeId. The point is to make it easier * to use a context stack and reduce the possibility of coding mistakes. */ @@ -32,6 +32,9 @@ public ContextStack(final SymbolResolver symbols) { * Creates a new Context for the given field and places it at the top of the stack. The new * Context is determined by the field itself, and it is made to be relative to the one currently * at the top of the stack (or absolute if the stack is empty). + * + * @param fieldId the field to create a context for. + * @return the new FieldContext. */ public FieldContext pushFieldContext(final String fieldId) { PathExpression absolutePath = symbols.getAbsolutePathOfField(fieldId); @@ -49,6 +52,9 @@ public FieldContext pushFieldContext(final String fieldId) { /** * Creates a new Context for the given node and places it at the top of the stack. The new Context * is relative to the one currently at the top of the stack (or absolute if the stack is empty). + * + * @param nodeId the id node to create a context for. + * @return the new NodeContext. */ public NodeContext pushNodeContext(final String nodeId) { PathExpression absolutePath = symbols.getAbsolutePathOfNode(nodeId); @@ -74,6 +80,8 @@ public Context getContextFromVariable(final String variableName) { /** * Returns true if the context at the top of the stack is a {@link FieldContext}. Does not remove * the context from the stack. + * + * @return true if the context at the top of the stack is a {@link FieldContext}. */ public Boolean isFieldContext() { if (this.isEmpty() || this.peek() == null) { @@ -86,6 +94,8 @@ public Boolean isFieldContext() { /** * Returns true if the context at the top of the stack is a {@link NodeContext}. Does not remove * the context from the stack. + * + * @return true if the context at the top of the stack is a {@link NodeContext}. */ public Boolean isNodeContext() { if (this.isEmpty() || this.peek() == null) { @@ -98,6 +108,8 @@ public Boolean isNodeContext() { /** * Returns the [field or node] identifier that was used to create the context that is currently at * the top of the stack. Does not remove the context from the stack. + * + * @return the [field or node] identifier that was used to create the context that is currently at */ public String symbol() { if (this.isEmpty() || this.peek() == null) { @@ -110,6 +122,8 @@ public String symbol() { /** * Returns the absolute path of the context that is currently at the top of the stack. Does not * remove the context from the stack. + * + * @return the absolute path of the context that is currently at the top of the stack. */ public PathExpression absolutePath() { if (this.isEmpty() || this.peek() == null) { @@ -122,6 +136,8 @@ public PathExpression absolutePath() { /** * Returns the relative path of the context that is currently at the top of the stack. Does not * remove the context from the stack. + * + * @return the relative path of the context that is currently at the top of the stack. */ public PathExpression relativePath() { if (this.isEmpty() || this.peek() == null) { diff --git a/src/main/java/eu/europa/ted/efx/model/Markup.java b/src/main/java/eu/europa/ted/efx/model/Markup.java index ed55e2a2..f525761a 100644 --- a/src/main/java/eu/europa/ted/efx/model/Markup.java +++ b/src/main/java/eu/europa/ted/efx/model/Markup.java @@ -16,6 +16,9 @@ public Markup(final String script) { /** * Helps combine two subsequent markup elements into one. + * + * @param next the next markup element. + * @return the combined markup element. */ public Markup join(final Markup next) { return new Markup(this.script + next.script); diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java index afc15a9e..8c82a2fb 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java @@ -105,9 +105,6 @@ public class EfxExpressionTranslator06 extends EfxBaseListener private static final String NOT_MODIFIER = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); - /** - * - */ private static final String TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES = "Type mismatch. Cannot compare values of different types: "; @@ -186,6 +183,9 @@ private String getTranslatedScript() { /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleFieldReferenceContext} to locate a field identifier. + * + * @param ctx the context to start from. + * @return the field identifier or null if not found. */ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { @@ -229,6 +229,9 @@ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRul /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleNodeReferenceContext} to locate a node identifier. + * + * @param ctx the context to start from. + * @return the node identifier or null if not found. */ protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { @@ -576,6 +579,8 @@ public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicat * Any field references in the predicate must be resolved relative to the field on which the * predicate is applied. Therefore we need to switch to the field's context while the predicate is * being parsed. + * + * @param ctx the predicate context */ @Override public void enterPredicate(EfxParser.PredicateContext ctx) { @@ -585,6 +590,8 @@ public void enterPredicate(EfxParser.PredicateContext ctx) { /** * After the predicate is parsed we need to switch back to the previous context. + * + * @param ctx the predicate context */ @Override public void exitPredicate(EfxParser.PredicateContext ctx) { @@ -638,6 +645,8 @@ public void exitUntypedAttributeValueReference(UntypedAttributeValueReferenceCon /** * Handles expressions of the form ContextField::ReferencedField. Changes the context before the * reference is resolved. + * + * @param ctx the context field context */ @Override public void exitFieldContext(FieldContextContext ctx) { @@ -653,6 +662,8 @@ public void exitFieldContext(FieldContextContext ctx) { /** * Handles expressions of the form ContextField::ReferencedField. Changes the context before the * reference is resolved. + * + * @param ctx the parser rule context */ @Override public void exitFieldReferenceWithFieldContextOverride( @@ -667,6 +678,8 @@ public void exitFieldReferenceWithFieldContextOverride( /** * Handles expressions of the form ContextNode::ReferencedField. Changes the context before the * reference is resolved. + * + * @param ctx the parser rule context */ @Override public void exitNodeContext(NodeContextContext ctx) { @@ -681,6 +694,8 @@ public void exitNodeContext(NodeContextContext ctx) { /** * Handles expressions of the form ContextNode::ReferencedField. Restores the context after the * reference is resolved. + * + * @param ctx the parser rule context */ @Override public void exitFieldReferenceWithNodeContextOverride( diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java index 9c4b12be..8afc1ec9 100644 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java +++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java @@ -141,6 +141,9 @@ private String getTranslatedScript() { /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleFieldReferenceContext} to locate a field identifier. + * + * @param ctx The context to start searching from. + * @return The field identifier, or null if none was found. */ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { @@ -184,6 +187,9 @@ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRul /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleNodeReferenceContext} to locate a node identifier. + * + * @param ctx The context to start searching from. + * @return The node identifier, or null if none was found. */ protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { 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 a6efb28b..7b023641 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -182,6 +182,9 @@ private String getTranslatedScript() { /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleFieldReferenceContext} to locate a field identifier. + * + * @param ctx the context to search in. + * @return the field identifier or null if none was found. */ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { @@ -227,6 +230,9 @@ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRul /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleNodeReferenceContext} to locate a node identifier. + * + * @param ctx the context to search in. + * @return the node identifier or null if none was found. */ protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { @@ -914,6 +920,8 @@ public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicat * Any field references in the predicate must be resolved relative to the node or field on which * the predicate is applied. Therefore we need to switch to that context while the predicate is * being parsed. + * + * @param ctx The predicate context */ @Override public void enterPredicate(EfxParser.PredicateContext ctx) { diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java index 7e15923d..226733b8 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -62,9 +62,10 @@ private static Queue getSteps(String xpath) { /** * Makes the given xpath relative to the given context xpath. - * @param contextXpath - * @param xpath - * @return + * + * @param contextXpath the context xpath + * @param xpath the xpath to contextualize + * @return the contextualized xpath */ public static PathExpression contextualize(final PathExpression contextXpath, final PathExpression xpath) { @@ -90,6 +91,10 @@ public static PathExpression addPredicate(final PathExpression pathExpression, f * It will add the predicate to the last axis-step in the xpath. * If there is no axis-step in the xpath then it will add the predicate to the last step. * If the xpath is empty then it will still return a PathExpression but with an empty xpath. + * + * @param xpath the xpath to add the predicate to + * @param predicate the predicate to add + * @return the xpath with the predicate added */ public static String addPredicate(final String xpath, final String predicate) { if (predicate == null) { From a0508e1edce2789c827be434d45c8bb5812931e9 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sat, 6 May 2023 15:00:02 +0200 Subject: [PATCH 23/57] Organized translator code into regions --- .../efx/sdk2/EfxExpressionTranslatorV2.java | 119 +++++++++++++----- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 31 +++-- 2 files changed, 114 insertions(+), 36 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 2a6b03a4..b064504d 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -59,8 +59,8 @@ * Apart from writing expressions that can be translated and evaluated in a target scripting * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The - * {@link EfxExpressionTranslatorV1} only focuses on EFX expressions. To translate EFX templates you - * need to use the {@link EfxTemplateTranslatorV1} which derives from this class. + * {@link EfxExpressionTranslatorV2} only focuses on EFX expressions. To translate EFX templates you + * need to use the {@link EfxTemplateTranslatorV2} which derives from this class. */ @SdkComponent(versions = {"2"}, componentType = SdkComponentType.EFX_EXPRESSION_TRANSLATOR) public class EfxExpressionTranslatorV2 extends EfxBaseListener @@ -189,6 +189,9 @@ private String getTranslatedScript() { /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleFieldReferenceContext} to locate a field identifier. + * + * @param ctx The context to start from. + * @return The field identifier, or null if none was found. */ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { @@ -234,6 +237,9 @@ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRul /** * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleNodeReferenceContext} to locate a node identifier. + * + * @param ctx The context to start from. + * @return The node identifier, or null if none was found. */ protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { @@ -271,7 +277,7 @@ public void exitSingleExpression(SingleExpressionContext ctx) { this.efxContext.pop(); } - /*** Boolean expressions ***/ + // #region Boolean expressions ---------------------------------------------- @Override public void exitParenthesizedBooleanExpression( @@ -294,7 +300,7 @@ public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { this.stack.push(this.script.composeLogicalOr(left, right)); } - /*** Boolean expressions - Comparisons ***/ + // #region Boolean expressions - Comparisons -------------------------------- @Override public void exitFieldValueComparison(FieldValueComparisonContext ctx) { @@ -349,7 +355,9 @@ public void exitDurationComparison(DurationComparisonContext ctx) { this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); } - /*** Boolean expressions - Conditions ***/ + // #endregion Boolean expressions - Comparisons ----------------------------- + + // #region Boolean expressions - Conditions -------------------------------- @Override public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { @@ -395,7 +403,9 @@ public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) this.stack.push(condition); } - /*** Boolean expressions - List membership conditions ***/ + // #endregion Boolean expressions - Conditions ------------------------------ + + // #region Boolean expressions - List membership conditions ----------------- @Override public void exitStringInListCondition(EfxParser.StringInListConditionContext ctx) { @@ -438,7 +448,11 @@ private > void exitInListCondi this.stack.push(condition); } - /*** Quantified expressions ***/ + // #endregion Boolean expressions - List membership conditions ----------------- + + // #endregion Boolean expressions ------------------------------------------- + + // #region Quantified expressions ------------------------------------------- @Override public void enterQuantifiedExpression(QuantifiedExpressionContext ctx) { @@ -460,7 +474,9 @@ public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { // here. } - /*** Numeric expressions ***/ + // #endregion Quantified expressions ---------------------------------------- + + // #region Numeric expressions ---------------------------------------------- @Override public void exitAdditionExpression(EfxParser.AdditionExpressionContext ctx) { @@ -482,7 +498,9 @@ public void exitParenthesizedNumericExpression(ParenthesizedNumericExpressionCon this.stack.pop(NumericExpression.class), NumericExpression.class)); } - /*** Duration Expressions ***/ + // #endregion Numeric expressions ------------------------------------------- + + // #region Duration Expressions --------------------------------------------- @Override public void exitDurationAdditionExpression(DurationAdditionExpressionContext ctx) { @@ -577,7 +595,9 @@ private > void exitList(int li this.stack.push(this.script.composeList(list, listType)); } - /*** Conditional Expressions ***/ + // #endregion Duration Expressions ------------------------------------------ + + // #region Conditional Expressions ------------------------------------------ @Override public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) { @@ -677,7 +697,9 @@ private void exitConditionalDurationExpression() { DurationExpression.class)); } - /*** Iterators ***/ + // #endregion Conditional Expressions --------------------------------------- + + // #region Iterators -------------------------------------------------------- @Override public void exitStringIteratorExpression(StringIteratorExpressionContext ctx) { @@ -858,7 +880,9 @@ public > void exitIterationExp .push(this.script.composeForExpression(iterators, expression, targetListType)); } - /*** Literals ***/ + // #endregion Iterators ----------------------------------------------------- + + // #region Literals --------------------------------------------------------- @Override public void exitNumericLiteral(NumericLiteralContext ctx) { @@ -895,7 +919,9 @@ public void exitDurationLiteral(DurationLiteralContext ctx) { this.stack.push(this.script.getDurationLiteralEquivalent(ctx.getText())); } - /*** References ***/ + // #endregion Literals ------------------------------------------------------ + + // #region References ------------------------------------------------------- @Override public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { @@ -938,7 +964,7 @@ public void exitAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { } - /*** References with Predicates ***/ + // #region References with Predicates --------------------------------------- @Override public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx) { @@ -992,7 +1018,9 @@ public void exitFieldReferenceWithAxis(FieldReferenceWithAxisContext ctx) { } } - /*** External References ***/ + // #endregion References with Predicates ------------------------------------ + + // #region External References ---------------------------------------------- @Override public void exitNoticeReference(EfxParser.NoticeReferenceContext ctx) { @@ -1019,7 +1047,9 @@ public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNotic } } - /*** Value References ***/ + // #endregion External References ------------------------------------------- + + // #region Value References ------------------------------------------------- @Override public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { @@ -1067,7 +1097,9 @@ public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext this.getAttributeName(ctx), StringExpression.class)); } - /*** References with context override ***/ + // #endregion Value References ---------------------------------------------- + + // #region References with context override --------------------------------- /** * Handles expressions of the form ContextField::ReferencedField. Changes the context before the @@ -1155,7 +1187,9 @@ public void exitFieldReferenceWithVariableContextOverride( } } - /*** Other References ***/ + // #endregion References with context override ------------------------------ + + // #region Other References ------------------------------------------------- @Override public void exitCodelistReference(CodelistReferenceContext ctx) { @@ -1171,7 +1205,11 @@ public void exitVariableReference(VariableReferenceContext ctx) { this.script.composeVariableReference(variableName, Expression.class)); } - /*** Indexers ***/ + // #endregion Other References ---------------------------------------------- + + // #endregion References ---------------------------------------------------- + + // #region Indexers --------------------------------------------------------- @Override public void exitStringAtSequenceIndex(StringAtSequenceIndexContext ctx) { @@ -1210,7 +1248,9 @@ private > void exitSequenceAtI this.stack.push(this.script.composeIndexer(list, index, itemType)); } - /*** Parameter Declarations ***/ + // #endregion Indexers ------------------------------------------------------ + + // #region Parameter Declarations ------------------------------------------- @Override @@ -1254,7 +1294,9 @@ private void exitParameterDeclaration(String parameterNam this.translateParameter(this.expressionParameters.pop(), parameterType)); } - /*** Variable Declarations ***/ + // #endregion Parameter Declarations ---------------------------------------- + + // #region Variable Declarations -------------------------------------------- @Override public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { @@ -1305,7 +1347,9 @@ public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx this.script.composeVariableDeclaration(variableName, ContextExpression.class)); } - /*** Boolean functions ***/ + // #endregion Variable Declarations ----------------------------------------- + + // #region Boolean functions ------------------------------------------------ @Override public void exitNotFunction(NotFunctionContext ctx) { @@ -1340,7 +1384,9 @@ public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { this.stack.push(this.script.composeSequenceEqualFunction(one, two)); } - /*** Numeric functions ***/ + // #endregion Boolean functions --------------------------------------------- + + // #region Numeric functions ------------------------------------------------ @Override public void exitCountFunction(CountFunctionContext ctx) { @@ -1364,7 +1410,9 @@ public void exitStringLengthFunction(StringLengthFunctionContext ctx) { .push(this.script.composeStringLengthCalculation(this.stack.pop(StringExpression.class))); } - /*** String functions ***/ + // #endregion Numeric functions --------------------------------------------- + + // #region String functions ------------------------------------------------- @Override public void exitSubstringFunction(SubstringFunctionContext ctx) { @@ -1412,7 +1460,9 @@ public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { this.stack.push(this.script.composeNumberFormatting(number, format)); } - /*** Date functions ***/ + // #endregion String functions ---------------------------------------------- + + // #region Date functions --------------------------------------------------- @Override public void exitDateFromStringFunction(DateFromStringFunctionContext ctx) { @@ -1433,14 +1483,18 @@ public void exitDateMinusMeasureFunction(DateMinusMeasureFunctionContext ctx) { this.stack.push(this.script.composeSubtraction(left, right)); } - /*** Time functions ***/ + // #endregion Date functions ------------------------------------------------ + + // #region Time functions --------------------------------------------------- @Override public void exitTimeFromStringFunction(TimeFromStringFunctionContext ctx) { this.stack.push(this.script.composeToTimeConversion(this.stack.pop(StringExpression.class))); } - /*** Duration Functions ***/ + // #endregion Time functions ---------------------------------------------- + + // #region Duration Functions ----------------------------------------------- @Override public void exitDayTimeDurationFromStringFunction(DayTimeDurationFromStringFunctionContext ctx) { @@ -1455,7 +1509,9 @@ public void exitYearMonthDurationFromStringFunction( this.script.composeToYearMonthDurationConversion(this.stack.pop(StringExpression.class))); } - /*** Sequence Functions ***/ + // #endregion Duration Functions -------------------------------------------- + + // #region Sequence Functions ----------------------------------------------- @Override public void exitDistinctValuesFunction(DistinctValuesFunctionContext ctx) { @@ -1568,6 +1624,10 @@ private > void exitExceptFunct this.stack.push(this.script.composeExceptFunction(one, two, listType)); } + // #endregion Sequence Functions -------------------------------------------- + + // #region Helpers ---------------------------------------------------------- + protected String getCodelistName(String efxCodelistIdentifier) { return StringUtils.substringAfter(efxCodelistIdentifier, CODELIST_PREFIX); } @@ -1643,4 +1703,7 @@ private String getVariableName(TimeParameterDeclarationContext ctx) { private String getVariableName(DurationParameterDeclarationContext ctx) { return this.getVariableName(ctx.Variable().getText()); } + + // #endregion Helpers ------------------------------------------------------- + } 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 17773081..748eeeef 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -49,7 +49,7 @@ import eu.europa.ted.efx.xpath.XPathContextualizer; /** - * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV1} to provide additional + * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV2} to provide additional * translation capabilities for EFX templates. If has been implemented as an extension to the * EfxExpressionTranslator in order to keep things simpler when one only needs to translate EFX * expressions (like the condition associated with a business rule). @@ -192,7 +192,7 @@ private String getTranslatedMarkup() { return sb.toString().trim(); } - /*** Template File ***/ + // #region Template File ---------------------------------------------------- @Override public void enterTemplateFile(TemplateFileContext ctx) { @@ -213,7 +213,9 @@ public void exitTemplateFile(TemplateFileContext ctx) { this.stack.push(file); } - /*** Source template blocks ***/ + // #endregion Template File ------------------------------------------------- + + // #region Source template blocks ------------------------------------------- @Override public void exitTextTemplate(TextTemplateContext ctx) { @@ -240,7 +242,9 @@ public void exitExpressionTemplate(ExpressionTemplateContext ctx) { } - /*** Label Blocks #{...} ***/ + // #endregion Source template blocks ---------------------------------------- + + // #region Label Blocks #{...} ---------------------------------------------- @Override public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { @@ -411,7 +415,9 @@ public void exitAssetId(AssetIdContext ctx) { } } - /*** Expression Blocks ${...} ***/ + // #endregion Label Blocks #{...} ------------------------------------------- + + // #region Expression Blocks ${...} ----------------------------------------- /** * Handles a standard expression block in a template line. Most of the work is done by the base @@ -439,7 +445,9 @@ public void exitShorthandFieldValueReferenceFromContextField( Expression.class)); } - /*** Context Declaration Blocks {...} ***/ + // #endregion Expression Blocks ${...} -------------------------------------- + + // #region Context Declaration Blocks {...} --------------------------------- /** * This method changes the current EFX context. @@ -539,7 +547,9 @@ private > void exitVariableInitializ } } - /*** Template lines ***/ + // #endregion Context Declaration Blocks {...} ------------------------------ + + // #region Template lines -------------------------------------------------- @Override public void enterTemplateLine(TemplateLineContext ctx) { @@ -624,7 +634,9 @@ private Context relativizeContext(Context childContext, Context parentContext) { this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); } - /*** Helpers ***/ + // #endregion Template lines ----------------------------------------------- + + // #region Helpers ---------------------------------------------------------- private int getIndentLevel(TemplateLineContext ctx) { if (ctx.MixedIndent() != null) { @@ -683,4 +695,7 @@ private String getVariableName(DurationVariableInitializerContext ctx) { private String getVariableName(ContextVariableInitializerContext ctx) { return this.getVariableName(ctx.Variable().getText()); } + + // #endregion Helpers ------------------------------------------------------- + } From 7933c2343d2fb4e2a244095c0fe94677d26aaa30 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sat, 6 May 2023 23:26:19 +0200 Subject: [PATCH 24/57] Added an EFX prep-processor --- .../eu/europa/ted/efx/model/CallStack.java | 18 +- .../efx/sdk2/EfxExpressionTranslatorV2.java | 465 ++++++++++++++---- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 196 ++++++-- .../sdk2/EfxExpressionTranslatorV2Test.java | 20 +- src/test/resources/json/fields-sdk2.json | 46 +- 5 files changed, 587 insertions(+), 158 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java index ee23f3d9..ead839e7 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -86,10 +86,10 @@ void storeValue(String identifier, Expression value) { * @return The object removed from the top of the stack. */ synchronized T pop(Class expectedType) { - Class actualType = peek().getClass(); + Class actualType = this.peek().getClass(); if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) { throw new ParseCancellationException(String.format(TYPE_MISMATCH, - expectedType.getSimpleName(), this.peek().getClass().getSimpleName())); + expectedType.getSimpleName(), actualType.getSimpleName())); } return expectedType.cast(this.pop()); } @@ -244,6 +244,20 @@ Optional> getVariable(String identifier) { .map(x -> x.typeRegister.get(identifier)); } + /** + * Gets the type of a variable. + * + * @param identifier The identifier of the variable. + * @return The type of the variable. + */ + public Class getTypeOfIdentifier(String identifier) { + Optional> type = this.getVariable(identifier); + if (!type.isPresent()) { + throw new ParseCancellationException(UNDECLARED_IDENTIFIER + identifier); + } + return type.get(); + } + /** * Pushes a variable reference on the current stack frame. Makes sure there is no name collision * with other identifiers already in scope. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index b064504d..842d7ecb 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1,28 +1,34 @@ package eu.europa.ted.efx.sdk2; +import static java.util.Map.entry; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; + import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; 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.TokenStreamRewriter; 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; import org.apache.commons.lang3.StringUtils; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.CallStack; -import eu.europa.ted.efx.model.CallStackObject; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; @@ -79,12 +85,6 @@ public class EfxExpressionTranslatorV2 extends EfxBaseListener private static final String BEGIN_EXPRESSION_BLOCK = "{"; private static final String END_EXPRESSION_BLOCK = "}"; - /** - * - */ - private static final String TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES = - "Type mismatch. Cannot compare values of different types: "; - /** * The stack is used by the methods of this listener to pass data to each other as the parse tree * is being walked. @@ -126,8 +126,11 @@ public EfxExpressionTranslatorV2(final SymbolResolver symbolResolver, public String translateExpression(final String expression, final String... parameters) { this.expressionParameters.addAll(Arrays.asList(parameters)); + final ExpressionPreprocessor preprocessor = this.new ExpressionPreprocessor(expression); + final String preprocessedExpression = preprocessor.processExpression(); + final EfxLexer lexer = - new EfxLexer(CharStreams.fromString(expression)); + new EfxLexer(CharStreams.fromString(preprocessedExpression)); final CommonTokenStream tokens = new CommonTokenStream(lexer); final EfxParser parser = new EfxParser(tokens); @@ -302,17 +305,6 @@ public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { // #region Boolean expressions - Comparisons -------------------------------- - @Override - public void exitFieldValueComparison(FieldValueComparisonContext ctx) { - Expression right = this.stack.pop(Expression.class); - Expression left = this.stack.pop(Expression.class); - if (!left.getClass().equals(right.getClass())) { - throw new ParseCancellationException(TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES - + left.getClass() + " and " + right.getClass()); - } - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - @Override public void exitStringComparison(StringComparisonContext ctx) { StringExpression right = this.stack.pop(StringExpression.class); @@ -599,26 +591,6 @@ private > void exitList(int li // #region Conditional Expressions ------------------------------------------ - @Override - public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) { - Class typeWhenFalse = this.stack.peek().getClass(); - if (typeWhenFalse == BooleanExpression.class) { - this.exitConditionalBooleanExpression(); - } else if (typeWhenFalse == NumericExpression.class) { - this.exitConditionalNumericExpression(); - } else if (typeWhenFalse == StringExpression.class) { - this.exitConditionalStringExpression(); - } else if (typeWhenFalse == DateExpression.class) { - this.exitConditionalDateExpression(); - } else if (typeWhenFalse == TimeExpression.class) { - this.exitConditionalTimeExpression(); - } else if (typeWhenFalse == DurationExpression.class) { - this.exitConditionalDurationExpression(); - } else { - throw new IllegalStateException("Unknown type " + typeWhenFalse); - } - } - @Override public void exitConditionalBooleanExpression(ConditionalBooleanExpressionContext ctx) { this.exitConditionalBooleanExpression(); @@ -1200,7 +1172,7 @@ public void exitCodelistReference(CodelistReferenceContext ctx) { @Override public void exitVariableReference(VariableReferenceContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableReference(variableName, this.script.composeVariableReference(variableName, Expression.class)); } @@ -1255,32 +1227,32 @@ private > void exitSequenceAtI @Override public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), StringExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), StringExpression.class); } @Override public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), NumericExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), NumericExpression.class); } @Override public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), BooleanExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), BooleanExpression.class); } @Override public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), DateExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), DateExpression.class); } @Override public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), TimeExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), TimeExpression.class); } @Override public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), DurationExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), DurationExpression.class); } private void exitParameterDeclaration(String parameterName, @@ -1300,49 +1272,49 @@ private void exitParameterDeclaration(String parameterNam @Override public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableDeclaration(variableName, this.script.composeVariableDeclaration(variableName, StringExpression.class)); } @Override public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableDeclaration(variableName, this.script.composeVariableDeclaration(variableName, BooleanExpression.class)); } @Override public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableDeclaration(variableName, this.script.composeVariableDeclaration(variableName, NumericExpression.class)); } @Override public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableDeclaration(variableName, this.script.composeVariableDeclaration(variableName, DateExpression.class)); } @Override public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableDeclaration(variableName, this.script.composeVariableDeclaration(variableName, TimeExpression.class)); } @Override public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableDeclaration(variableName, this.script.composeVariableDeclaration(variableName, DurationExpression.class)); } @Override public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); + String variableName = getVariableName(ctx); this.stack.pushVariableDeclaration(variableName, this.script.composeVariableDeclaration(variableName, ContextExpression.class)); } @@ -1625,9 +1597,13 @@ private > void exitExceptFunct } // #endregion Sequence Functions -------------------------------------------- - - // #region Helpers ---------------------------------------------------------- + // #region Helpers ---------------------------------------------------------- + + protected static String getLexerSymbol(int tokenType) { + return EfxLexer.VOCABULARY.getLiteralName(tokenType).replaceAll("^'|'$", ""); + } + protected String getCodelistName(String efxCodelistIdentifier) { return StringUtils.substringAfter(efxCodelistIdentifier, CODELIST_PREFIX); } @@ -1644,66 +1620,387 @@ private String getAttributeName(ScalarFromAttributeReferenceContext ctx) { return this.getAttributeName(ctx.attributeReference().Attribute().getText()); } - protected String getVariableName(String efxVariableIdentifier) { + static protected String getVariableName(String efxVariableIdentifier) { return StringUtils.substringAfter(efxVariableIdentifier, VARIABLE_PREFIX); } - private String getVariableName(VariableReferenceContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(VariableReferenceContext ctx) { + return getVariableName(ctx.Variable().getText()); } - protected String getVariableName(ContextVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static protected String getVariableName(ContextVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(StringVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(StringVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(NumericVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(NumericVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(BooleanVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(BooleanVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DateVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DateVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(TimeVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(TimeVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DurationVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DurationVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(StringParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(StringParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(NumericParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(NumericParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(BooleanParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(BooleanParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DateParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DateParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(TimeParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(TimeParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DurationParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DurationParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } // #endregion Helpers ------------------------------------------------------- + // #region Pre-processing --------------------------------------------------- + + private static String textTypeName = getLexerSymbol(EfxLexer.Text); + private static String booleanTypeName = getLexerSymbol(EfxLexer.Indicator); + private static String numericTypeName = getLexerSymbol(EfxLexer.Number); + private static String dateTypeName = getLexerSymbol(EfxLexer.Date); + private static String timeTypeName = getLexerSymbol(EfxLexer.Time); + private static String durationTypeName = getLexerSymbol(EfxLexer.Measure); + + private static final Map eFormsToEfxTypeMap = Map.ofEntries(entry("id", textTypeName), // + entry("id-ref", textTypeName), // + entry("text", textTypeName), // + entry("text-multilingual", textTypeName), // + entry("indicator", booleanTypeName), // + entry("amount", numericTypeName), // + entry("number", numericTypeName), // + entry("measure", durationTypeName), // + entry("code", textTypeName), // + entry("internal-code", textTypeName), // + entry("integer", numericTypeName), // + entry("date", dateTypeName), // + entry("zoned-date", dateTypeName), // + entry("time", timeTypeName), // + entry("zoned-time", timeTypeName), // + entry("url", textTypeName), // + entry("phone", textTypeName), // + entry("email", textTypeName)); + + private static final Map, String> javaToEfxTypeMap = Map.ofEntries( + entry(StringExpression.class, textTypeName), // + entry(BooleanExpression.class, booleanTypeName), // + entry(NumericExpression.class, numericTypeName), // + entry(DurationExpression.class, durationTypeName), // + entry(DateExpression.class, dateTypeName), // + entry(TimeExpression.class, timeTypeName)); + + /** + * The EFX expression pre-processor is used to remove expression ambiguities + * that cannot be addressed by the EFX grammar itself. The EFX grammar tries to + * enforce type checking to the extent possible, however, the types of fields, + * variables and expression parameters (as well as the type of some expressions + * that reference them) cannot be determined until the EFX expression is being + * parsed. For example, adding a duration to a date has different semantics than + * adding two numbers together. + * + * Expressions referencing fields, variables and parameters are called + * late-bound expressions in EFX because their type cannot be inferred by the + * syntax but instead needs to be determined by the parser. Since the types of + * late-bound expressions are critical in determining the correct parse tree, a + * one-pass parser would require the use of type casting in the EFX expression + * itself to resolve any ambiguities. The role of the expression preprocessor is + * therefore to do a first pass on the EFX expression to insert type casts where + * necessary. + */ + class ExpressionPreprocessor extends EfxBaseListener { + final SymbolResolver symbols; + final BaseErrorListener errorListener; + final EfxLexer lexer; + final CommonTokenStream tokens; + final EfxParser parser; + final TokenStreamRewriter rewriter; + final CallStack stack = new CallStack(); + + ExpressionPreprocessor(String expression) { + this(CharStreams.fromString(expression)); + } + + ExpressionPreprocessor(final CharStream charStream) { + super(); + + this.symbols = EfxExpressionTranslatorV2.this.symbols; + this.errorListener = EfxExpressionTranslatorV2.this.errorListener; + + this.lexer = new EfxLexer(charStream); + this.tokens = new CommonTokenStream(lexer); + this.parser = new EfxParser(tokens); + this.rewriter = new TokenStreamRewriter(tokens); + + if (this.errorListener != null) { + lexer.removeErrorListeners(); + lexer.addErrorListener(this.errorListener); + parser.removeErrorListeners(); + parser.addErrorListener(this.errorListener); + } + } + + String processExpression() { + final ParseTree tree = parser.singleExpression(); + final ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(this, tree); + return this.rewriter.getText(); + } + + @Override + public void exitUntypedFieldReferenceExpression(UntypedFieldReferenceExpressionContext ctx) { + if (!hasParentContextOfType(ctx, LateBoundExpressionContext.class)) { + return; + } + + String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + String fieldType = eFormsToEfxTypeMap.get(this.symbols.getTypeOfField(fieldId)); + + // Insert the type cast + this.rewriter.insertBefore(ctx.getStart(), "(" + fieldType + ")"); + } + + @Override + public void exitUntypedSequenceExpression(UntypedSequenceExpressionContext ctx) { + if (!hasParentContextOfType(ctx, LateBoundExpressionContext.class)) { + return; + } + + String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + String fieldType = eFormsToEfxTypeMap.get(this.symbols.getTypeOfField(fieldId)); + + if (fieldType != null) { + // Insert the type cast + this.rewriter.insertBefore(ctx.getStart(), "(" + fieldType + ")"); + } + } + + @Override + public void exitVariableReference(VariableReferenceContext ctx) { + if (!hasParentContextOfType(ctx, LateBoundExpressionContext.class)) { + return; + } + + String variableName = getVariableName(ctx); + String variableType = javaToEfxTypeMap.get(this.stack.getTypeOfIdentifier(variableName)); + + if (variableType != null) { + // Insert the type cast + this.rewriter.insertBefore(ctx.Variable().getSymbol(), "(" + variableType + ")"); + } + } + + boolean hasParentContextOfType(ParserRuleContext ctx, Class parentClass) { + ParserRuleContext parent = ctx.getParent(); + while (parent != null) { + if (parentClass.isInstance(parent)) { + return true; + } + parent = parent.getParent(); + } + return false; + } + + // #region Variable declarations ------------------------------------------ + + @Override + public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { + String variableName = getVariableName(ctx); + this.stack.declareTemplateVariable(variableName, StringExpression.class); + } + + @Override + public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { + String variableName = getVariableName(ctx); + this.stack.declareTemplateVariable(variableName, BooleanExpression.class); + } + + @Override + public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { + String variableName = getVariableName(ctx); + this.stack.declareTemplateVariable(variableName, NumericExpression.class); + } + + @Override + public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { + String variableName = getVariableName(ctx); + this.stack.declareTemplateVariable(variableName, DateExpression.class); + } + + @Override + public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { + String variableName = getVariableName(ctx); + this.stack.declareTemplateVariable(variableName, TimeExpression.class); + } + + @Override + public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { + String variableName = getVariableName(ctx); + this.stack.declareTemplateVariable(variableName, DurationExpression.class); + } + + @Override + public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) { + String variableName = getVariableName(ctx); + this.stack.declareTemplateVariable(variableName, ContextExpression.class); + } + + // #endregion Variable declarations --------------------------------------- + + // #region Parameter declarations ----------------------------------------- + + @Override + public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) { + String identifier = getVariableName(ctx); + this.stack.declareTemplateVariable(identifier, StringExpression.class); + } + + @Override + public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) { + String identifier = getVariableName(ctx); + this.stack.declareTemplateVariable(identifier, NumericExpression.class); + } + + @Override + public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) { + String identifier = getVariableName(ctx); + this.stack.declareTemplateVariable(identifier, BooleanExpression.class); + } + + @Override + public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) { + String identifier = getVariableName(ctx); + this.stack.declareTemplateVariable(identifier, DateExpression.class); + } + + @Override + public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) { + String identifier = getVariableName(ctx); + this.stack.declareTemplateVariable(identifier, TimeExpression.class); + } + + @Override + public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) { + String identifier = getVariableName(ctx); + this.stack.declareTemplateVariable(identifier, DurationExpression.class); + } + + // #endregion Parameter declarations -------------------------------------- + + // #region Scope management ----------------------------------------------- + + @Override + public void enterQuantifiedExpression(QuantifiedExpressionContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { + this.stack.popStackFrame(); + } + + @Override + public void enterStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { + this.stack.popStackFrame(); + } + + @Override + public void enterNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { + this.stack.popStackFrame(); + } + + @Override + public void enterBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { + this.stack.popStackFrame(); + } + + @Override + public void enterDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { + this.stack.popStackFrame(); + } + + @Override + public void enterTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { + this.stack.popStackFrame(); + } + + @Override + public void enterDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { + this.stack.popStackFrame(); + } + + @Override + public void enterLateBoundSequenceFromIteration(LateBoundSequenceFromIterationContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitLateBoundSequenceFromIteration(LateBoundSequenceFromIterationContext ctx) { + this.stack.popStackFrame(); + } + + // #endregion Scope management -------------------------------------------- + + } + + // #endregion Pre-processing ------------------------------------------------ + } 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 748eeeef..f3050d05 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -6,6 +6,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Stack; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; @@ -69,23 +70,14 @@ public class EfxTemplateTranslatorV2 extends EfxExpressionTranslatorV2 "Do not mix indentation methods. Stick with either tabs or spaces."; private static final String UNEXPECTED_INDENTATION = "Unexpected indentation tracker state."; - private static final String LABEL_TYPE_NAME = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.LABEL_TYPE_NAME).replaceAll("^'|'$", ""); - private static final String LABEL_TYPE_WHEN = EfxLexer.VOCABULARY - .getLiteralName(EfxLexer.LABEL_TYPE_WHEN_TRUE).replaceAll("^'|'$", "").replace("-true", ""); - private static final String SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ValueKeyword).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_INDICATOR = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_INDICATOR).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_BT = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_BT).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_FIELD = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_FIELD).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_NODE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_NODE).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_CODE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_CODE).replaceAll("^'|'$", ""); - + private static final String LABEL_TYPE_NAME = getLexerSymbol(EfxLexer.LABEL_TYPE_NAME); + private static final String LABEL_TYPE_WHEN = getLexerSymbol(EfxLexer.LABEL_TYPE_WHEN_TRUE).replace("-true", ""); + private static final String SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE = getLexerSymbol(EfxLexer.ValueKeyword); + private static final String ASSET_TYPE_INDICATOR = getLexerSymbol(EfxLexer.Indicator); + private static final String ASSET_TYPE_BT = getLexerSymbol(EfxLexer.ASSET_TYPE_BT); + private static final String ASSET_TYPE_FIELD = getLexerSymbol(EfxLexer.ASSET_TYPE_FIELD); + private static final String ASSET_TYPE_NODE = getLexerSymbol(EfxLexer.ASSET_TYPE_NODE); + private static final String ASSET_TYPE_CODE = getLexerSymbol(EfxLexer.Code); /** * Used to control the indentation style used in a template @@ -150,7 +142,10 @@ public String renderTemplate(final InputStream stream) throws IOException { private String renderTemplate(final CharStream charStream) { logger.debug("Rendering template"); - final EfxLexer lexer = new EfxLexer(charStream); + final TemplatePreprocessor preprocessor = this.new TemplatePreprocessor(charStream); + final String preprocessedTemplate = preprocessor.processTemplate(); + + final EfxLexer lexer = new EfxLexer(CharStreams.fromString(preprocessedTemplate)); final CommonTokenStream tokens = new CommonTokenStream(lexer); final EfxParser parser = new EfxParser(tokens); @@ -485,7 +480,7 @@ private Variable getContextVariable(ContextDeclarationContext ct if (ctx.contextVariableInitializer() == null) { return null; } - final String variableName = this.getVariableName(ctx.contextVariableInitializer()); + final String variableName = getVariableName(ctx.contextVariableInitializer()); return new Variable<>(variableName, XPathContextualizer.contextualize(contextPath, contextPath), this.script.composeVariableReference(variableName, PathExpression.class)); } @@ -497,37 +492,37 @@ public void enterTemplateVariableList(TemplateVariableListContext ctx) { @Override public void exitStringVariableInitializer(StringVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), StringVariable.class, + this.exitVariableInitializer(getVariableName(ctx), StringVariable.class, StringExpression.class); } @Override public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), BooleanVariable.class, + this.exitVariableInitializer(getVariableName(ctx), BooleanVariable.class, BooleanExpression.class); } @Override public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), NumericVariable.class, + this.exitVariableInitializer(getVariableName(ctx), NumericVariable.class, NumericExpression.class); } @Override public void exitDateVariableInitializer(DateVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), DateVariable.class, + this.exitVariableInitializer(getVariableName(ctx), DateVariable.class, DateExpression.class); } @Override public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), TimeVariable.class, + this.exitVariableInitializer(getVariableName(ctx), TimeVariable.class, TimeExpression.class); } @Override public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { - this.exitVariableInitializer(this.getVariableName(ctx), DurationVariable.class, + this.exitVariableInitializer(getVariableName(ctx), DurationVariable.class, DurationExpression.class); } @@ -668,34 +663,157 @@ private int getIndentLevel(TemplateLineContext ctx) { return 0; } - private String getVariableName(StringVariableInitializerContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(StringVariableInitializerContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(NumericVariableInitializerContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(NumericVariableInitializerContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(BooleanVariableInitializerContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(BooleanVariableInitializerContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DateVariableInitializerContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DateVariableInitializerContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(TimeVariableInitializerContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(TimeVariableInitializerContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DurationVariableInitializerContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DurationVariableInitializerContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(ContextVariableInitializerContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(ContextVariableInitializerContext ctx) { + return getVariableName(ctx.Variable().getText()); } // #endregion Helpers ------------------------------------------------------- + // #region Pre-processing ------------------------------------------------- + + /** + * This class is used to pre-process the template before it is actually translated. + * For details, see the comments in the base class {@link ExpressionPreprocessor}. + */ + class TemplatePreprocessor extends ExpressionPreprocessor { + + TemplatePreprocessor(CharStream template) { + super(template); + } + + String processTemplate() { + final ParseTree tree = parser.templateFile(); + final ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(this, tree); + return this.rewriter.getText(); + } + + // #region Template Variables --------------------------------------------- + + @Override + public void exitContextDeclaration(ContextDeclarationContext ctx) { + final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + if (filedId != null) { + final ContextVariableInitializerContext initializer = ctx.contextVariableInitializer(); + if (initializer != null) { + this.stack.declareTemplateVariable(getVariableName(initializer), + Expression.types.get(this.symbols.getTypeOfField(filedId))); + } + } + } + + @Override + public void exitStringVariableInitializer(StringVariableInitializerContext ctx) { + this.stack.declareTemplateVariable(getVariableName(ctx), StringExpression.class); + } + + @Override + public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) { + this.stack.declareTemplateVariable(getVariableName(ctx), BooleanExpression.class); + } + + @Override + public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) { + this.stack.declareTemplateVariable(getVariableName(ctx), NumericExpression.class); + } + + @Override + public void exitDateVariableInitializer(DateVariableInitializerContext ctx) { + this.stack.declareTemplateVariable(getVariableName(ctx), DateExpression.class); + } + + @Override + public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { + this.stack.declareTemplateVariable(getVariableName(ctx), TimeExpression.class); + } + + @Override + public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { + this.stack.declareTemplateVariable(getVariableName(ctx), DurationExpression.class); + } + + // #endregion Template Variables ------------------------------------------ + + // #region Scope management -------------------------------------------- + + Stack levels = new Stack(); + + @Override + public void enterTemplateLine(TemplateLineContext ctx) { + final int indentLevel = EfxTemplateTranslatorV2.this.getIndentLevel(ctx); + final int indentChange = indentLevel - (this.levels.isEmpty() ? 0 : this.levels.peek()); + if (indentChange > 1) { + throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); + } else if (indentChange == 1) { + if (this.levels.isEmpty()) { + throw new ParseCancellationException(START_INDENT_AT_ZERO); + } + this.stack.pushStackFrame(); // Create a stack frame for the new template line. + } else if (indentChange < 0) { + for (int i = indentChange; i < 0; i++) { + assert !this.levels.isEmpty() : UNEXPECTED_INDENTATION; + assert this.levels.peek() > indentLevel : UNEXPECTED_INDENTATION; + this.levels.pop(); + this.stack.popStackFrame(); // Each skipped indentation level must go out of scope. + } + this.stack.popStackFrame(); + this.stack.pushStackFrame(); + assert this.levels.peek() == indentLevel : UNEXPECTED_INDENTATION; + } else if (indentChange == 0) { + this.stack.popStackFrame(); + this.stack.pushStackFrame(); + } + } + + @Override + public void exitTemplateLine(TemplateLineContext ctx) { + final int indentLevel = EfxTemplateTranslatorV2.this.getIndentLevel(ctx); + final int indentChange = indentLevel - (this.levels.isEmpty() ? 0 : this.levels.peek()); + assert this.stack.empty() : "Stack should be empty at this point."; + + if (indentChange > 1) { + throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); + } else if (indentChange == 1) { + if (this.levels.isEmpty()) { + throw new ParseCancellationException(START_INDENT_AT_ZERO); + } + this.levels.push(this.levels.peek() + 1); + } else if (indentChange == 0) { + + if (this.levels.isEmpty()) { + assert indentLevel == 0 : UNEXPECTED_INDENTATION; + this.levels.push(0); + } + } + } + + // #endregion Scope management -------------------------------------------- + + } + + // #endregion Pre-processing ------------------------------------------------ } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 70f7e1a4..ff77145c 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -91,14 +91,14 @@ void testLikePatternCondition_WithNot() { void testFieldValueComparison_UsingTextFields() { testExpressionTranslationWithContext( "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", - "Root", "text == textMultilingual"); + "Root", "textField == textMultilingualField"); } @Test void testFieldValueComparison_UsingNumericFields() { testExpressionTranslationWithContext( "PathNode/NumberField/number() <= PathNode/IntegerField/number()", "ND-Root", - "BT-00-Number <= integer"); + "BT-00-Number <= integerField"); } @Test @@ -138,7 +138,7 @@ void testFieldValueComparison_WithStringLiteral() { void testFieldValueComparison_WithNumericLiteral() { testExpressionTranslationWithContext( "PathNode/IntegerField/number() - PathNode/NumberField/number() > 0", "ND-Root", - "integer - BT-00-Number > 0"); + "integerField - BT-00-Number > 0"); } @Test @@ -268,7 +268,7 @@ void testNegativeDuration_ViaMultiplication() { void testNegativeDuration_ViaMultiplicationWithField() { assertEquals( "(-3 * (2 * (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ()))))", - translateExpressionWithContext("ND-Root", "2 * measure:BT-00-Measure * -3")); + translateExpressionWithContext("ND-Root", "2 * (measure)BT-00-Measure * -3")); } @Test @@ -1078,19 +1078,19 @@ void testFieldReferenceWithFieldContextOverride() { @Test void testFieldReferenceWithFieldContextOverride_WithIntegerField() { testExpressionTranslationWithContext("../IntegerField/number()", "BT-00-Code", - "BT-01-SubLevel-Text::integer"); + "BT-01-SubLevel-Text::integerField"); } @Test void testFieldReferenceWithNodeContextOverride() { testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", - "ND-Root::integer"); + "ND-Root::integerField"); } @Test void testFieldReferenceWithNodeContextOverride_WithPredicate() { testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", - "ND-Root[BT-00-Indicator == TRUE]::integer"); + "ND-Root[BT-00-Indicator == TRUE]::integerField"); } @Test @@ -1114,7 +1114,7 @@ void testFieldReference_ForDurationFields() { @Test void testFieldReference_WithAxis() { testExpressionTranslationWithContext("./preceding::PathNode/IntegerField/number()", "ND-Root", - "ND-Root::preceding::integer"); + "ND-Root::preceding::integerField"); } // #endregion: References @@ -1616,14 +1616,14 @@ void testParameterizedExpression_WithDurationParameter() { @Test void testIndexer_WithFieldReference() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text())[1]", "ND-Root", "text:BT-00-Text[1]"); + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text())[1]", "ND-Root", "(text)BT-00-Text[1]"); } @Test void testIndexer_WithFieldReferenceAndPredicate() { testExpressionTranslationWithContext( "PathNode/TextField[./normalize-space(text()) = 'hello']/normalize-space(text())[1]", "ND-Root", - "text:BT-00-Text[BT-00-Text == 'hello'][1]"); + "(text)BT-00-Text[BT-00-Text == 'hello'][1]"); } @Test diff --git a/src/test/resources/json/fields-sdk2.json b/src/test/resources/json/fields-sdk2.json index a07130eb..d0ab5a2c 100644 --- a/src/test/resources/json/fields-sdk2.json +++ b/src/test/resources/json/fields-sdk2.json @@ -1,7 +1,7 @@ [ { "id": "BT-00-Text", - "alias": "text", + "alias": "textField", "type": "text", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/TextField", @@ -9,7 +9,7 @@ }, { "id": "BT-00-Attribute", - "alias": "attribute", + "alias": "attributeField", "type": "text", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/TextField/@Attribute", @@ -17,7 +17,7 @@ }, { "id": "BT-00-Indicator", - "alias": "indicator", + "alias": "indicatorField", "type": "indicator", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/IndicatorField", @@ -25,7 +25,7 @@ }, { "id": "BT-00-Code", - "alias": "code", + "alias": "codeField", "type": "code", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/CodeField", @@ -40,7 +40,7 @@ }, { "id": "BT-00-Internal-Code", - "alias": "internalCode", + "alias": "internalCodeField", "type": "internal-code", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/InternalCodeField", @@ -55,7 +55,7 @@ }, { "id": "BT-00-CodeAttribute", - "alias": "codeAttribute", + "alias": "codeAttributeField", "type": "code", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/CodeField/@attribute", @@ -70,7 +70,7 @@ }, { "id": "BT-00-Text-Multilingual", - "alias": "textMultilingual", + "alias": "textMultilingualField", "type": "text-multilingual", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/TextMultilingualField", @@ -78,7 +78,7 @@ }, { "id": "BT-00-StartDate", - "alias": "startDate", + "alias": "startDateField", "type": "date", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/StartDateField", @@ -86,7 +86,7 @@ }, { "id": "BT-00-EndDate", - "alias": "endDate", + "alias": "endDateField", "type": "date", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/EndDateField", @@ -94,7 +94,7 @@ }, { "id": "BT-00-StartTime", - "alias": "startTime", + "alias": "startTimeField", "type": "time", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/StartTimeField", @@ -102,7 +102,7 @@ }, { "id": "BT-00-EndTime", - "alias": "endTime", + "alias": "endTimeField", "type": "time", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/EndTimeField", @@ -110,7 +110,7 @@ }, { "id": "BT-00-Measure", - "alias": "measure", + "alias": "measureField", "type": "measure", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/MeasureField", @@ -118,7 +118,7 @@ }, { "id": "BT-00-Integer", - "alias": "integer", + "alias": "integerField", "type": "integer", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/IntegerField", @@ -126,7 +126,7 @@ }, { "id": "BT-00-Amount", - "alias": "amount", + "alias": "amountField", "type": "amount", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/AmountField", @@ -134,7 +134,7 @@ }, { "id": "BT-00-Url", - "alias": "url", + "alias": "urlField", "type": "url", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/UrlField", @@ -142,7 +142,7 @@ }, { "id": "BT-00-Zoned-Date", - "alias": "zonedDate", + "alias": "zonedDateField", "type": "zoned-date", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/ZonedDateField", @@ -150,7 +150,7 @@ }, { "id": "BT-00-Zoned-Time", - "alias": "zonedTime", + "alias": "zonedTimeField", "type": "zoned-time", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/ZonedTimeField", @@ -158,7 +158,7 @@ }, { "id": "BT-00-Id-Ref", - "alias": "idRef", + "alias": "idRefField", "type": "id-ref", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/IdRefField", @@ -166,7 +166,7 @@ }, { "id": "BT-00-Number", - "alias": "number", + "alias": "numberField", "type": "number", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/NumberField", @@ -174,7 +174,7 @@ }, { "id": "BT-00-Phone", - "alias": "phone", + "alias": "phoneField", "type": "phone", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/PhoneField", @@ -182,7 +182,7 @@ }, { "id": "BT-00-Email", - "alias": "email", + "alias": "emailField", "type": "email", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/EmailField", @@ -190,7 +190,7 @@ }, { "id": "BT-01-SubLevel-Text", - "alias": "subLevel_text", + "alias": "subLevel_textField", "type": "text", "parentNodeId": "ND-Root", "xpathAbsolute": "/*/PathNode/ChildNode/SubLevelTextField", @@ -198,7 +198,7 @@ }, { "id": "BT-01-SubNode-Text", - "alias": "subNode_text", + "alias": "subNode_textField", "type": "text", "parentNodeId": "ND-SubNode", "xpathAbsolute": "/*/SubNode/SubTextField", From 8c027110f20a31e108219c4c072821e03a2c734c Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sun, 7 May 2023 16:25:37 +0200 Subject: [PATCH 25/57] Added computed label references. --- .../eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java | 8 +++++++- .../europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java | 7 +++++++ 2 files changed, 14 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 f3050d05..ba9dc61c 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -254,6 +254,12 @@ public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { this.script.getStringLiteralFromUnquotedString("|"), assetId)))); } + @Override + public void exitComputedLabelReference(ComputedLabelReferenceContext ctx) { + StringExpression expression = this.stack.pop(StringExpression.class); + this.stack.push(this.markup.renderLabelFromExpression(expression)); + } + @Override public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) { StringExpression assetId = this.script.getStringLiteralFromUnquotedString(ctx.BtId().getText()); @@ -384,7 +390,7 @@ public void exitShorthandIndirectLabelReferenceFromContextField( ShorthandIndirectLabelReferenceFromContextFieldContext ctx) { if (!this.efxContext.isFieldContext()) { throw new ParseCancellationException( - "The #value shorthand syntax can only be used in a field is declared as context."); + "The #value shorthand syntax can only be used if a field is declared as context."); } this.shorthandIndirectLabelReference(this.efxContext.symbol()); } 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 a361c2df..d46776ed 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -212,6 +212,13 @@ void testStandardLabelReference_UsingLabelTypeAsAssetId() { translateTemplate("{BT-00-Text} #{auxiliary|text|value}")); } + @Test + void testComputedLabelReference() { + assertEquals( + "let block01() -> { label(string-join(('field','|','name','|','BT-00-Text'), ', ')) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{${string-join(('field', '|', 'name', '|', 'BT-00-Text'), ', ')}}")); + } + @Test void testShorthandBtLabelReference() { assertEquals( From c781641e67380ae6e7e75bdf0e5cb6d454e66f61 Mon Sep 17 00:00:00 2001 From: meletev <88482280+meletev@users.noreply.github.com> Date: Mon, 8 May 2023 10:17:38 +0200 Subject: [PATCH 26/57] [EfxTemplateTranslatorV2]: Fixed Javadoc. --- .../java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java | 2 +- 1 file changed, 1 insertion(+), 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 17773081..5bad6e0f 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -49,7 +49,7 @@ import eu.europa.ted.efx.xpath.XPathContextualizer; /** - * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV1} to provide additional + * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV2} to provide additional * translation capabilities for EFX templates. If has been implemented as an extension to the * EfxExpressionTranslator in order to keep things simpler when one only needs to translate EFX * expressions (like the condition associated with a business rule). From ea9e82e0dadb30e378fa2064ac70546743e5a921 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Tue, 9 May 2023 15:35:48 +0200 Subject: [PATCH 27/57] Added shortcut context declarations in template lines. --- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 76 +++++++++++++++---- .../efx/sdk2/EfxTemplateTranslatorV2Test.java | 20 +++++ 2 files changed, 83 insertions(+), 13 deletions(-) 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 ba9dc61c..017704e6 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -465,22 +465,72 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { @Override public void exitContextDeclaration(ContextDeclarationContext ctx) { - final PathExpression contextPath = this.stack.pop(PathExpression.class); - Variable contextVariable = this.getContextVariable(ctx, contextPath); - final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - if (filedId != null) { - this.efxContext.push(new FieldContext(filedId, contextPath, contextVariable)); - if (contextVariable != null) { - this.stack.declareTemplateVariable(contextVariable.name, - Expression.types.get(this.symbols.getTypeOfField(filedId))); - } - } else { - final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); - assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; - this.efxContext.push(new NodeContext(nodeId, contextPath)); + String shortcut = ctx.shortcut != null ? ctx.shortcut.getText() : "none"; + switch (shortcut) { + case ".": + this.exitSameContextDeclaration(); + break; + case "..": + this.exitParentContextDeclaration(); + break; + case "/": + this.exitRootContextDeclaration(); + break; + default: + PathExpression contextPath = this.stack.pop(PathExpression.class); + String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); + if (fieldId != null) { + Variable contextVariable = this.getContextVariable(ctx, contextPath); + this.exitFieldContextDeclaration(fieldId, contextPath, contextVariable); + } else { + String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); + assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; + this.exitNodeContextDeclaration(nodeId, contextPath); + } + break; + } + } + + private void exitSameContextDeclaration() { + Context currentContext = this.blockStack.currentContext(); + PathExpression contextPath = currentContext.absolutePath(); + String symbol = currentContext.symbol(); + if (currentContext.isFieldContext()) { + this.exitFieldContextDeclaration(symbol, contextPath, null); + } else if (currentContext.isNodeContext()) { + this.exitNodeContextDeclaration(symbol, contextPath); } } + private void exitParentContextDeclaration() { + Context parentContext = this.blockStack.parentContext(); + PathExpression contextPath = parentContext.absolutePath(); + String symbol = parentContext.symbol(); + if (parentContext.isFieldContext()) { + this.exitFieldContextDeclaration(symbol, contextPath, null); + } else if (parentContext.isNodeContext()) { + this.exitNodeContextDeclaration(symbol, contextPath); + } + } + + private void exitRootContextDeclaration() { + PathExpression contextPath = new PathExpression("/*"); + String symbol = "ND-Root"; + this.exitNodeContextDeclaration(symbol, contextPath); + } + + private void exitFieldContextDeclaration(String fieldId, PathExpression contextPath, Variable contextVariable) { + this.efxContext.push(new FieldContext(fieldId, contextPath, contextVariable)); + if (contextVariable != null) { + this.stack.declareTemplateVariable(contextVariable.name, + Expression.types.get(this.symbols.getTypeOfField(fieldId))); + } + } + + private void exitNodeContextDeclaration(String nodeId, PathExpression contextPath) { + this.efxContext.push(new NodeContext(nodeId, contextPath)); + } + private Variable getContextVariable(ContextDeclarationContext ctx, PathExpression contextPath) { if (ctx.contextVariableInitializer() == null) { 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 d46776ed..8ca01746 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -195,6 +195,26 @@ void testTemplateLine_ContextVariable() { } + @Test + void testTemplateLine_ContextDeclarationShortcuts() { + assertEquals( + translateTemplate(lines( + "{BT-00-Number} 1", + " {BT-00-Text} 2", + " {ND-Root} 3", + " {BT-00-Text} ${BT-00-Number}", + " {BT-00-Text} 5", + " {BT-00-Text} ${BT-00-Number}", // + " {BT-00-Number} ${BT-00-Text}")), // + translateTemplate(lines( + "{BT-00-Number} 1", // + " {BT-00-Text} 2", // + " {/} 3", // + " {..} ${BT-00-Number}", // + " {.} 5", // + " {.} ${BT-00-Number}", // + " {..} ${BT-00-Text}"))); + } /*** Labels ***/ From 92c6915fb15e01eee8d98fc17181c6e6cd30632c Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Tue, 9 May 2023 21:30:10 +0200 Subject: [PATCH 28/57] Added secondary template lines sharing the same context. --- .../europa/ted/efx/interfaces/MarkupGenerator.java | 2 ++ .../europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java | 5 +++++ .../eu/europa/ted/efx/mock/MarkupGeneratorMock.java | 5 +++++ .../ted/efx/sdk2/EfxTemplateTranslatorV2Test.java | 12 ++++++++++-- 4 files changed, 22 insertions(+), 2 deletions(-) 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 30f2f1a2..86b997b7 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -80,6 +80,8 @@ public interface MarkupGenerator { */ Markup renderFreeText(final String freeText); + Markup renderLineBreak(); + /** * @deprecated Use {@link #composeFragmentDefinition(String, String, Markup, Set)} instead. * 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 017704e6..905467c3 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -236,6 +236,11 @@ public void exitExpressionTemplate(ExpressionTemplateContext ctx) { this.stack.push(this.markup.renderVariableExpression(expression).join(template)); } + @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)); + } // #endregion Source template blocks ---------------------------------------- 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 d1e5c681..e31787e9 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -34,6 +34,11 @@ public Markup renderFreeText(String freeText) { return new Markup(String.format("text('%s')", freeText)); } + @Override + public Markup renderLineBreak() { + return new Markup(""); + } + @Override public Markup composeFragmentDefinition(String name, String number, Markup content) { return this.composeFragmentDefinition(name, number, content, new LinkedHashSet<>()); 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 8ca01746..b64f6202 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -216,6 +216,14 @@ void testTemplateLine_ContextDeclarationShortcuts() { " {..} ${BT-00-Text}"))); } + @Test + void testTemplateLine_WithSecondaryTemplate() { + assertEquals( + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text('some text') }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n some text")); + } + + /*** Labels ***/ @Test @@ -377,7 +385,7 @@ void testShorthandFieldValueReferenceFromContextField() { @Test void testShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( - "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", + "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' blah ')eval(.)text(' blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} blah #value blah $value blah")); } @@ -399,7 +407,7 @@ void testNestedExpression() { @Test void testEndOfLineComments() { assertEquals( - "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01())", + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' blah blah') }\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah")); } } From 89c2a9826034bfee2c59715dbc37ee03e9a5f795 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Wed, 10 May 2023 02:44:48 +0200 Subject: [PATCH 29/57] Added pluralization of labels --- .../ted/efx/interfaces/MarkupGenerator.java | 22 +++++++++++++ .../eu/europa/ted/efx/model/Expression.java | 4 +++ .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 32 +++++++++++-------- .../ted/efx/mock/MarkupGeneratorMock.java | 21 ++++++++++-- 4 files changed, 64 insertions(+), 15 deletions(-) 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 86b997b7..757f1c13 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -17,6 +17,7 @@ import java.util.Set; import org.apache.commons.lang3.tuple.Pair; import eu.europa.ted.efx.model.Expression; +import eu.europa.ted.efx.model.Expression.NumericExpression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Markup; @@ -61,6 +62,16 @@ public interface MarkupGenerator { */ Markup renderLabelFromKey(final StringExpression key); + /** + * Given a label key (which will eventually, at runtime, be dereferenced to a label text), this + * method returns the template code that renders this label in the target template language. + * + * @param key the label key to be dereferenced. + * @param quantity a numeric quantity used to decide if the label needs to be pluralized. + * @return the template code that renders the label in the target template language. + */ + Markup renderLabelFromKey(final StringExpression key, final NumericExpression quantity); + /** * Given an expression (which will eventually, at runtime, be evaluated to a label key and * subsequently dereferenced to a label text), this method returns the template code that renders @@ -71,6 +82,17 @@ public interface MarkupGenerator { */ Markup renderLabelFromExpression(final Expression expression); + /** + * Given an expression (which will eventually, at runtime, be evaluated to a label key and + * subsequently dereferenced to a label text), this method returns the template code that renders + * this label in the target template language. + * + * @param expression the expression that returns the label key. + * @param quantity a numeric quantity used to decide if the label needs to be pluralized. + * @return the template code that renders the label in the target template language. + */ + Markup renderLabelFromExpression(final Expression expression, final NumericExpression quantity); + /** * Given a string of free text, this method returns the template code that adds this text in the * target template. diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java index 4f30fdaf..c6f36b79 100644 --- a/src/main/java/eu/europa/ted/efx/model/Expression.java +++ b/src/main/java/eu/europa/ted/efx/model/Expression.java @@ -92,6 +92,10 @@ public static T empty(Class type) { return instantiate("", type); } + public final Boolean isEmpty() { + return this.script.isEmpty(); + } + @Override public boolean equals(Object obj) { if (obj == null) { 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 905467c3..84ad8848 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -248,6 +248,7 @@ public void exitSecondaryTemplate(SecondaryTemplateContext ctx) { @Override public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { + NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : Expression.empty(NumericExpression.class); StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class) : this.script.getStringLiteralFromUnquotedString(""); StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) @@ -256,49 +257,53 @@ public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { : this.script.getStringLiteralFromUnquotedString(""); this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(assetType, this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), assetId)))); + this.script.getStringLiteralFromUnquotedString("|"), assetId)), quantity)); } @Override public void exitComputedLabelReference(ComputedLabelReferenceContext ctx) { + NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : Expression.empty(NumericExpression.class); StringExpression expression = this.stack.pop(StringExpression.class); - this.stack.push(this.markup.renderLabelFromExpression(expression)); + this.stack.push(this.markup.renderLabelFromExpression(expression, quantity)); } @Override public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) { + NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : Expression.empty(NumericExpression.class); StringExpression assetId = this.script.getStringLiteralFromUnquotedString(ctx.BtId().getText()); StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) : this.script.getStringLiteralFromUnquotedString(""); 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)), quantity)); } @Override public void exitShorthandFieldLabelReference(ShorthandFieldLabelReferenceContext ctx) { final String fieldId = ctx.FieldId().getText(); + NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : Expression.empty(NumericExpression.class); StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) : this.script.getStringLiteralFromUnquotedString(""); if (labelType.script.equals("value")) { - this.shorthandIndirectLabelReference(fieldId); + this.shorthandIndirectLabelReference(fieldId, quantity); } else { this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), this.script.getStringLiteralFromUnquotedString("|"), labelType, this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(fieldId))))); + this.script.getStringLiteralFromUnquotedString(fieldId))), quantity)); } } @Override public void exitShorthandIndirectLabelReference(ShorthandIndirectLabelReferenceContext ctx) { - this.shorthandIndirectLabelReference(ctx.FieldId().getText()); + NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : Expression.empty(NumericExpression.class); + this.shorthandIndirectLabelReference(ctx.FieldId().getText(), quantity); } - private void shorthandIndirectLabelReference(final String fieldId) { + private void shorthandIndirectLabelReference(final String fieldId, final NumericExpression quantity) { final Context currentContext = this.efxContext.peek(); final String fieldType = this.symbols.getTypeOfField(fieldId); final XPathAttributeLocator parsedPath = @@ -326,7 +331,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { loopVariable, this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(fieldId))), - StringListExpression.class))); + StringListExpression.class), quantity)); break; case "code": case "internal-code": @@ -343,7 +348,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.symbols.getRootCodelistOfField(fieldId)), this.script.getStringLiteralFromUnquotedString("."), loopVariable)), - StringListExpression.class))); + StringListExpression.class), quantity)); break; default: throw new ParseCancellationException(String.format( @@ -363,17 +368,18 @@ private void shorthandIndirectLabelReference(final String fieldId) { @Override public void exitShorthandLabelReferenceFromContext( ShorthandLabelReferenceFromContextContext ctx) { + NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : Expression.empty(NumericExpression.class); final String labelType = ctx.LabelType().getText(); if (this.efxContext.isFieldContext()) { if (labelType.equals(SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE)) { - this.shorthandIndirectLabelReference(this.efxContext.symbol()); + this.shorthandIndirectLabelReference(this.efxContext.symbol(), quantity); } else { this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(labelType), this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), quantity)); } } else if (this.efxContext.isNodeContext()) { this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( @@ -381,7 +387,7 @@ public void exitShorthandLabelReferenceFromContext( this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(labelType), this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); + this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))), quantity)); } } @@ -397,7 +403,7 @@ public void exitShorthandIndirectLabelReferenceFromContextField( throw new ParseCancellationException( "The #value shorthand syntax can only be used if a field is declared as context."); } - this.shorthandIndirectLabelReference(this.efxContext.symbol()); + this.shorthandIndirectLabelReference(this.efxContext.symbol(), Expression.empty(NumericExpression.class)); } @Override 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 e31787e9..d7255ac8 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -8,6 +8,7 @@ import org.apache.commons.lang3.tuple.Pair; import eu.europa.ted.efx.interfaces.MarkupGenerator; import eu.europa.ted.efx.model.Expression; +import eu.europa.ted.efx.model.Expression.NumericExpression; import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.model.Expression.StringExpression; import eu.europa.ted.efx.model.Markup; @@ -21,12 +22,28 @@ public Markup renderVariableExpression(Expression valueReference) { @Override public Markup renderLabelFromKey(StringExpression key) { - return new Markup(String.format("label(%s)", key.script)); + return this.renderLabelFromKey(key, Expression.empty(NumericExpression.class)); + } + + @Override + public Markup renderLabelFromKey(StringExpression key, NumericExpression quantity) { + if (quantity.isEmpty()) { + return new Markup(String.format("label(%s)", key.script)); + } + return new Markup(String.format("label(%s, %s)", key.script, quantity.script)); } @Override public Markup renderLabelFromExpression(Expression expression) { - return new Markup(String.format("label(%s)", expression.script)); + return this.renderLabelFromExpression(expression, Expression.empty(NumericExpression.class)); + } + + @Override + public Markup renderLabelFromExpression(Expression expression, NumericExpression quantity) { + if (quantity.isEmpty()) { + return new Markup(String.format("label(%s)", expression.script)); + } + return new Markup(String.format("label(%s, %s)", expression.script, quantity.script)); } @Override From 504fb6bd7f4078fa26a27ee96d183db4a7897801 Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Wed, 10 May 2023 11:58:31 +0200 Subject: [PATCH 30/57] tests: Add unit test for standard label with pluraliser --- .../europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 b64f6202..26448dd5 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -233,6 +233,13 @@ void testStandardLabelReference() { translateTemplate("{BT-00-Text} #{field|name|BT-00-Text}")); } + @Test + void testStandardLabelReference_WithPluraliser() { + assertEquals( + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'), ../NumberField/number()) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{field|name|BT-00-Text;${BT-00-Number}}")); + } + @Test void testStandardLabelReference_UsingLabelTypeAsAssetId() { assertEquals( From 51573e2632bd6a35457e4ae942ed0b289e39e3fd Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Wed, 10 May 2023 12:03:38 +0200 Subject: [PATCH 31/57] tests: Add unit test for template line with a line break --- .../europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 b64f6202..c882faba 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -223,6 +223,13 @@ void testTemplateLine_WithSecondaryTemplate() { translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n some text")); } + @Test + void testTemplateLine_WithLineBreak() { + assertEquals( + "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())", + translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n")); + } + /*** Labels ***/ From ccebd6690bcf3680fa2462a5ebbc24b8c620fc1b Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Wed, 10 May 2023 12:04:31 +0200 Subject: [PATCH 32/57] tests: Complement unit test for context declaration shortcuts Add lines with shortcuts referring to a node, to improve test coverage. --- .../ted/efx/sdk2/EfxTemplateTranslatorV2Test.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 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 c882faba..3e47a0fe 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -205,7 +205,11 @@ void testTemplateLine_ContextDeclarationShortcuts() { " {BT-00-Text} ${BT-00-Number}", " {BT-00-Text} 5", " {BT-00-Text} ${BT-00-Number}", // - " {BT-00-Number} ${BT-00-Text}")), // + " {BT-00-Number} ${BT-00-Text}", + " {ND-SubNode} N2", + " {ND-Root} N3", + " {ND-Root} N4", + " {ND-SubNode} ${BT-00-Number}")), // translateTemplate(lines( "{BT-00-Number} 1", // " {BT-00-Text} 2", // @@ -213,7 +217,11 @@ void testTemplateLine_ContextDeclarationShortcuts() { " {..} ${BT-00-Number}", // " {.} 5", // " {.} ${BT-00-Number}", // - " {..} ${BT-00-Text}"))); + " {..} ${BT-00-Text}", + " {ND-SubNode} N2", + " {ND-Root} N3", // + " {.} N4", // + " {..} ${BT-00-Number}"))); } @Test From 0f83dbb16709c229efe6452de8d6391db569e691 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Mon, 15 May 2023 00:28:27 +0200 Subject: [PATCH 33/57] Fixed preprocessing issues. --- .../efx/sdk2/EfxExpressionTranslatorV2.java | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 842d7ecb..821ec34a 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1684,6 +1684,16 @@ static private String getVariableName(DurationParameterDeclarationContext ctx) { // #region Pre-processing --------------------------------------------------- + @Override + public void exitLateBoundSequence(LateBoundSequenceContext ctx) { + assert false: "This should have been handled by the preprocessor: " + ctx.getText() +". Check any changes that you might have made in the EFX grammar that may have broken this assumption."; + } + + @Override + public void exitLateBoundScalar(LateBoundScalarContext ctx) { + assert false: "This should have been handled by the preprocessor: " + ctx.getText() +". Check any changes that you might have made in the EFX grammar that may have broken this assumption."; + } + private static String textTypeName = getLexerSymbol(EfxLexer.Text); private static String booleanTypeName = getLexerSymbol(EfxLexer.Indicator); private static String numericTypeName = getLexerSymbol(EfxLexer.Number); @@ -1776,8 +1786,8 @@ String processExpression() { } @Override - public void exitUntypedFieldReferenceExpression(UntypedFieldReferenceExpressionContext ctx) { - if (!hasParentContextOfType(ctx, LateBoundExpressionContext.class)) { + public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { + if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { return; } @@ -1789,11 +1799,22 @@ public void exitUntypedFieldReferenceExpression(UntypedFieldReferenceExpressionC } @Override - public void exitUntypedSequenceExpression(UntypedSequenceExpressionContext ctx) { - if (!hasParentContextOfType(ctx, LateBoundExpressionContext.class)) { + public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext ctx) { + if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { return; } + // Insert the type cast. For attributes, the type is always text. + this.rewriter.insertBefore(ctx.getStart(), "(" + textTypeName + ")"); + } + + @Override + public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx) { + if (!hasParentContextOfType(ctx, LateBoundSequenceContext.class)) { + return; + } + + // Find the referenced field and get its type String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); String fieldType = eFormsToEfxTypeMap.get(this.symbols.getTypeOfField(fieldId)); @@ -1803,9 +1824,19 @@ public void exitUntypedSequenceExpression(UntypedSequenceExpressionContext ctx) } } + @Override + public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceContext ctx) { + if (!hasParentContextOfType(ctx, LateBoundSequenceContext.class)) { + return; + } + + // Insert the type cast + this.rewriter.insertBefore(ctx.getStart(), "(" + textTypeName + ")"); + } + @Override public void exitVariableReference(VariableReferenceContext ctx) { - if (!hasParentContextOfType(ctx, LateBoundExpressionContext.class)) { + if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { return; } From e0323ccc3844ef921a053242360091647237def2 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sun, 28 May 2023 02:21:13 +0200 Subject: [PATCH 34/57] Fixed a bug in StringListExpression type hierarchy --- src/main/java/eu/europa/ted/efx/model/Expression.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java index 23381939..a8bb68ba 100644 --- a/src/main/java/eu/europa/ted/efx/model/Expression.java +++ b/src/main/java/eu/europa/ted/efx/model/Expression.java @@ -244,7 +244,7 @@ public StringListExpression(final String script) { } } - public static class MultilingualStringListExpression extends ListExpression { + public static class MultilingualStringListExpression extends StringListExpression { public MultilingualStringListExpression(final String script) { super(script); From 77566a9ff056b4f47d087d1703e01b0858491370 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Tue, 6 Jun 2023 00:04:19 +0200 Subject: [PATCH 35/57] Added upper-case and lower-case conversion to EFX 2. --- .../europa/ted/efx/interfaces/ScriptGenerator.java | 4 ++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 10 ++++++++++ .../europa/ted/efx/xpath/XPathScriptGenerator.java | 10 ++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2Test.java | 14 ++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index ae1373c6..e8f7c65c 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -368,6 +368,10 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public StringExpression composeToStringConversion(NumericExpression number); + public StringExpression composeToUpperCaseConversion(StringExpression text); + + public StringExpression composeToLowerCaseConversion(StringExpression text); + /* * Boolean Functions */ diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 821ec34a..47478f11 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1386,6 +1386,16 @@ public void exitStringLengthFunction(StringLengthFunctionContext ctx) { // #region String functions ------------------------------------------------- + @Override + public void exitUpperCaseFunction(UpperCaseFunctionContext ctx) { + this.stack.push(this.script.composeToUpperCaseConversion(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitLowerCaseFunction(LowerCaseFunctionContext ctx) { + this.stack.push(this.script.composeToLowerCaseConversion(this.stack.pop(StringExpression.class))); + } + @Override public void exitSubstringFunction(SubstringFunctionContext ctx) { final NumericExpression length = diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index cf9fd48a..c4b72c0a 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -445,6 +445,16 @@ public StringExpression composeToStringConversion(NumericExpression number) { return new StringExpression("format-number(" + number.script + ", '" + formatString + "')"); } + @Override + public StringExpression composeToUpperCaseConversion(StringExpression text) { + return new StringExpression("upper-case(" + text.script + ")"); + } + + @Override + public StringExpression composeToLowerCaseConversion(StringExpression text) { + return new StringExpression("lower-case(" + text.script + ")"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 09acc399..27246686 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1205,6 +1205,20 @@ void testSubstringFunction() { "ND-Root", "substring(BT-00-Text, 4)"); } + @Test + void testUpperCaseFunction() { + testExpressionTranslation( + "upper-case(PathNode/TextField/normalize-space(text()))", + "{ND-Root} ${upper-case(BT-00-Text)}"); + } + + @Test + void testLowerCaseFunction() { + testExpressionTranslation( + "lower-case(PathNode/TextField/normalize-space(text()))", + "{ND-Root} ${lower-case(BT-00-Text)}"); + } + @Test void testToStringFunction() { testExpressionTranslationWithContext("format-number(123, '0,##########')", "ND-Root", From a3d97ec4995c6e28178962cce0b3f329e1963a17 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Thu, 8 Jun 2023 11:19:59 +0200 Subject: [PATCH 36/57] Fixed a typo in the README. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d03e4e8d..4984ff1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation --- ## In this release: -- Updated the XPath 2.0 parse, XPathContextualizer and XPathScriptGenerator to correctly translate sequences. +- Updated the XPath 2.0 parser, XPathContextualizer and XPathScriptGenerator to correctly translate sequences. - Improved numeric formatting. The EfxTranslator API now includes overloaded methods that permit control of numeric formatting. The existing API has been preserved. - Improved handling of multilingual text fields by adding automatic selection of the visualisation language . --- From 4df5afee686133f38a7f14092e61f0ab753f3061 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sun, 11 Jun 2023 20:55:03 +0200 Subject: [PATCH 37/57] Added a missing rule handler for attribute references. --- .../europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 47478f11..be221d93 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1069,6 +1069,12 @@ public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext this.getAttributeName(ctx), StringExpression.class)); } + @Override + public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceContext ctx) { + this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), + this.getAttributeName(ctx), StringListExpression.class)); + } + // #endregion Value References ---------------------------------------------- // #region References with context override --------------------------------- @@ -1626,6 +1632,10 @@ protected String getAttributeName(String efxAttributeIdentifier) { return StringUtils.substringAfter(efxAttributeIdentifier, ATTRIBUTE_PREFIX); } + private String getAttributeName(SequenceFromAttributeReferenceContext ctx) { + return this.getAttributeName(ctx.attributeReference().Attribute().getText()); + } + private String getAttributeName(ScalarFromAttributeReferenceContext ctx) { return this.getAttributeName(ctx.attributeReference().Attribute().getText()); } From 0b435101ec0b2e4ec35e91fe59be1b5e131e6a61 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sun, 11 Jun 2023 21:02:14 +0200 Subject: [PATCH 38/57] Added two i18n functions for multilingual text fields in EFX-2. Added a workaround for similar behaviour in EFX-1. --- .../ted/efx/interfaces/ScriptGenerator.java | 49 +++++++++++++++ .../sdk1/xpath/XPathScriptGeneratorV1.java | 52 ++++++++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 10 ++++ .../ted/efx/xpath/XPathContextualizer.java | 8 +++ .../ted/efx/xpath/XPathScriptGenerator.java | 60 +++++++++++++------ 5 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index e8f7c65c..92e80167 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -368,10 +368,59 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public StringExpression composeToStringConversion(NumericExpression number); + /** + * Returns the target language script that converts the given text to upper case. + * + * @since SDK 2.0.0 + * @see #composeToLowerCaseConversion(StringExpression) + * + * @param text The text to be converted to upper case. + * @return The target language script that converts the text to upper case. + */ public StringExpression composeToUpperCaseConversion(StringExpression text); + /** + * Returns the target language script that converts the given text to lower case. + * + * @since SDK 2.0.0 + * @see #composeToUpperCaseConversion(StringExpression) + * + * @param text The text to be converted to lower case. + * @return The target language script that converts the text to lower case. + */ public StringExpression composeToLowerCaseConversion(StringExpression text); + /** + * Gets the target language script that retrieves the preferred language ID + * out of the languages available in the given field. + * + * The function is intended to be used in a predicate to select the text in the preferred + * language. The function's implementation will typically have to depend on a runtime call + * to a runtime library function that retrieves the language identifiers that are preferred + * for the current visualisation. + * + * @since SDK 2.0.0 + * @see #getTextInPreferredLanguage(PathExpression) + * + * @param fieldReference The multilingual text field. + * @return The target language script that retrieves the preferred language ID. + */ + public StringExpression getPreferredLanguage(final PathExpression fieldReference); + + /** + * Given a reference to a multilingual field, this function should generate the target language script + * that returns the text value of the field in the preferred language. + * + * Calling the function in EFX 2 + * + * @since SDK 2.0.0 + * @see #getPreferredLanguage(PathExpression) + * + * @param fieldReference The multilingual text field. + * @return The target language script that retrieves the field's text in the preferred language. + */ + public StringExpression getTextInPreferredLanguage(final PathExpression fieldReference); + /* * Boolean Functions */ diff --git a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java new file mode 100644 index 00000000..108da92b --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java @@ -0,0 +1,52 @@ +package eu.europa.ted.efx.sdk1.xpath; + +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.efx.interfaces.TranslatorOptions; +import eu.europa.ted.efx.model.Expression; +import eu.europa.ted.efx.model.Expression.MultilingualStringExpression; +import eu.europa.ted.efx.model.Expression.MultilingualStringListExpression; +import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.xpath.XPathContextualizer; +import eu.europa.ted.efx.xpath.XPathScriptGenerator; + +@SdkComponent(versions = {"0.6", "0.7", "1"}, componentType = SdkComponentType.SCRIPT_GENERATOR) +public class XPathScriptGeneratorV1 extends XPathScriptGenerator { + + public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { + super(translatorOptions); + } + + /*** + * This method is overridden to workaround a limitation of EFX 1. + * + * When a multilingual text field is referenced, then a special XPath expression + * is generated to retrieve the value in the "preferred" language. + * Preferred language is the first language among the languages listed in the + * translator options for which a text value is available in the field. + * + * The logic of the workaround is as follows: + * if the fieldReference is a multilingual text field and it does not + * already come with a predicate that filters by @languageID, then we add a + * predicate which, using a for loop, will find the first language for which a + * value is available in the field. + * + * In EFX 1 therefore the selection of the appropriate (preferred) language is + * done implicitly, whereas in EFX 2 it is done explicitly by calling a special + * function designed to perform this task. + * + * Both EFX and EFX 2 implementations of the feature rely on the existence of a + * "ted:preferred-languages()" function in the XSLT. + * This function returns the list of languages used in the visualisation in the + * order of preference (visualisation language followed by notice language(s)). + */ + @Override + public T composeFieldValueReference(PathExpression fieldReference, Class type) { + if ((MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) { + PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__"); + String script = "(for $__LANG__ in ted:preferred-languages() return " + languageSpecific.script + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]"; + return Expression.instantiate(script, type); + } + return super.composeFieldValueReference(fieldReference, type); + } +} diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index be221d93..4940c161 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1448,6 +1448,16 @@ public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { this.stack.push(this.script.composeNumberFormatting(number, format)); } + @Override + public void exitPreferredLanguageFunction(PreferredLanguageFunctionContext ctx) { + this.stack.push(this.script.getPreferredLanguage(this.stack.pop(PathExpression.class))); + } + + @Override + public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionContext ctx) { + this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(PathExpression.class))); + } + // #endregion String functions ---------------------------------------------- // #region Date functions --------------------------------------------------- diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java index 226733b8..3c7bb102 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -82,6 +82,14 @@ public static PathExpression contextualize(final PathExpression contextXpath, return getContextualizedXpath(contextSteps, pathSteps); } + public static boolean hasPredicate(final PathExpression xpath, String match) { + return hasPredicate(xpath.script, match); + } + + public static boolean hasPredicate(final String xpath, String match) { + return getSteps(xpath).stream().anyMatch(s -> s.getPredicateText().contains(match)); + } + public static PathExpression addPredicate(final PathExpression pathExpression, final String predicate) { return new PathExpression(addPredicate(pathExpression.script, predicate)); } diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index c4b72c0a..0ef92ea0 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -1,6 +1,7 @@ package eu.europa.ted.efx.xpath; import static java.util.Map.entry; + import java.lang.reflect.Constructor; import java.util.List; import java.util.Map; @@ -8,7 +9,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; + import org.antlr.v4.runtime.misc.ParseCancellationException; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.ScriptGenerator; @@ -22,8 +25,6 @@ import eu.europa.ted.efx.model.Expression.IteratorExpression; import eu.europa.ted.efx.model.Expression.IteratorListExpression; import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.MultilingualStringExpression; -import eu.europa.ted.efx.model.Expression.MultilingualStringListExpression; import eu.europa.ted.efx.model.Expression.NumericExpression; import eu.europa.ted.efx.model.Expression.NumericListExpression; import eu.europa.ted.efx.model.Expression.PathExpression; @@ -32,7 +33,7 @@ import eu.europa.ted.efx.model.Expression.TimeExpression; import eu.europa.ted.efx.model.Expression.TimeListExpression; -@SdkComponent(versions = {"0.6", "0.7", "1", "2"}, +@SdkComponent(versions = {"2"}, componentType = SdkComponentType.SCRIPT_GENERATOR) public class XPathScriptGenerator implements ScriptGenerator { @@ -78,13 +79,6 @@ public T composeFieldReferenceWithAxis(final PathExpressi @Override public T composeFieldValueReference(PathExpression fieldReference, Class type) { - - if (MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) { - String languages = "('" + String.join("','", this.translatorOptions.getAllLanguage3LetterCodes()) + "')"; - PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__"); - String script = "(for $__LANG__ in " + languages + " return " + languageSpecific.script + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]"; - return Expression.instantiate(script, type); - } if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) { return Expression.instantiate(fieldReference.script + "/normalize-space(text())", type); } @@ -301,7 +295,7 @@ public PathExpression joinPaths(final PathExpression first, final PathExpression return XPathContextualizer.join(first, second); } - /** Indexers ***/ + //#region Indexers ---------------------------------------------------------- @Override public > T composeIndexer(L list, @@ -310,7 +304,9 @@ public > T composeIndexer(L li return Expression.instantiate(String.format("%s[%s]", list.script, index.script), type); } - /*** BooleanExpressions ***/ + //#endregion Indexers ------------------------------------------------------- + + //#region Boolean Expressions ----------------------------------------------- @Override @@ -344,7 +340,9 @@ public BooleanExpression composeUniqueValueCondition(PathExpression needle, + "[. = $x] return $y) = 1"); } - /*** Boolean functions ***/ + //#endregion Boolean Expressions ------------------------------------------ + + //#region Boolean functions ----------------------------------------------- @Override public BooleanExpression composeContainsCondition(StringExpression haystack, @@ -384,7 +382,9 @@ public BooleanExpression composeSequenceEqualFunction(ListExpression> L composeExceptFuncti return Expression.instantiate("distinct-values(for $L1 in " + listOne.script + " return if (every $L2 in " + listTwo.script + " satisfies $L1 != $L2) then $L1 else ())", listType); } + //#endregion Duration functions --------------------------------------------- - /*** Helpers ***/ + //#region Helpers ----------------------------------------------------------- private String quoted(final String text) { @@ -575,4 +595,6 @@ private int getWeeksFromDurationLiteral(final String literal) { Matcher weeksMatcher = Pattern.compile("(?<=[^0-9])[0-9]+(?=W)").matcher(literal); return weeksMatcher.find() ? Integer.parseInt(weeksMatcher.group()) : 0; } + + //#endregion Helpers -------------------------------------------------------- } From 1ad364c1fdedca18bdbfaeaf8235e3d49c707472 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sun, 11 Jun 2023 21:03:08 +0200 Subject: [PATCH 39/57] Fixed existing and added new test for multilingual text features. --- .../ted/efx/mock/DependencyFactoryMock.java | 27 +++++++---- .../sdk1/EfxExpressionTranslatorV1Test.java | 14 +++++- .../sdk2/EfxExpressionTranslatorV2Test.java | 46 ++++++++++++++++++- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java index cbdf59fe..656a16cd 100644 --- a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java @@ -1,14 +1,17 @@ package eu.europa.ted.efx.mock; +import java.util.HashMap; +import java.util.Map; + import org.antlr.v4.runtime.BaseErrorListener; +import eu.europa.ted.eforms.sdk.ComponentFactory; import eu.europa.ted.efx.exceptions.ThrowingErrorListener; 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.TranslatorDependencyFactory; import eu.europa.ted.efx.interfaces.TranslatorOptions; -import eu.europa.ted.efx.xpath.XPathScriptGenerator; /** * Provides EfxTranslator dependencies used for unit testing. @@ -19,9 +22,9 @@ private DependencyFactoryMock() {} final public static DependencyFactoryMock INSTANCE = new DependencyFactoryMock(); - ScriptGenerator scriptGenerator; - MarkupGenerator markupGenerator; - + Map scriptGenerators = new HashMap<>(); + Map markupGenerators = new HashMap<>(); + @Override public SymbolResolver createSymbolResolver(String sdkVersion) { return SymbolResolverMockFactory.getInstance(sdkVersion); @@ -29,18 +32,22 @@ public SymbolResolver createSymbolResolver(String sdkVersion) { @Override public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options) { - if (scriptGenerator == null) { - this.scriptGenerator = new XPathScriptGenerator(options); + if (!scriptGenerators.containsKey(sdkVersion)) { + try { + this.scriptGenerators.put(sdkVersion, ComponentFactory.getScriptGenerator(sdkVersion, options)); + } catch (InstantiationException e) { + throw new RuntimeException(e.getMessage(), e); + } } - return this.scriptGenerator; + return this.scriptGenerators.get(sdkVersion); } @Override public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options) { - if (this.markupGenerator == null) { - this.markupGenerator = new MarkupGeneratorMock(); + if (!this.markupGenerators.containsKey(sdkVersion)) { + this.markupGenerators.put(sdkVersion, new MarkupGeneratorMock()); } - return this.markupGenerator; + return this.markupGenerators.get(sdkVersion); } @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java index 67006b40..df26ecc4 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -90,7 +90,7 @@ void testLikePatternCondition_WithNot() { @Test void testFieldValueComparison_UsingTextFields() { testExpressionTranslationWithContext( - "PathNode/TextField/normalize-space(text()) = (for $__LANG__ in ('eng') return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", + "PathNode/TextField/normalize-space(text()) = (for $__LANG__ in ted:preferred-languages() return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", "ND-Root", "BT-00-Text == BT-00-Text-Multilingual"); } @@ -1117,6 +1117,18 @@ void testFieldReference_WithAxis() { "ND-Root::preceding::BT-00-Integer"); } + @Test + void testMultilingualTextFieldReference() { + testExpressionTranslationWithContext("(for $__LANG__ in ted:preferred-languages() return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", + "ND-Root", "BT-00-Text-Multilingual"); + } + + @Test + void testMultilingualTextFieldReference_WithLanguagePredicate() { + testExpressionTranslationWithContext("PathNode/TextMultilingualField[./@languageID = 'eng']/normalize-space(text())", + "ND-Root", "BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == 'eng']"); + } + // #endregion: References // #region: Boolean functions ----------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 27246686..2a78e79d 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -87,10 +87,34 @@ void testLikePatternCondition_WithNot() { "BT-00-Text", "'123' not like '[0-9]*'"); } + @Test + void testLikePatternCondition_WithTextField() { + testExpressionTranslation("fn:matches(normalize-space(PathNode/TextField/normalize-space(text())), '[0-9]*')", + "{ND-Root} ${BT-00-Text like '[0-9]*'}"); + } + + @Test + void testLikePatternCondition_WithTextMultilingualField() { + testExpressionTranslation("every $lang in PathNode/TextMultilingualField/@languageID satisfies fn:matches(normalize-space(PathNode/TextMultilingualField[./@languageID = $lang]/normalize-space(text())), '[0-9]*')", + "{ND-Root} ${every text:$lang in BT-00-Text-Multilingual/@languageID satisfies BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == $lang] like '[0-9]*'}"); + } + + @Test + void testVisualisationLanguageFunction() { + testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = ((for $__LANG__ in ('eng') return if (.[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), ./normalize-space(text()))[1]]/normalize-space(text())", + "{ND-Root} ${BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == preferred-language(BT-00-Text-Multilingual)]}"); + } + + @Test + void testGetPreferredLanguageTextFunction() { + testExpressionTranslation("PathNode/TextMultilingualField[@languageID=((for $__LANG__ in ('eng') return if (PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), PathNode/TextMultilingualField/normalize-space(text()))[1]]", + "{ND-Root} ${preferred-language-text(BT-00-Text-Multilingual)}"); + } + @Test void testFieldValueComparison_UsingTextFields() { testExpressionTranslationWithContext( - "PathNode/TextField/normalize-space(text()) = (for $__LANG__ in ('eng') return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", + "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())", "Root", "textField == textMultilingualField"); } @@ -1117,6 +1141,26 @@ void testFieldReference_WithAxis() { "ND-Root::preceding::integerField"); } + /** + * Unlike EFX-1, where any reference toa text-multilingual field, is automatically translated to + * an expression that returns the value of the field in the preferred language, in EFX-2 there are + * no such implicit assumptions made. In EFX-2 a reference to a text-multilingual field behaves just + * like any other field reference. To get the value of the field in a specific language you either need + * to add a predicate that selects it or use the in-preferred-language function. + */ + @Test + void testMultilingualTextFieldReference() { + testExpressionTranslationWithContext("PathNode/TextMultilingualField/normalize-space(text())", + "ND-Root", "BT-00-Text-Multilingual"); + } + + @Test + void testMultilingualTextFieldReference_WithLanguagePredicate() { + testExpressionTranslationWithContext("PathNode/TextMultilingualField[./@languageID = 'eng']/normalize-space(text())", + "ND-Root", "BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == 'eng']"); + } + + // #endregion: References // #region: Boolean functions ----------------------------------------------- From df628132347c1750b123bd9d7a48aa82db66af30 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Mon, 12 Jun 2023 21:14:34 +0200 Subject: [PATCH 40/57] Fixed issues spotted during review. --- .../eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 3 +-- .../ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 0ef92ea0..6fbade98 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -483,9 +483,8 @@ public StringExpression getStringLiteralFromUnquotedString(String value) { @Override public StringExpression getPreferredLanguage(PathExpression fieldReference) { final String variableName = "$__LANG__"; - String languages = "('" + String.join("','", this.translatorOptions.getAllLanguage3LetterCodes()) + "')"; PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=" + variableName); - String script = "((for " + variableName + " in " + languages + " return if (" + languageSpecific.script + "/normalize-space(text())) then " + variableName + " else ()), " + fieldReference.script + "/normalize-space(text()))[1]"; + String script = "((for " + variableName + " in ted:preferred-languages() return if (" + languageSpecific.script + "/normalize-space(text())) then " + variableName + " else ()), " + fieldReference.script + "/normalize-space(text()))[1]"; return new StringExpression(script); } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 2a78e79d..c6f7e42c 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -101,13 +101,13 @@ void testLikePatternCondition_WithTextMultilingualField() { @Test void testVisualisationLanguageFunction() { - testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = ((for $__LANG__ in ('eng') return if (.[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), ./normalize-space(text()))[1]]/normalize-space(text())", + testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = ((for $__LANG__ in ted:preferred-languages() return if (.[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), ./normalize-space(text()))[1]]/normalize-space(text())", "{ND-Root} ${BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == preferred-language(BT-00-Text-Multilingual)]}"); } @Test void testGetPreferredLanguageTextFunction() { - testExpressionTranslation("PathNode/TextMultilingualField[@languageID=((for $__LANG__ in ('eng') return if (PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), PathNode/TextMultilingualField/normalize-space(text()))[1]]", + testExpressionTranslation("PathNode/TextMultilingualField[@languageID=((for $__LANG__ in ted:preferred-languages() return if (PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), PathNode/TextMultilingualField/normalize-space(text()))[1]]", "{ND-Root} ${preferred-language-text(BT-00-Text-Multilingual)}"); } @@ -1142,11 +1142,11 @@ void testFieldReference_WithAxis() { } /** - * Unlike EFX-1, where any reference toa text-multilingual field, is automatically translated to + * Unlike EFX-1, where any reference to a text-multilingual field, is automatically translated to * an expression that returns the value of the field in the preferred language, in EFX-2 there are * no such implicit assumptions made. In EFX-2 a reference to a text-multilingual field behaves just * like any other field reference. To get the value of the field in a specific language you either need - * to add a predicate that selects it or use the in-preferred-language function. + * to add a predicate that selects it or use the preferred-language-text function. */ @Test void testMultilingualTextFieldReference() { From 197e35998fb5c91ae006b9a435c8f3e5737186a0 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Mon, 12 Jun 2023 21:35:21 +0200 Subject: [PATCH 41/57] Simplified codelist identifier prefix (#) --- .../eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index c6f7e42c..45af9da3 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1043,7 +1043,7 @@ void testDurationList_UsingDurationField() { @Test void testCodeList() { testExpressionTranslationWithContext("'a' = ('code1','code2','code3')", "BT-00-Text", - "'a' in codelist:accessibility"); + "'a' in #accessibility"); } // #endregion: Lists From edcfaecfca2e0f2a4001823c4a2f2dbd6e3fc910 Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Tue, 13 Jun 2023 11:46:31 +0200 Subject: [PATCH 42/57] test: Fix unit test failures depending on execution order Fix caching of ScriptGenerator in DependencyFactoryMock to take into account TranslatorOptions. The EFX Template tests use the XSL_DEFAULT DecimalFormat, and the EFX expression tests use the EFX_DEFAULT DecimalFormat. Before this fix, the first one executed would add a ScriptGenerator instance in the cache, with its TranslatorOptions. And the next one would use the same instance, with the wrong TranslatorOptions. So you would get test failures with number-format if EfxTemplateTranslatorV1Test was run before EfxExpressionTranslatorV1Test. --- .../eu/europa/ted/efx/mock/DependencyFactoryMock.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java index 656a16cd..e05c038f 100644 --- a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java @@ -32,14 +32,17 @@ public SymbolResolver createSymbolResolver(String sdkVersion) { @Override public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options) { - if (!scriptGenerators.containsKey(sdkVersion)) { + // Default hashCode() implementation is OK here + // we just need to distinguish TranslatorOptions instances + String key = sdkVersion + options.hashCode(); + if (!scriptGenerators.containsKey(key)) { try { - this.scriptGenerators.put(sdkVersion, ComponentFactory.getScriptGenerator(sdkVersion, options)); + this.scriptGenerators.put(key, ComponentFactory.getScriptGenerator(sdkVersion, options)); } catch (InstantiationException e) { throw new RuntimeException(e.getMessage(), e); } } - return this.scriptGenerators.get(sdkVersion); + return this.scriptGenerators.get(key); } @Override From 6d35feee14f68474f501818909c0790381884115 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Tue, 13 Jun 2023 19:48:08 +0200 Subject: [PATCH 43/57] Changed the implementation of multilingual text filtering to improve XSLT performance. --- .../europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java | 6 +++--- .../java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 5 +++-- .../europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java | 4 ++-- .../europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java index 108da92b..2cf2c7c9 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java @@ -35,8 +35,8 @@ public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { * done implicitly, whereas in EFX 2 it is done explicitly by calling a special * function designed to perform this task. * - * Both EFX and EFX 2 implementations of the feature rely on the existence of a - * "ted:preferred-languages()" function in the XSLT. + * Both EFX-1 and EFX-2 implementations of the feature rely on the existence of a + * $PREFERRED_LANGUAGES variable in the XSLT. * This function returns the list of languages used in the visualisation in the * order of preference (visualisation language followed by notice language(s)). */ @@ -44,7 +44,7 @@ public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { public T composeFieldValueReference(PathExpression fieldReference, Class type) { if ((MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) { PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__"); - String script = "(for $__LANG__ in ted:preferred-languages() return " + languageSpecific.script + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]"; + String script = "(for $__LANG__ in $PREFERRED_LANGUAGES return " + languageSpecific.script + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]"; return Expression.instantiate(script, type); } return super.composeFieldValueReference(fieldReference, type); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 6fbade98..b0d25712 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -483,8 +483,9 @@ public StringExpression getStringLiteralFromUnquotedString(String value) { @Override public StringExpression getPreferredLanguage(PathExpression fieldReference) { final String variableName = "$__LANG__"; - PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=" + variableName); - String script = "((for " + variableName + " in ted:preferred-languages() return if (" + languageSpecific.script + "/normalize-space(text())) then " + variableName + " else ()), " + fieldReference.script + "/normalize-space(text()))[1]"; + PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=" + variableName); + // TODO: rethink about the implicit dependency to the existence of the $PREFERRED_LANGUAGES runtime variable in the XSLT. + String script = "((for " + variableName + " in $PREFERRED_LANGUAGES return if (" + languageSpecific.script + "/normalize-space(text())) then " + variableName + " else ()), " + fieldReference.script + "/normalize-space(text()))[1]"; return new StringExpression(script); } diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java index df26ecc4..960174fd 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -90,7 +90,7 @@ void testLikePatternCondition_WithNot() { @Test void testFieldValueComparison_UsingTextFields() { testExpressionTranslationWithContext( - "PathNode/TextField/normalize-space(text()) = (for $__LANG__ in ted:preferred-languages() return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", + "PathNode/TextField/normalize-space(text()) = (for $__LANG__ in $PREFERRED_LANGUAGES return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", "ND-Root", "BT-00-Text == BT-00-Text-Multilingual"); } @@ -1119,7 +1119,7 @@ void testFieldReference_WithAxis() { @Test void testMultilingualTextFieldReference() { - testExpressionTranslationWithContext("(for $__LANG__ in ted:preferred-languages() return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", + testExpressionTranslationWithContext("(for $__LANG__ in $PREFERRED_LANGUAGES return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", "ND-Root", "BT-00-Text-Multilingual"); } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 45af9da3..37439cf9 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -101,13 +101,13 @@ void testLikePatternCondition_WithTextMultilingualField() { @Test void testVisualisationLanguageFunction() { - testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = ((for $__LANG__ in ted:preferred-languages() return if (.[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), ./normalize-space(text()))[1]]/normalize-space(text())", + testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = ((for $__LANG__ in $PREFERRED_LANGUAGES return if (.[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), ./normalize-space(text()))[1]]/normalize-space(text())", "{ND-Root} ${BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == preferred-language(BT-00-Text-Multilingual)]}"); } @Test void testGetPreferredLanguageTextFunction() { - testExpressionTranslation("PathNode/TextMultilingualField[@languageID=((for $__LANG__ in ted:preferred-languages() return if (PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), PathNode/TextMultilingualField/normalize-space(text()))[1]]", + testExpressionTranslation("PathNode/TextMultilingualField[@languageID=((for $__LANG__ in $PREFERRED_LANGUAGES return if (PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), PathNode/TextMultilingualField/normalize-space(text()))[1]]", "{ND-Root} ${preferred-language-text(BT-00-Text-Multilingual)}"); } From 96625984bbc2f6f05050b1a61b8ca0c6b35a290f Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Sat, 17 Jun 2023 01:04:58 +0200 Subject: [PATCH 44/57] Simplified test-multilingual rendering --- .../ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java | 6 ++---- .../europa/ted/efx/xpath/XPathScriptGenerator.java | 8 ++------ .../efx/sdk1/EfxExpressionTranslatorV1Test.java | 4 ++-- .../efx/sdk2/EfxExpressionTranslatorV2Test.java | 14 ++++++++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java index 2cf2c7c9..25302965 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java @@ -43,10 +43,8 @@ public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { @Override public T composeFieldValueReference(PathExpression fieldReference, Class type) { if ((MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) { - PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__"); - String script = "(for $__LANG__ in $PREFERRED_LANGUAGES return " + languageSpecific.script + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]"; - return Expression.instantiate(script, type); - } + return Expression.instantiate("efx:preferred-language-text(" + fieldReference.script + ")", type); + } return super.composeFieldValueReference(fieldReference, type); } } diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index b0d25712..b98c85cd 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -482,16 +482,12 @@ public StringExpression getStringLiteralFromUnquotedString(String value) { @Override public StringExpression getPreferredLanguage(PathExpression fieldReference) { - final String variableName = "$__LANG__"; - PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=" + variableName); - // TODO: rethink about the implicit dependency to the existence of the $PREFERRED_LANGUAGES runtime variable in the XSLT. - String script = "((for " + variableName + " in $PREFERRED_LANGUAGES return if (" + languageSpecific.script + "/normalize-space(text())) then " + variableName + " else ()), " + fieldReference.script + "/normalize-space(text()))[1]"; - return new StringExpression(script); + return new StringExpression("efx:preferred-language(" + fieldReference.script + ")"); } @Override public StringExpression getTextInPreferredLanguage(PathExpression fieldReference) { - return new StringExpression(XPathContextualizer.addPredicate(fieldReference, "[@languageID=" + this.getPreferredLanguage(fieldReference).script + "]").script ); + return new StringExpression("efx:preferred-language-text(" + fieldReference.script + ")"); } //#endregion String functions ----------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java index 960174fd..16862e4d 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -90,7 +90,7 @@ void testLikePatternCondition_WithNot() { @Test void testFieldValueComparison_UsingTextFields() { testExpressionTranslationWithContext( - "PathNode/TextField/normalize-space(text()) = (for $__LANG__ in $PREFERRED_LANGUAGES return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", + "PathNode/TextField/normalize-space(text()) = efx:preferred-language-text(PathNode/TextMultilingualField)", "ND-Root", "BT-00-Text == BT-00-Text-Multilingual"); } @@ -1119,7 +1119,7 @@ void testFieldReference_WithAxis() { @Test void testMultilingualTextFieldReference() { - testExpressionTranslationWithContext("(for $__LANG__ in $PREFERRED_LANGUAGES return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]", + testExpressionTranslationWithContext("efx:preferred-language-text(PathNode/TextMultilingualField)", "ND-Root", "BT-00-Text-Multilingual"); } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 37439cf9..83136d50 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -100,14 +100,20 @@ void testLikePatternCondition_WithTextMultilingualField() { } @Test - void testVisualisationLanguageFunction() { - testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = ((for $__LANG__ in $PREFERRED_LANGUAGES return if (.[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), ./normalize-space(text()))[1]]/normalize-space(text())", + void testPreferredLanguageFunction() { + testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = efx:preferred-language(.)]/normalize-space(text())", "{ND-Root} ${BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == preferred-language(BT-00-Text-Multilingual)]}"); } + @Test + void testPreferredLanguageFunction_InPredicate() { + testExpressionTranslation("efx:preferred-language(PathNode/TextMultilingualField)", + "{ND-Root} ${preferred-language(BT-00-Text-Multilingual)}"); + } + @Test - void testGetPreferredLanguageTextFunction() { - testExpressionTranslation("PathNode/TextMultilingualField[@languageID=((for $__LANG__ in $PREFERRED_LANGUAGES return if (PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text())) then $__LANG__ else ()), PathNode/TextMultilingualField/normalize-space(text()))[1]]", + void testPreferredLanguageTextFunction() { + testExpressionTranslation("efx:preferred-language-text(PathNode/TextMultilingualField)", "{ND-Root} ${preferred-language-text(BT-00-Text-Multilingual)}"); } From ac42972402679e7cca59588c5dd041c9b020f3b5 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Wed, 21 Jun 2023 22:59:55 +0200 Subject: [PATCH 45/57] TEDEFO-2362: late-bound expressions can now be passed ass parameters to the notice() function. --- .../ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 7 +++++++ src/test/resources/json/fields-sdk2.json | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 83136d50..f8b20805 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1099,6 +1099,13 @@ void testFieldReferenceInOtherNotice() { "ND-Root", "notice('da4d46e9-490b-41ff-a2ae-8166d356a619')/BT-00-Text"); } + @Test + void testFieldReferenceInOtherNotice_UsingAReference() { + testExpressionTranslationWithContext( + "fn:doc(concat($urlPrefix, /*/PathNode/IdField/normalize-space(text())))/*/PathNode/TextField/normalize-space(text())", + "ND-Root", "notice(BT-00-Identifier)/BT-00-Text"); + } + @Test void testFieldReferenceWithFieldContextOverride() { testExpressionTranslationWithContext("../TextField/normalize-space(text())", "BT-00-Code", diff --git a/src/test/resources/json/fields-sdk2.json b/src/test/resources/json/fields-sdk2.json index d0ab5a2c..9d0e6183 100644 --- a/src/test/resources/json/fields-sdk2.json +++ b/src/test/resources/json/fields-sdk2.json @@ -156,6 +156,14 @@ "xpathAbsolute": "/*/PathNode/ZonedTimeField", "xpathRelative": "PathNode/ZonedTimeField" }, + { + "id": "BT-00-Identifier", + "alias": "identifierField", + "type": "id", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IdField", + "xpathRelative": "PathNode/IdField" + }, { "id": "BT-00-Id-Ref", "alias": "idRefField", From 27ac90d8337d1eafb8c850746d4ebeeeab9ddd3e Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Thu, 22 Jun 2023 04:08:29 +0200 Subject: [PATCH 46/57] Removed support of deprecated SDKs 0.6 and 0.7. --- CHANGELOG.md | 2 - pom.xml | 36 - .../ted/eforms/sdk/SdkSymbolResolver.java | 2 +- .../sdk0/v6/EfxExpressionTranslator06.java | 838 ---------- .../efx/sdk0/v6/EfxTemplateTranslator06.java | 532 ------- .../ted/efx/sdk0/v6/entity/SdkCodelist06.java | 21 - .../ted/efx/sdk0/v6/entity/SdkField06.java | 19 - .../ted/efx/sdk0/v6/entity/SdkNode06.java | 22 - .../sdk0/v7/EfxExpressionTranslator07.java | 1345 ----------------- .../efx/sdk0/v7/EfxTemplateTranslator07.java | 532 ------- .../ted/efx/sdk0/v7/entity/SdkCodelist07.java | 21 - .../ted/efx/sdk0/v7/entity/SdkField07.java | 19 - .../ted/efx/sdk0/v7/entity/SdkNode07.java | 22 - .../sdk1/xpath/XPathScriptGeneratorV1.java | 2 +- 14 files changed, 2 insertions(+), 3411 deletions(-) delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkField06.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkField07.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 16045893..378cb637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,6 @@ Documentation for the EFX Toolkit is available at: https://docs.ted.europa.eu/ef --- This version of the EFX Toolkit has a compile-time dependency on the following versions of eForms SDK versions and uses the EFX grammar that each version provides: -- eForms SDK 0.6.x -- eForms SDK 0.7.x - eForms SDK 1.x.x It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.0.5. diff --git a/pom.xml b/pom.xml index 2b235132..42fe1a50 100644 --- a/pom.xml +++ b/pom.xml @@ -265,18 +265,6 @@ - - eu.europa.ted.eforms - eforms-sdk - 2.0.0-SNAPSHOT - jar - eforms-sdk/efx-grammar/**/*.g4 - ${sdk.antlr4.dir}/eu/europa/ted/efx/sdk2 - - - - - eu.europa.ted.eforms eforms-sdk @@ -289,30 +277,6 @@ - - eu.europa.ted.eforms - eforms-sdk - 0.7.0 - jar - eforms-sdk/efx-grammar/**/*.g4 - ${sdk.antlr4.dir}/eu/europa/ted/efx/sdk0/v7 - - - - - - - eu.europa.ted.eforms - eforms-sdk - 0.6.2 - jar - eforms-sdk/efx-grammar/**/*.g4 - ${sdk.antlr4.dir}/eu/europa/ted/efx/sdk0/v6 - - - - - diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java index 809761c5..304b1c5b 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -17,7 +17,7 @@ import eu.europa.ted.efx.model.Expression.PathExpression; import eu.europa.ted.efx.xpath.XPathContextualizer; -@SdkComponent(versions = {"0.6", "0.7", "1", "2"}, +@SdkComponent(versions = {"1", "2"}, componentType = SdkComponentType.SYMBOL_RESOLVER) public class SdkSymbolResolver implements SymbolResolver { protected Map fieldById; diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java deleted file mode 100644 index 8c82a2fb..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxExpressionTranslator06.java +++ /dev/null @@ -1,838 +0,0 @@ -package eu.europa.ted.efx.sdk0.v6; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -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.misc.ParseCancellationException; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.ParseTreeWalker; -import org.antlr.v4.runtime.tree.TerminalNode; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; -import eu.europa.ted.efx.interfaces.ScriptGenerator; -import eu.europa.ted.efx.interfaces.SymbolResolver; -import eu.europa.ted.efx.model.CallStack; -import eu.europa.ted.efx.model.Context.FieldContext; -import eu.europa.ted.efx.model.Context.NodeContext; -import eu.europa.ted.efx.model.ContextStack; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.BooleanExpression; -import eu.europa.ted.efx.model.Expression.DateExpression; -import eu.europa.ted.efx.model.Expression.DurationExpression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Expression.StringListExpression; -import eu.europa.ted.efx.model.Expression.TimeExpression; -import eu.europa.ted.efx.sdk0.v6.EfxParser.AbsoluteFieldReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.AbsoluteNodeReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.BooleanComparisonContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.CodeListContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.CodelistReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ConcatFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ContainsFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.CountFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DateComparisonContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DateFromStringFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DateLiteralContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DateMinusMeasureFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DatePlusMeasureFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DateSubtractionExpressionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DurationAdditionExpressionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DurationComparisonContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DurationLeftMultiplicationExpressionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DurationLiteralContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DurationRightMultiplicationExpressionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.DurationSubtractionExpressionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.EndsWithFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ExplicitListContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.FalseBooleanLiteralContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.FieldContextContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.FieldReferenceWithFieldContextOverrideContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.FieldReferenceWithNodeContextOverrideContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.FieldValueComparisonContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.FormatNumberFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.NodeContextContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.NodeReferenceWithPredicateContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.NotFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.NumberFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.NumericComparisonContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.NumericLiteralContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ParenthesizedNumericExpressionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.SimpleFieldReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.SimpleNodeReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.SingleExpressionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.StartsWithFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.StringComparisonContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.StringLengthFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.StringLiteralContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.SubstringFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.SumFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.TimeComparisonContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.TimeFromStringFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.TimeLiteralContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ToStringFunctionContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.TrueBooleanLiteralContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.UntypedAttributeValueReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.UntypedFieldValueReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxLexer; -import eu.europa.ted.efx.xpath.XPathAttributeLocator; - -/** - * The the goal of the EfxExpressionTranslator is to take an EFX expression and translate it to a - * target scripting language. - * - * The target language syntax is not hardcoded into the translator so that this class can be reused - * to translate to several different languages. Instead a {@link ScriptGenerator} interface is used - * to provide specifics on the syntax of the target scripting language. - * - * Apart from writing expressions that can be translated and evaluated in a target scripting - * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that - * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The - * {@link EfxExpressionTranslator06} only focuses on EFX expressions. To translate EFX templates you - * need to use the {@link EfxTemplateTranslator06} which derives from this class. - */ -@SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.EFX_EXPRESSION_TRANSLATOR) -public class EfxExpressionTranslator06 extends EfxBaseListener - implements EfxExpressionTranslator { - - private static final String NOT_MODIFIER = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); - - private static final String TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES = - "Type mismatch. Cannot compare values of different types: "; - - /** - * The stack is used by the methods of this listener to pass data to each other as the parse tree - * is being walked. - */ - protected CallStack stack = new CallStack(); - - /** - * The context stack is used to keep track of context switching in nested expressions. - */ - protected ContextStack efxContext; - - /** - * Symbols are the field identifiers and node identifiers. The symbols map is used to resolve them - * to their location in the data source (typically their XPath). - */ - protected SymbolResolver symbols; - - /** - * The ScriptGenerator is called to determine the target language syntax whenever needed. - */ - protected ScriptGenerator script; - - protected BaseErrorListener errorListener; - - protected EfxExpressionTranslator06() { - throw new UnsupportedOperationException(); - } - - public EfxExpressionTranslator06(final SymbolResolver symbolResolver, - final ScriptGenerator scriptGenerator, final BaseErrorListener errorListener) { - this.symbols = symbolResolver; - this.script = scriptGenerator; - this.errorListener = errorListener; - - this.efxContext = new ContextStack(symbols); - } - - @Override - public String translateExpression(final String expression, final String... expressionParameters) { - final EfxLexer lexer = - new EfxLexer(CharStreams.fromString(expression)); - final CommonTokenStream tokens = new CommonTokenStream(lexer); - final EfxParser parser = new EfxParser(tokens); - - if (errorListener != null) { - lexer.removeErrorListeners(); - lexer.addErrorListener(errorListener); - parser.removeErrorListeners(); - parser.addErrorListener(errorListener); - } - - final ParseTree tree = parser.singleExpression(); - final ParseTreeWalker walker = new ParseTreeWalker(); - - walker.walk(this, tree); - - return getTranslatedScript(); - } - - /** - * Used to get the translated target language script, after the walker finished its walk. - * - * @return The translated code, trimmed - */ - private String getTranslatedScript() { - final StringBuilder sb = new StringBuilder(this.stack.size() * 100); - while (!this.stack.empty()) { - sb.insert(0, '\n').insert(0, this.stack.pop(Expression.class).script); - } - return sb.toString().trim(); - } - - /** - * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a - * {@link SimpleFieldReferenceContext} to locate a field identifier. - * - * @param ctx the context to start from. - * @return the field identifier or null if not found. - */ - protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { - - if (ctx instanceof SimpleFieldReferenceContext) { - return ((SimpleFieldReferenceContext) ctx).FieldId().getText(); - } - - if (ctx instanceof AbsoluteFieldReferenceContext) { - return ((AbsoluteFieldReferenceContext) ctx).reference.simpleFieldReference().FieldId() - .getText(); - } - - if (ctx instanceof FieldReferenceWithFieldContextOverrideContext) { - return ((FieldReferenceWithFieldContextOverrideContext) ctx).reference.simpleFieldReference() - .FieldId().getText(); - } - - if (ctx instanceof FieldReferenceWithNodeContextOverrideContext) { - return ((FieldReferenceWithNodeContextOverrideContext) ctx).reference - .fieldReferenceWithPredicate().simpleFieldReference().FieldId().getText(); - } - - SimpleFieldReferenceContext fieldReferenceContext = - ctx.getChild(SimpleFieldReferenceContext.class, 0); - if (fieldReferenceContext != null) { - return fieldReferenceContext.FieldId().getText(); - } - - for (ParseTree child : ctx.children) { - if (child instanceof ParserRuleContext) { - String fieldId = getFieldIdFromChildSimpleFieldReferenceContext((ParserRuleContext) child); - if (fieldId != null) { - return fieldId; - } - } - } - - return null; - } - - /** - * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a - * {@link SimpleNodeReferenceContext} to locate a node identifier. - * - * @param ctx the context to start from. - * @return the node identifier or null if not found. - */ - protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { - - if (ctx instanceof SimpleNodeReferenceContext) { - return ((SimpleNodeReferenceContext) ctx).NodeId().getText(); - } - - for (ParseTree child : ctx.children) { - if (child instanceof ParserRuleContext) { - String nodeId = getNodeIdFromChildSimpleNodeReferenceContext((ParserRuleContext) child); - if (nodeId != null) { - return nodeId; - } - } - } - - return null; - } - - @Override - public void enterSingleExpression(SingleExpressionContext ctx) { - final TerminalNode fieldContext = ctx.FieldId(); - if (fieldContext != null) { - this.efxContext.pushFieldContext(fieldContext.getText()); - } else { - final TerminalNode nodeContext = ctx.NodeId(); - if (nodeContext != null) { - this.efxContext.pushNodeContext(nodeContext.getText()); - } - } - } - - @Override - public void exitSingleExpression(SingleExpressionContext ctx) { - this.efxContext.pop(); - } - - /*** Boolean expressions ***/ - - @Override - public void exitParenthesizedBooleanExpression( - EfxParser.ParenthesizedBooleanExpressionContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(BooleanExpression.class), BooleanExpression.class)); - } - - @Override - public void exitLogicalAndCondition(EfxParser.LogicalAndConditionContext ctx) { - BooleanExpression right = this.stack.pop(BooleanExpression.class); - BooleanExpression left = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeLogicalAnd(left, right)); - } - - @Override - public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { - BooleanExpression right = this.stack.pop(BooleanExpression.class); - BooleanExpression left = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeLogicalOr(left, right)); - } - - /*** Boolean expressions - Comparisons ***/ - - @Override - public void exitFieldValueComparison(FieldValueComparisonContext ctx) { - Expression right = this.stack.pop(Expression.class); - Expression left = this.stack.pop(Expression.class); - if (!left.getClass().equals(right.getClass())) { - throw new ParseCancellationException(TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES - + left.getClass() + " and " + right.getClass()); - } - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitStringComparison(StringComparisonContext ctx) { - StringExpression right = this.stack.pop(StringExpression.class); - StringExpression left = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitNumericComparison(NumericComparisonContext ctx) { - NumericExpression right = this.stack.pop(NumericExpression.class); - NumericExpression left = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitBooleanComparison(BooleanComparisonContext ctx) { - BooleanExpression right = this.stack.pop(BooleanExpression.class); - BooleanExpression left = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitDateComparison(DateComparisonContext ctx) { - DateExpression right = this.stack.pop(DateExpression.class); - DateExpression left = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitTimeComparison(TimeComparisonContext ctx) { - TimeExpression right = this.stack.pop(TimeExpression.class); - TimeExpression left = this.stack.pop(TimeExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitDurationComparison(DurationComparisonContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DurationExpression left = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - /*** Boolean expressions - Conditions ***/ - - @Override - public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { - StringExpression expression = this.stack.pop(StringExpression.class); - String operator = - ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; - this.stack.push(this.script.composeComparisonOperation(expression, operator, - this.script.getStringLiteralFromUnquotedString(""))); - } - - @Override - public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { - PathExpression reference = this.stack.pop(PathExpression.class); - if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { - this.stack.push(this.script.composeLogicalNot(this.script.composeExistsCondition(reference))); - } else { - this.stack.push(this.script.composeExistsCondition(reference)); - } - } - - @Override - public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { - StringExpression expression = this.stack.pop(StringExpression.class); - - BooleanExpression condition = - this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); - if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { - condition = this.script.composeLogicalNot(condition); - } - this.stack.push(condition); - } - - @Override - public void exitInListCondition(EfxParser.InListConditionContext ctx) { - StringListExpression list = this.stack.pop(StringListExpression.class); - StringExpression expression = this.stack.pop(StringExpression.class); - BooleanExpression condition = this.script.composeContainsCondition(expression, list); - if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { - condition = this.script.composeLogicalNot(condition); - } - this.stack.push(condition); - } - - /*** Numeric expressions ***/ - - @Override - public void exitAdditionExpression(EfxParser.AdditionExpressionContext ctx) { - NumericExpression right = this.stack.pop(NumericExpression.class); - NumericExpression left = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitMultiplicationExpression(EfxParser.MultiplicationExpressionContext ctx) { - NumericExpression right = this.stack.pop(NumericExpression.class); - NumericExpression left = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitParenthesizedNumericExpression(ParenthesizedNumericExpressionContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(NumericExpression.class), NumericExpression.class)); - } - - /*** Duration Expressions ***/ - - @Override - public void exitDurationAdditionExpression(DurationAdditionExpressionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DurationExpression left = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeAddition(left, right)); - } - - @Override - public void exitDurationSubtractionExpression(DurationSubtractionExpressionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DurationExpression left = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeSubtraction(left, right)); - } - - @Override - public void exitDurationLeftMultiplicationExpression( - DurationLeftMultiplicationExpressionContext ctx) { - DurationExpression duration = this.stack.pop(DurationExpression.class); - NumericExpression number = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeMultiplication(number, duration)); - } - - @Override - public void exitDurationRightMultiplicationExpression( - DurationRightMultiplicationExpressionContext ctx) { - NumericExpression number = this.stack.pop(NumericExpression.class); - DurationExpression duration = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeMultiplication(number, duration)); - } - - @Override - public void exitDateSubtractionExpression(DateSubtractionExpressionContext ctx) { - final DateExpression startDate = this.stack.pop(DateExpression.class); - final DateExpression endDate = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeSubtraction(startDate, endDate)); - } - - @Override - public void exitCodeList(CodeListContext ctx) { - if (this.stack.empty()) { - this.stack.push(this.script.composeList(Collections.emptyList(), StringListExpression.class)); - } - } - - @Override - public void exitExplicitList(ExplicitListContext ctx) { - if (this.stack.empty() || ctx.expression().size() == 0) { - this.stack.push(this.script.composeList(Collections.emptyList(), StringListExpression.class)); - return; - } - - List list = new ArrayList<>(); - for (int i = 0; i < ctx.expression().size(); i++) { - list.add(0, this.stack.pop(StringExpression.class)); - } - this.stack.push(this.script.composeList(list, StringListExpression.class)); - } - - /*** Literals ***/ - - @Override - public void exitNumericLiteral(NumericLiteralContext ctx) { - this.stack.push(this.script.getNumericLiteralEquivalent(ctx.getText())); - } - - @Override - public void exitStringLiteral(StringLiteralContext ctx) { - this.stack.push(this.script.getStringLiteralEquivalent(ctx.getText())); - } - - @Override - public void exitTrueBooleanLiteral(TrueBooleanLiteralContext ctx) { - this.stack.push(this.script.getBooleanEquivalent(true)); - } - - @Override - public void exitFalseBooleanLiteral(FalseBooleanLiteralContext ctx) { - this.stack.push(this.script.getBooleanEquivalent(false)); - } - - @Override - public void exitDateLiteral(DateLiteralContext ctx) { - this.stack.push(this.script.getDateLiteralEquivalent(ctx.DATE().getText())); - } - - @Override - public void exitTimeLiteral(TimeLiteralContext ctx) { - this.stack.push(this.script.getTimeLiteralEquivalent(ctx.TIME().getText())); - } - - @Override - public void exitDurationLiteral(DurationLiteralContext ctx) { - this.stack.push(this.script.getDurationLiteralEquivalent(ctx.getText())); - } - - /*** References ***/ - - @Override - public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { - this.stack.push( - this.symbols.getRelativePathOfNode(ctx.NodeId().getText(), this.efxContext.absolutePath())); - } - - @Override - public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx) { - this.stack.push( - symbols.getRelativePathOfField(ctx.FieldId().getText(), this.efxContext.absolutePath())); - } - - @Override - public void enterAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.push(null); - } - } - - @Override - public void exitAbsoluteFieldReference(EfxParser.AbsoluteFieldReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.pop(); - } - } - - @Override - public void enterAbsoluteNodeReference(EfxParser.AbsoluteNodeReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.push(null); - } - } - - @Override - public void exitAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.pop(); - } - } - - - /*** References with Predicates ***/ - - @Override - public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx) { - if (ctx.predicate() != null) { - BooleanExpression predicate = this.stack.pop(BooleanExpression.class); - PathExpression nodeReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate, - PathExpression.class)); - } - } - - @Override - public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicateContext ctx) { - if (ctx.predicate() != null) { - BooleanExpression predicate = this.stack.pop(BooleanExpression.class); - PathExpression fieldReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeFieldReferenceWithPredicate(fieldReference, predicate, - PathExpression.class)); - } - } - - /** - * Any field references in the predicate must be resolved relative to the field on which the - * predicate is applied. Therefore we need to switch to the field's context while the predicate is - * being parsed. - * - * @param ctx the predicate context - */ - @Override - public void enterPredicate(EfxParser.PredicateContext ctx) { - final String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx.getParent()); - this.efxContext.pushFieldContext(fieldId); - } - - /** - * After the predicate is parsed we need to switch back to the previous context. - * - * @param ctx the predicate context - */ - @Override - public void exitPredicate(EfxParser.PredicateContext ctx) { - this.efxContext.pop(); - } - - /*** External References ***/ - - @Override - public void exitNoticeReference(EfxParser.NoticeReferenceContext ctx) { - this.stack.push(this.script.composeExternalReference(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNoticeContext ctx) { - if (ctx.noticeReference() != null) { - PathExpression field = this.stack.pop(PathExpression.class); - PathExpression notice = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeFieldInExternalReference(notice, field)); - } - } - - /*** Value References ***/ - - @Override - public void exitUntypedFieldValueReference(UntypedFieldValueReferenceContext ctx) { - - PathExpression path = this.stack.pop(PathExpression.class); - String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); - - if (parsedPath.hasAttribute()) { - this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), - parsedPath.getAttribute(), StringExpression.class)); - } else if (fieldId != null) { - this.stack.push(this.script.composeFieldValueReference(path, - Expression.types.get(this.symbols.getTypeOfField(fieldId)))); - } else { - this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); - } - } - - @Override - public void exitUntypedAttributeValueReference(UntypedAttributeValueReferenceContext ctx) { - this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - ctx.Identifier().getText(), StringExpression.class)); - } - - /*** References with context override ***/ - - /** - * Handles expressions of the form ContextField::ReferencedField. Changes the context before the - * reference is resolved. - * - * @param ctx the context field context - */ - @Override - public void exitFieldContext(FieldContextContext ctx) { - this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for - // the context field. - final String contextFieldId = ctx.context.reference.simpleFieldReference().FieldId().getText(); - this.efxContext - .push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), - this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); - } - - - /** - * Handles expressions of the form ContextField::ReferencedField. Changes the context before the - * reference is resolved. - * - * @param ctx the parser rule context - */ - @Override - public void exitFieldReferenceWithFieldContextOverride( - FieldReferenceWithFieldContextOverrideContext ctx) { - if (ctx.context != null) { - final PathExpression field = this.stack.pop(PathExpression.class); - this.stack.push(this.script.joinPaths(this.efxContext.relativePath(), field)); - this.efxContext.pop(); // Restores the previous context - } - } - - /** - * Handles expressions of the form ContextNode::ReferencedField. Changes the context before the - * reference is resolved. - * - * @param ctx the parser rule context - */ - @Override - public void exitNodeContext(NodeContextContext ctx) { - this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for - // the context node. - final String contextNodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx.context); - this.efxContext - .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); - } - - /** - * Handles expressions of the form ContextNode::ReferencedField. Restores the context after the - * reference is resolved. - * - * @param ctx the parser rule context - */ - @Override - public void exitFieldReferenceWithNodeContextOverride( - FieldReferenceWithNodeContextOverrideContext ctx) { - if (ctx.context != null) { - final PathExpression field = this.stack.pop(PathExpression.class); - this.stack.push(this.script.joinPaths(this.efxContext.relativePath(), field)); - this.efxContext.pop(); // Restores the previous context - } - } - - /*** Other References ***/ - - @Override - public void exitCodelistReference(CodelistReferenceContext ctx) { - this.stack.push(this.script.composeList(this.symbols.expandCodelist(ctx.codeListId.getText()) - .stream().map(s -> this.script.getStringLiteralFromUnquotedString(s)) - .collect(Collectors.toList()), StringListExpression.class)); - } - - /*** Boolean functions ***/ - - @Override - public void exitNotFunction(NotFunctionContext ctx) { - this.stack.push(this.script.composeLogicalNot(this.stack.pop(BooleanExpression.class))); - } - - @Override - public void exitContainsFunction(ContainsFunctionContext ctx) { - final StringExpression needle = this.stack.pop(StringExpression.class); - final StringExpression haystack = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeContainsCondition(haystack, needle)); - } - - @Override - public void exitStartsWithFunction(StartsWithFunctionContext ctx) { - final StringExpression startsWith = this.stack.pop(StringExpression.class); - final StringExpression text = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeStartsWithCondition(text, startsWith)); - } - - @Override - public void exitEndsWithFunction(EndsWithFunctionContext ctx) { - final StringExpression endsWith = this.stack.pop(StringExpression.class); - final StringExpression text = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeEndsWithCondition(text, endsWith)); - } - - /*** Numeric functions ***/ - - @Override - public void exitCountFunction(CountFunctionContext ctx) { - this.stack.push(this.script.composeCountOperation(this.stack.pop(PathExpression.class))); - } - - @Override - public void exitNumberFunction(NumberFunctionContext ctx) { - this.stack.push(this.script.composeToNumberConversion(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitSumFunction(SumFunctionContext ctx) { - this.stack.push(this.script.composeSumOperation(this.stack.pop(PathExpression.class))); - } - - @Override - public void exitStringLengthFunction(StringLengthFunctionContext ctx) { - this.stack - .push(this.script.composeStringLengthCalculation(this.stack.pop(StringExpression.class))); - } - - /*** String functions ***/ - - @Override - public void exitSubstringFunction(SubstringFunctionContext ctx) { - final NumericExpression length = - ctx.length != null ? this.stack.pop(NumericExpression.class) : null; - final NumericExpression start = this.stack.pop(NumericExpression.class); - final StringExpression text = this.stack.pop(StringExpression.class); - if (length != null) { - this.stack.push(this.script.composeSubstringExtraction(text, start, length)); - } else { - this.stack.push(this.script.composeSubstringExtraction(text, start)); - } - } - - @Override - public void exitToStringFunction(ToStringFunctionContext ctx) { - this.stack.push(this.script.composeToStringConversion(this.stack.pop(NumericExpression.class))); - } - - @Override - public void exitConcatFunction(ConcatFunctionContext ctx) { - if (this.stack.empty() || ctx.stringExpression().size() == 0) { - this.stack.push(this.script.composeStringConcatenation(Collections.emptyList())); - return; - } - - List list = new ArrayList<>(); - for (int i = 0; i < ctx.stringExpression().size(); i++) { - list.add(0, this.stack.pop(StringExpression.class)); - } - this.stack.push(this.script.composeStringConcatenation(list)); - } - - @Override - public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { - final StringExpression format = this.stack.pop(StringExpression.class); - final NumericExpression number = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeNumberFormatting(number, format)); - } - - /*** Date functions ***/ - - @Override - public void exitDateFromStringFunction(DateFromStringFunctionContext ctx) { - this.stack.push(this.script.composeToDateConversion(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitDatePlusMeasureFunction(DatePlusMeasureFunctionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DateExpression left = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeAddition(left, right)); - } - - @Override - public void exitDateMinusMeasureFunction(DateMinusMeasureFunctionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DateExpression left = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeSubtraction(left, right)); - } - - /*** Time functions ***/ - - @Override - public void exitTimeFromStringFunction(TimeFromStringFunctionContext ctx) { - this.stack.push(this.script.composeToTimeConversion(this.stack.pop(StringExpression.class))); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java deleted file mode 100644 index 2da591f1..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java +++ /dev/null @@ -1,532 +0,0 @@ -package eu.europa.ted.efx.sdk0.v6; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import org.antlr.v4.runtime.BaseErrorListener; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.ParseTreeWalker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -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.model.ContentBlock; -import eu.europa.ted.efx.model.ContentBlockStack; -import eu.europa.ted.efx.model.Context; -import eu.europa.ted.efx.model.Context.FieldContext; -import eu.europa.ted.efx.model.Context.NodeContext; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Markup; -import eu.europa.ted.efx.model.VariableList; -import eu.europa.ted.efx.sdk0.v6.EfxParser.AssetIdContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.AssetTypeContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ContextDeclarationBlockContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.LabelTemplateContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.LabelTypeContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ShorthandBtLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ShorthandContextFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ShorthandContextFieldValueReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ShorthandContextLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ShorthandFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ShorthandFieldValueLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.StandardExpressionBlockContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.StandardLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.TemplateFileContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.TemplateLineContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.TextTemplateContext; -import eu.europa.ted.efx.sdk0.v6.EfxParser.ValueTemplateContext; - -/** - * The EfxTemplateTranslator extends the {@link EfxExpressionTranslator06} to provide additional - * translation capabilities for EFX templates. If has been implemented as an extension to the - * EfxExpressionTranslator in order to keep things simpler when one only needs to translate EFX - * expressions (like the condition associated with a business rule). - */ -@SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.EFX_TEMPLATE_TRANSLATOR) -public class EfxTemplateTranslator06 extends EfxExpressionTranslator06 - implements EfxTemplateTranslator { - - private static final Logger logger = LoggerFactory.getLogger(EfxTemplateTranslator06.class); - - private static final String INCONSISTENT_INDENTATION_SPACES = - "Inconsistent indentation. Expected a multiple of %d spaces."; - private static final String INDENTATION_LEVEL_SKIPPED = "Indentation level skipped."; - private static final String START_INDENT_AT_ZERO = - "Incorrect indentation. Please do not indent the first level in your template."; - private static final String MIXED_INDENTATION = - "Do not mix indentation methods. Stick with either tabs or spaces."; - private static final String UNEXPECTED_INDENTATION = "Unexpected indentation tracker state."; - - private static final String LABEL_TYPE_VALUE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.LABEL_TYPE_VALUE).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_INDICATOR = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_INDICATOR).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_BT = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_BT).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_FIELD = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_FIELD).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_CODE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_CODE).replaceAll("^'|'$", ""); - - - /** - * Used to control the indentation style used in a template - */ - private enum Indent { - TABS, SPACES, UNDETERMINED - } - - private Indent indentWith = Indent.UNDETERMINED; - private int indentSpaces = -1; - - /** - * The MarkupGenerator is called to retrieve markup in the target template language when needed. - */ - MarkupGenerator markup; - - final ContentBlock rootBlock = ContentBlock.newRootBlock(); - - /** - * The block stack is used to keep track of the indentation of template lines and adjust the EFX - * context accordingly. A block is a template line together with the template lines nested - * (through indentation) under it. At the top of the blockStack is the template block that is - * currently being processed. The next block in the stack is its parent block, and so on. - */ - ContentBlockStack blockStack = new ContentBlockStack(); - - @SuppressWarnings("unused") - private EfxTemplateTranslator06() { - super(); - } - - public EfxTemplateTranslator06(final MarkupGenerator markupGenerator, - final SymbolResolver symbolResolver, final ScriptGenerator scriptGenerator, - final BaseErrorListener errorListener) { - super(symbolResolver, scriptGenerator, errorListener); - - this.markup = markupGenerator; - } - - /** - * Opens the indicated EFX file and translates the EFX template it contains. - */ - @Override - public String renderTemplate(final Path pathname) throws IOException { - - return renderTemplate(CharStreams.fromPath(pathname)); - } - - /** - * Translates the template contained in the string passed as a parameter. - */ - @Override - public String renderTemplate(final String template) { - return renderTemplate(CharStreams.fromString(template)); - } - - @Override - public String renderTemplate(final InputStream stream) throws IOException { - return renderTemplate(CharStreams.fromStream(stream)); - } - - private String renderTemplate(final CharStream charStream) { - logger.info("Rendering template"); - - final EfxLexer lexer = new EfxLexer(charStream); - final CommonTokenStream tokens = new CommonTokenStream(lexer); - final EfxParser parser = new EfxParser(tokens); - - if (errorListener != null) { - lexer.removeErrorListeners(); - lexer.addErrorListener(errorListener); - parser.removeErrorListeners(); - parser.addErrorListener(errorListener); - } - - final ParseTree tree = parser.templateFile(); - - final ParseTreeWalker walker = new ParseTreeWalker(); - walker.walk(this, tree); - - logger.info("Finished rendering template"); - - return getTranslatedMarkup(); - } - - /** - * Gets the translated code after the walker finished its walk. Every line in the template has now - * been translated and a {@link Markup} object is in the stack for each block in the template. The - * output template is built from the end towards the top, because the items are removed from the - * stack in reverse order. - * - * @return The translated code, trimmed - */ - private String getTranslatedMarkup() { - logger.debug("Getting translated markup."); - - final StringBuilder sb = new StringBuilder(64); - while (!this.stack.empty()) { - sb.insert(0, '\n').insert(0, this.stack.pop(Markup.class).script); - } - - logger.debug("Finished getting translated markup."); - - return sb.toString().trim(); - } - - /*** Template File ***/ - - @Override - public void enterTemplateFile(TemplateFileContext ctx) { - assert blockStack.isEmpty() : UNEXPECTED_INDENTATION; - } - - @Override - public void exitTemplateFile(TemplateFileContext ctx) { - this.blockStack.pop(); - - List templateCalls = new ArrayList<>(); - List templates = new ArrayList<>(); - for (ContentBlock rootBlock : this.rootBlock.getChildren()) { - templateCalls.add(rootBlock.renderCallTemplate(markup)); - rootBlock.renderTemplate(markup, templates); - } - Markup file = this.markup.composeOutputFile(templateCalls, templates); - this.stack.push(file); - } - - /*** Source template blocks ***/ - - @Override - public void exitTextTemplate(TextTemplateContext ctx) { - Markup template = - ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); - String text = ctx.text() != null ? ctx.text().getText() : ""; - this.stack.push(this.markup.renderFreeText(text).join(template)); - } - - @Override - public void exitLabelTemplate(LabelTemplateContext ctx) { - 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 exitValueTemplate(ValueTemplateContext 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)); - } - - - /*** Label Blocks #{...} ***/ - - @Override - public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { - StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(assetType, this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), assetId)))); - } - - @Override - public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) { - StringExpression assetId = this.script.getStringLiteralFromUnquotedString(ctx.BtId().getText()); - StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - 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)))); - } - - @Override - public void exitShorthandFieldLabelReference(ShorthandFieldLabelReferenceContext ctx) { - final String fieldId = ctx.FieldId().getText(); - StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - - if (labelType.script.equals("value")) { - this.shorthandFieldValueLabelReference(fieldId); - } else { - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), - this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(fieldId))))); - } - } - - @Override - public void exitShorthandFieldValueLabelReference(ShorthandFieldValueLabelReferenceContext ctx) { - this.shorthandFieldValueLabelReference(ctx.FieldId().getText()); - } - - private void shorthandFieldValueLabelReference(final String fieldId) { - final Context currentContext = this.efxContext.peek(); - final StringExpression valueReference = this.script.composeFieldValueReference( - symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), - StringExpression.class); - final String fieldType = this.symbols.getTypeOfField(fieldId); - switch (fieldType) { - case "indicator": - this.stack - .push(this.markup.renderLabelFromExpression(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_VALUE), - this.script.getStringLiteralFromUnquotedString("-"), valueReference, - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(fieldId))))); - break; - case "code": - case "internal-code": - this.stack - .push(this.markup.renderLabelFromExpression(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_VALUE), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString( - this.symbols.getRootCodelistOfField(fieldId)), - this.script.getStringLiteralFromUnquotedString("."), valueReference)))); - break; - default: - throw new ParseCancellationException(String.format( - "Unexpected field type '%s'. Expected a field of either type 'code' or 'indicator'.", - fieldType)); - } - } - - /** - * Handles the #{labelType} shorthand syntax which renders the label of the Field or Node declared - * as the context. - * - * If the labelType is 'value', then the label depends on the field's value and is rendered - * according to the field's type. The assetType is inferred from the Field or Node declared as - * context. - */ - @Override - public void exitShorthandContextLabelReference(ShorthandContextLabelReferenceContext ctx) { - final String labelType = ctx.LabelType().getText(); - if (this.efxContext.isFieldContext()) { - if (labelType.equals(LABEL_TYPE_VALUE)) { - this.shorthandFieldValueLabelReference(this.efxContext.symbol()); - } else { - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(labelType), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); - } - } else if (this.efxContext.isNodeContext()) { - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_BT), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(labelType), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); - } - } - - /** - * Handles the #value shorthand syntax which renders the label corresponding to the value of the - * the field declared as the context of the current line of the template. This shorthand syntax is - * only supported for fields of type 'code' or 'indicator'. - */ - @Override - public void exitShorthandContextFieldLabelReference( - ShorthandContextFieldLabelReferenceContext ctx) { - if (!this.efxContext.isFieldContext()) { - throw new ParseCancellationException( - "The #value shorthand syntax can only be used in a field is declared as context."); - } - this.shorthandFieldValueLabelReference(this.efxContext.symbol()); - } - - @Override - public void exitAssetType(AssetTypeContext ctx) { - if (ctx.expressionBlock() == null) { - this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); - } - } - - @Override - public void exitLabelType(LabelTypeContext ctx) { - if (ctx.expressionBlock() == null) { - this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); - } - } - - @Override - public void exitAssetId(AssetIdContext ctx) { - if (ctx.expressionBlock() == null) { - this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); - } - } - - /*** Expression Blocks ${...} ***/ - - /** - * Handles a standard expression block in a template line. Most of the work is done by the base - * class {@link EfxExpressionTranslator06}. After the expression is translated, the result is - * passed through the renderer. - */ - @Override - public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { - this.stack.push(this.stack.pop(Expression.class)); - } - - /*** - * Handles the $value shorthand syntax which renders the value of the field declared as context in - * the current line of the template. - */ - @Override - public void exitShorthandContextFieldValueReference( - ShorthandContextFieldValueReferenceContext ctx) { - if (!this.efxContext.isFieldContext()) { - throw new ParseCancellationException( - "The $value shorthand syntax can only be used when a field is declared as the context."); - } - this.stack.push(this.script.composeFieldValueReference( - symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.absolutePath()), - Expression.class)); - } - - /*** Context Declaration Blocks {...} ***/ - - /** - * This method changes the current EFX context. - * - * The EFX context is always assumed to be either a Field or a Node. Any predicate included in the - * EFX context declaration is not relevant and is ignored. - */ - @Override - public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { - - final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - if (filedId != null) { - this.efxContext.push(new FieldContext(filedId, this.stack.pop(PathExpression.class))); - } else { - final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); - assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; - this.efxContext.push(new NodeContext(nodeId, this.stack.pop(PathExpression.class))); - } - } - - - /*** Template lines ***/ - - @Override - public void exitTemplateLine(TemplateLineContext ctx) { - final VariableList variables = new VariableList(); // template variables not supported by EFX - // prior to 2.0.0 - final Context lineContext = this.efxContext.pop(); - final int indentLevel = this.getIndentLevel(ctx); - final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); - final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); - final Integer outlineNumber = - ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; - assert this.stack.empty() : "Stack should be empty at this point."; - - if (indentChange > 1) { - throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); - } else if (indentChange == 1) { - if (this.blockStack.isEmpty()) { - throw new ParseCancellationException(START_INDENT_AT_ZERO); - } - this.blockStack.pushChild(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); - } else if (indentChange < 0) { - // lower indent level - for (int i = indentChange; i < 0; i++) { - assert !this.blockStack.isEmpty() : UNEXPECTED_INDENTATION; - assert this.blockStack.currentIndentationLevel() > indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pop(); - } - assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); - } else if (indentChange == 0) { - - if (blockStack.isEmpty()) { - assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, - this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); - } else { - this.blockStack.pushSibling(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); - } - } - } - - private Context relativizeContext(Context childContext, Context parentContext) { - if (parentContext == null) { - return childContext; - } - - if (FieldContext.class.isAssignableFrom(childContext.getClass())) { - return new FieldContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); - } - - assert NodeContext.class.isAssignableFrom( - childContext.getClass()) : "Child context should be either a FieldContext NodeContext."; - - return new NodeContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); - } - - /*** Helpers ***/ - - private int getIndentLevel(TemplateLineContext ctx) { - if (ctx.MixedIndent() != null) { - throw new ParseCancellationException(MIXED_INDENTATION); - } - - if (ctx.Spaces() != null) { - if (this.indentWith == Indent.UNDETERMINED) { - this.indentWith = Indent.SPACES; - this.indentSpaces = ctx.Spaces().getText().length(); - } else if (this.indentWith == Indent.TABS) { - throw new ParseCancellationException(MIXED_INDENTATION); - } - - if (ctx.Spaces().getText().length() % this.indentSpaces != 0) { - throw new ParseCancellationException( - String.format(INCONSISTENT_INDENTATION_SPACES, this.indentSpaces)); - } - return ctx.Spaces().getText().length() / this.indentSpaces; - } else if (ctx.Tabs() != null) { - if (this.indentWith == Indent.UNDETERMINED) { - this.indentWith = Indent.TABS; - } else if (this.indentWith == Indent.SPACES) { - throw new ParseCancellationException(MIXED_INDENTATION); - } - - return ctx.Tabs().getText().length(); - } - return 0; - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java deleted file mode 100644 index 9b09183f..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkCodelist06.java +++ /dev/null @@ -1,21 +0,0 @@ -package eu.europa.ted.efx.sdk0.v6.entity; - -import java.util.List; -import java.util.Optional; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.eforms.sdk.entity.SdkCodelist; - -/** - * Representation of an SdkCodelist for usage in the symbols map. - * - * @author rouschr - */ -@SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.CODELIST) -public class SdkCodelist06 extends SdkCodelist { - - public SdkCodelist06(String codelistId, String codelistVersion, List codes, - Optional parentId) { - super(codelistId, codelistVersion, codes, parentId); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkField06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkField06.java deleted file mode 100644 index d0d3bf9f..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkField06.java +++ /dev/null @@ -1,19 +0,0 @@ -package eu.europa.ted.efx.sdk0.v6.entity; - -import com.fasterxml.jackson.databind.JsonNode; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.eforms.sdk.entity.SdkField; - -@SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.FIELD) -public class SdkField06 extends SdkField { - - public SdkField06(String id, String type, String parentNodeId, String xpathAbsolute, - String xpathRelative, String codelistId) { - super(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId); - } - - public SdkField06(JsonNode field) { - super(field); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java deleted file mode 100644 index 988eef90..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v6/entity/SdkNode06.java +++ /dev/null @@ -1,22 +0,0 @@ -package eu.europa.ted.efx.sdk0.v6.entity; - -import com.fasterxml.jackson.databind.JsonNode; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.eforms.sdk.entity.SdkNode; - -/** - * A node is something like a section. Nodes can be parents of other nodes or parents of fields. - */ -@SdkComponent(versions = {"0.6"}, componentType = SdkComponentType.NODE) -public class SdkNode06 extends SdkNode { - - public SdkNode06(String id, String parentId, String xpathAbsolute, String xpathRelative, - boolean repeatable) { - super(id, parentId, xpathAbsolute, xpathRelative, repeatable); - } - - public SdkNode06(JsonNode node) { - super(node); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java deleted file mode 100644 index 8afc1ec9..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java +++ /dev/null @@ -1,1345 +0,0 @@ -package eu.europa.ted.efx.sdk0.v7; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -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; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; -import eu.europa.ted.efx.interfaces.ScriptGenerator; -import eu.europa.ted.efx.interfaces.SymbolResolver; -import eu.europa.ted.efx.model.CallStack; -import eu.europa.ted.efx.model.CallStackObject; -import eu.europa.ted.efx.model.Context.FieldContext; -import eu.europa.ted.efx.model.Context.NodeContext; -import eu.europa.ted.efx.model.ContextStack; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.BooleanExpression; -import eu.europa.ted.efx.model.Expression.BooleanListExpression; -import eu.europa.ted.efx.model.Expression.DateExpression; -import eu.europa.ted.efx.model.Expression.DateListExpression; -import eu.europa.ted.efx.model.Expression.DurationExpression; -import eu.europa.ted.efx.model.Expression.DurationListExpression; -import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.NumericListExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Expression.StringListExpression; -import eu.europa.ted.efx.model.Expression.TimeExpression; -import eu.europa.ted.efx.model.Expression.TimeListExpression; -import eu.europa.ted.efx.sdk0.v7.EfxParser.*; -import eu.europa.ted.efx.xpath.XPathAttributeLocator; - -/** - * The the goal of the EfxExpressionTranslator is to take an EFX expression and translate it to a - * target scripting language. - * - * The target language syntax is not hardcoded into the translator so that this class can be reused - * to translate to several different languages. Instead a {@link ScriptGenerator} interface is used - * to provide specifics on the syntax of the target scripting language. - * - * Apart from writing expressions that can be translated and evaluated in a target scripting - * language (e.g. XPath/XQuery, JavaScript etc.), EFX also allows the definition of templates that - * can be translated to a target template markup language (e.g. XSLT, Thymeleaf etc.). The - * {@link EfxExpressionTranslator07} only focuses on EFX expressions. To translate EFX templates you - * need to use the {@link EfxTemplateTranslator07} which derives from this class. - */ -@SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.EFX_EXPRESSION_TRANSLATOR) -public class EfxExpressionTranslator07 extends EfxBaseListener - implements EfxExpressionTranslator { - - private static final String NOT_MODIFIER = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); - - /** - * - */ - private static final String TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES = - "Type mismatch. Cannot compare values of different types: "; - - /** - * The stack is used by the methods of this listener to pass data to each other as the parse tree - * is being walked. - */ - protected CallStack stack = new CallStack(); - - /** - * The context stack is used to keep track of context switching in nested expressions. - */ - protected ContextStack efxContext; - - /** - * Symbols are the field identifiers and node identifiers. The symbols map is used to resolve them - * to their location in the data source (typically their XPath). - */ - protected SymbolResolver symbols; - - protected BaseErrorListener errorListener; - - /** - * The ScriptGenerator is called to determine the target language syntax whenever needed. - */ - protected ScriptGenerator script; - - protected EfxExpressionTranslator07() {} - - public EfxExpressionTranslator07(final SymbolResolver symbolResolver, - final ScriptGenerator scriptGenerator, final BaseErrorListener errorListener) { - this.symbols = symbolResolver; - this.script = scriptGenerator; - this.errorListener = errorListener; - - this.efxContext = new ContextStack(symbols); - } - - @Override - public String translateExpression(final String expression, final String... expressionParameters) { - final EfxLexer lexer = - new EfxLexer(CharStreams.fromString(expression)); - final CommonTokenStream tokens = new CommonTokenStream(lexer); - final EfxParser parser = new EfxParser(tokens); - - if (errorListener != null) { - lexer.removeErrorListeners(); - lexer.addErrorListener(errorListener); - parser.removeErrorListeners(); - parser.addErrorListener(errorListener); - } - - final ParseTree tree = parser.singleExpression(); - final ParseTreeWalker walker = new ParseTreeWalker(); - - walker.walk(this, tree); - - return getTranslatedScript(); - } - - /** - * Used to get the translated target language script, after the walker finished its walk. - * - * @return The translated code, trimmed - */ - private String getTranslatedScript() { - final StringBuilder sb = new StringBuilder(this.stack.size() * 100); - while (!this.stack.empty()) { - sb.insert(0, '\n').insert(0, this.stack.pop(Expression.class).script); - } - return sb.toString().trim(); - } - - /** - * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a - * {@link SimpleFieldReferenceContext} to locate a field identifier. - * - * @param ctx The context to start searching from. - * @return The field identifier, or null if none was found. - */ - protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { - - if (ctx instanceof SimpleFieldReferenceContext) { - return ((SimpleFieldReferenceContext) ctx).FieldId().getText(); - } - - if (ctx instanceof AbsoluteFieldReferenceContext) { - return ((AbsoluteFieldReferenceContext) ctx).reference.simpleFieldReference().FieldId() - .getText(); - } - - if (ctx instanceof FieldReferenceWithFieldContextOverrideContext) { - return ((FieldReferenceWithFieldContextOverrideContext) ctx).reference.simpleFieldReference() - .FieldId().getText(); - } - - if (ctx instanceof FieldReferenceWithNodeContextOverrideContext) { - return ((FieldReferenceWithNodeContextOverrideContext) ctx).reference - .fieldReferenceWithPredicate().simpleFieldReference().FieldId().getText(); - } - - SimpleFieldReferenceContext fieldReferenceContext = - ctx.getChild(SimpleFieldReferenceContext.class, 0); - if (fieldReferenceContext != null) { - return fieldReferenceContext.FieldId().getText(); - } - - for (ParseTree child : ctx.children) { - if (child instanceof ParserRuleContext) { - String fieldId = getFieldIdFromChildSimpleFieldReferenceContext((ParserRuleContext) child); - if (fieldId != null) { - return fieldId; - } - } - } - - return null; - } - - /** - * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a - * {@link SimpleNodeReferenceContext} to locate a node identifier. - * - * @param ctx The context to start searching from. - * @return The node identifier, or null if none was found. - */ - protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { - - if (ctx instanceof SimpleNodeReferenceContext) { - return ((SimpleNodeReferenceContext) ctx).NodeId().getText(); - } - - for (ParseTree child : ctx.children) { - if (child instanceof ParserRuleContext) { - String nodeId = getNodeIdFromChildSimpleNodeReferenceContext((ParserRuleContext) child); - if (nodeId != null) { - return nodeId; - } - } - } - - return null; - } - - @Override - public void enterSingleExpression(SingleExpressionContext ctx) { - final TerminalNode fieldContext = ctx.FieldId(); - if (fieldContext != null) { - this.efxContext.pushFieldContext(fieldContext.getText()); - } else { - final TerminalNode nodeContext = ctx.NodeId(); - if (nodeContext != null) { - this.efxContext.pushNodeContext(nodeContext.getText()); - } - } - } - - @Override - public void exitSingleExpression(SingleExpressionContext ctx) { - this.efxContext.pop(); - } - - /*** Boolean expressions ***/ - - @Override - public void exitParenthesizedBooleanExpression( - EfxParser.ParenthesizedBooleanExpressionContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(BooleanExpression.class), BooleanExpression.class)); - } - - @Override - public void exitLogicalAndCondition(EfxParser.LogicalAndConditionContext ctx) { - BooleanExpression right = this.stack.pop(BooleanExpression.class); - BooleanExpression left = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeLogicalAnd(left, right)); - } - - @Override - public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { - BooleanExpression right = this.stack.pop(BooleanExpression.class); - BooleanExpression left = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeLogicalOr(left, right)); - } - - /*** Boolean expressions - Comparisons ***/ - - @Override - public void exitFieldValueComparison(FieldValueComparisonContext ctx) { - Expression right = this.stack.pop(Expression.class); - Expression left = this.stack.pop(Expression.class); - if (!left.getClass().equals(right.getClass())) { - throw new ParseCancellationException(TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES - + left.getClass() + " and " + right.getClass()); - } - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitStringComparison(StringComparisonContext ctx) { - StringExpression right = this.stack.pop(StringExpression.class); - StringExpression left = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitNumericComparison(NumericComparisonContext ctx) { - NumericExpression right = this.stack.pop(NumericExpression.class); - NumericExpression left = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitBooleanComparison(BooleanComparisonContext ctx) { - BooleanExpression right = this.stack.pop(BooleanExpression.class); - BooleanExpression left = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitDateComparison(DateComparisonContext ctx) { - DateExpression right = this.stack.pop(DateExpression.class); - DateExpression left = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitTimeComparison(TimeComparisonContext ctx) { - TimeExpression right = this.stack.pop(TimeExpression.class); - TimeExpression left = this.stack.pop(TimeExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitDurationComparison(DurationComparisonContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DurationExpression left = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); - } - - /*** Boolean expressions - Conditions ***/ - - @Override - public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { - StringExpression expression = this.stack.pop(StringExpression.class); - String operator = - ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; - this.stack.push(this.script.composeComparisonOperation(expression, operator, - this.script.getStringLiteralFromUnquotedString(""))); - } - - @Override - public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { - PathExpression reference = this.stack.pop(PathExpression.class); - if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { - this.stack.push(this.script.composeLogicalNot(this.script.composeExistsCondition(reference))); - } else { - this.stack.push(this.script.composeExistsCondition(reference)); - } - } - - @Override - public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { - StringExpression expression = this.stack.pop(StringExpression.class); - - BooleanExpression condition = - this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); - if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { - condition = this.script.composeLogicalNot(condition); - } - this.stack.push(condition); - } - - /*** Boolean expressions - List membership conditions ***/ - - @Override - public void exitStringInListCondition(EfxParser.StringInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, StringExpression.class, StringListExpression.class); - } - - @Override - public void exitBooleanInListCondition(BooleanInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, BooleanExpression.class, BooleanListExpression.class); - } - - @Override - public void exitNumberInListCondition(NumberInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, NumericExpression.class, NumericListExpression.class); - } - - @Override - public void exitDateInListCondition(DateInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, DateExpression.class, DateListExpression.class); - } - - @Override - public void exitTimeInListCondition(TimeInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, TimeExpression.class, TimeListExpression.class); - } - - @Override - public void exitDurationInListCondition(DurationInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, DurationExpression.class, DurationListExpression.class); - } - - private > void exitInListCondition( - Token modifier, Class expressionType, Class listType) { - ListExpression list = this.stack.pop(listType); - T expression = this.stack.pop(expressionType); - BooleanExpression condition = this.script.composeContainsCondition(expression, list); - if (modifier != null && modifier.getText().equals(NOT_MODIFIER)) { - condition = this.script.composeLogicalNot(condition); - } - this.stack.push(condition); - } - - /*** Quantified expressions ***/ - - @Override - public void exitStringQuantifiedExpression(StringQuantifiedExpressionContext ctx) { - this.exitQuantifiedExpression(ctx.Every() != null, StringExpression.class, - StringListExpression.class); - } - - @Override - public void exitBooleanQuantifiedExpression(BooleanQuantifiedExpressionContext ctx) { - this.exitQuantifiedExpression(ctx.Every() != null, BooleanExpression.class, - BooleanListExpression.class); - } - - @Override - public void exitNumericQuantifiedExpression(NumericQuantifiedExpressionContext ctx) { - this.exitQuantifiedExpression(ctx.Every() != null, NumericExpression.class, - NumericListExpression.class); - } - - @Override - public void exitDateQuantifiedExpression(DateQuantifiedExpressionContext ctx) { - this.exitQuantifiedExpression(ctx.Every() != null, DateExpression.class, - DateListExpression.class); - } - - @Override - public void exitTimeQuantifiedExpression(TimeQuantifiedExpressionContext ctx) { - this.exitQuantifiedExpression(ctx.Every() != null, TimeExpression.class, - TimeListExpression.class); - } - - @Override - public void exitDurationQuantifiedExpression(DurationQuantifiedExpressionContext ctx) { - this.exitQuantifiedExpression(ctx.Every() != null, DurationExpression.class, - DurationListExpression.class); - } - - private > void exitQuantifiedExpression( - boolean every, Class expressionType, Class listType) { - BooleanExpression booleanExpression = this.stack.pop(BooleanExpression.class); - L list = this.stack.pop(listType); - T variable = this.stack.pop(expressionType); - if (every) { - this.stack.push(this.script.composeAllSatisfy(list, variable.script, booleanExpression)); - } else { - this.stack.push(this.script.composeAnySatisfies(list, variable.script, booleanExpression)); - } - } - - /*** Numeric expressions ***/ - - @Override - public void exitAdditionExpression(EfxParser.AdditionExpressionContext ctx) { - NumericExpression right = this.stack.pop(NumericExpression.class); - NumericExpression left = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitMultiplicationExpression(EfxParser.MultiplicationExpressionContext ctx) { - NumericExpression right = this.stack.pop(NumericExpression.class); - NumericExpression left = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); - } - - @Override - public void exitParenthesizedNumericExpression(ParenthesizedNumericExpressionContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(NumericExpression.class), NumericExpression.class)); - } - - /*** Duration Expressions ***/ - - @Override - public void exitDurationAdditionExpression(DurationAdditionExpressionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DurationExpression left = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeAddition(left, right)); - } - - @Override - public void exitDurationSubtractionExpression(DurationSubtractionExpressionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DurationExpression left = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeSubtraction(left, right)); - } - - @Override - public void exitDurationLeftMultiplicationExpression( - DurationLeftMultiplicationExpressionContext ctx) { - DurationExpression duration = this.stack.pop(DurationExpression.class); - NumericExpression number = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeMultiplication(number, duration)); - } - - @Override - public void exitDurationRightMultiplicationExpression( - DurationRightMultiplicationExpressionContext ctx) { - NumericExpression number = this.stack.pop(NumericExpression.class); - DurationExpression duration = this.stack.pop(DurationExpression.class); - this.stack.push(this.script.composeMultiplication(number, duration)); - } - - @Override - public void exitDateSubtractionExpression(DateSubtractionExpressionContext ctx) { - final DateExpression startDate = this.stack.pop(DateExpression.class); - final DateExpression endDate = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeSubtraction(startDate, endDate)); - } - - @Override - public void exitCodeList(CodeListContext ctx) { - if (this.stack.empty()) { - this.stack.push(this.script.composeList(Collections.emptyList(), StringListExpression.class)); - } - } - - @Override - public void exitStringList(StringListContext ctx) { - this.exitList(ctx.stringExpression().size(), StringExpression.class, - StringListExpression.class); - } - - @Override - public void exitBooleanList(BooleanListContext ctx) { - this.exitList(ctx.booleanExpression().size(), BooleanExpression.class, - BooleanListExpression.class); - } - - @Override - public void exitNumericList(NumericListContext ctx) { - this.exitList(ctx.numericExpression().size(), NumericExpression.class, - NumericListExpression.class); - } - - @Override - public void exitDateList(DateListContext ctx) { - this.exitList(ctx.dateExpression().size(), DateExpression.class, DateListExpression.class); - } - - @Override - public void exitTimeList(TimeListContext ctx) { - this.exitList(ctx.timeExpression().size(), TimeExpression.class, TimeListExpression.class); - } - - - @Override - public void exitDurationList(DurationListContext ctx) { - this.exitList(ctx.durationExpression().size(), DurationExpression.class, - DurationListExpression.class); - } - - private > void exitList(int listSize, - Class expressionType, Class listType) { - if (this.stack.empty() || listSize == 0) { - this.stack.push(this.script.composeList(Collections.emptyList(), listType)); - return; - } - - List list = new ArrayList<>(); - for (int i = 0; i < listSize; i++) { - list.add(0, this.stack.pop(expressionType)); - } - this.stack.push(this.script.composeList(list, listType)); - } - - /*** Conditional Expressions ***/ - - @Override - public void exitUntypedConditonalExpression(UntypedConditonalExpressionContext ctx) { - Class typeWhenFalse = this.stack.peek().getClass(); - if (typeWhenFalse == BooleanExpression.class) { - this.exitConditionalBooleanExpression(); - } else if (typeWhenFalse == NumericExpression.class) { - this.exitConditionalNumericExpression(); - } else if (typeWhenFalse == StringExpression.class) { - this.exitConditionalStringExpression(); - } else if (typeWhenFalse == DateExpression.class) { - this.exitConditionalDateExpression(); - } else if (typeWhenFalse == TimeExpression.class) { - this.exitConditionalTimeExpression(); - } else if (typeWhenFalse == DurationExpression.class) { - this.exitConditionalDurationExpression(); - } else { - throw new IllegalStateException("Unknown type " + typeWhenFalse); - } - } - - @Override - public void exitConditionalBooleanExpression(ConditionalBooleanExpressionContext ctx) { - this.exitConditionalBooleanExpression(); - } - - @Override - public void exitConditionalNumericExpression(ConditionalNumericExpressionContext ctx) { - this.exitConditionalNumericExpression(); - } - - @Override - public void exitConditionalStringExpression(ConditionalStringExpressionContext ctx) { - this.exitConditionalStringExpression(); - } - - @Override - public void exitConditionalDateExpression(ConditionalDateExpressionContext ctx) { - this.exitConditionalDateExpression(); - } - - @Override - public void exitConditionalTimeExpression(ConditionalTimeExpressionContext ctx) { - this.exitConditionalTimeExpression(); - } - - @Override - public void exitConditionalDurationExpression(ConditionalDurationExpressionContext ctx) { - this.exitConditionalDurationExpression(); - } - - private void exitConditionalBooleanExpression() { - BooleanExpression whenFalse = this.stack.pop(BooleanExpression.class); - BooleanExpression whenTrue = this.stack.pop(BooleanExpression.class); - BooleanExpression condition = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, - BooleanExpression.class)); - } - - private void exitConditionalNumericExpression() { - NumericExpression whenFalse = this.stack.pop(NumericExpression.class); - NumericExpression whenTrue = this.stack.pop(NumericExpression.class); - BooleanExpression condition = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, - NumericExpression.class)); - } - - private void exitConditionalStringExpression() { - StringExpression whenFalse = this.stack.pop(StringExpression.class); - StringExpression whenTrue = this.stack.pop(StringExpression.class); - BooleanExpression condition = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, - StringExpression.class)); - } - - private void exitConditionalDateExpression() { - DateExpression whenFalse = this.stack.pop(DateExpression.class); - DateExpression whenTrue = this.stack.pop(DateExpression.class); - BooleanExpression condition = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, - DateExpression.class)); - } - - private void exitConditionalTimeExpression() { - TimeExpression whenFalse = this.stack.pop(TimeExpression.class); - TimeExpression whenTrue = this.stack.pop(TimeExpression.class); - BooleanExpression condition = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, - TimeExpression.class)); - } - - private void exitConditionalDurationExpression() { - DurationExpression whenFalse = this.stack.pop(DurationExpression.class); - DurationExpression whenTrue = this.stack.pop(DurationExpression.class); - BooleanExpression condition = this.stack.pop(BooleanExpression.class); - this.stack.push(this.script.composeConditionalExpression(condition, whenTrue, whenFalse, - DurationExpression.class)); - } - - /*** Iteration expressions ***/ - - @Override - public void exitParenthesizedStringsFromIteration(ParenthesizedStringsFromIterationContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(StringListExpression.class), StringListExpression.class)); - } - - @Override - public void exitParenthesizedNumbersFromIteration(ParenthesizedNumbersFromIterationContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(NumericListExpression.class), NumericListExpression.class)); - } - - @Override - public void exitParenthesizedBooleansFromIteration( - ParenthesizedBooleansFromIterationContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(BooleanListExpression.class), BooleanListExpression.class)); - } - - @Override - public void exitParenthesizedDatesFromIteration(ParenthesizedDatesFromIterationContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(DateListExpression.class), DateListExpression.class)); - } - - @Override - public void exitParenthesizedTimesFromIteration(ParenthesizedTimesFromIterationContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(TimeListExpression.class), TimeListExpression.class)); - } - - @Override - public void exitParenthesizedDurationsFromITeration( - ParenthesizedDurationsFromITerationContext ctx) { - this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(DurationListExpression.class), DurationListExpression.class)); - } - - @Override - public void exitStringsFromStringIteration(StringsFromStringIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class, - StringExpression.class, StringListExpression.class); - } - - @Override - public void exitStringsFromBooleanIteration(StringsFromBooleanIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class, - StringExpression.class, StringListExpression.class); - } - - @Override - public void exitStringsFromNumericIteration(StringsFromNumericIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class, - StringExpression.class, StringListExpression.class); - } - - @Override - public void exitStringsFromDateIteration(StringsFromDateIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class, - StringExpression.class, StringListExpression.class); - } - - @Override - public void exitStringsFromTimeIteration(StringsFromTimeIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class, - StringExpression.class, StringListExpression.class); - } - - @Override - public void exitStringsFromDurationIteration(StringsFromDurationIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class, - StringExpression.class, StringListExpression.class); - } - - @Override - public void exitBooleansFromStringIteration(BooleansFromStringIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class, - BooleanExpression.class, BooleanListExpression.class); - } - - @Override - public void exitBooleansFromBooleanIteration(BooleansFromBooleanIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class, - BooleanExpression.class, BooleanListExpression.class); - } - - @Override - public void exitBooleansFromNumericIteration(BooleansFromNumericIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class, - BooleanExpression.class, BooleanListExpression.class); - } - - @Override - public void exitBooleansFromDateIteration(BooleansFromDateIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class, - BooleanExpression.class, BooleanListExpression.class); - } - - @Override - public void exitBooleansFromTimeIteration(BooleansFromTimeIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class, - BooleanExpression.class, BooleanListExpression.class); - } - - @Override - public void exitBooleansFromDurationIteration(BooleansFromDurationIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class, - BooleanExpression.class, BooleanListExpression.class); - } - - @Override - public void exitNumbersFromStringIteration(NumbersFromStringIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class, - NumericExpression.class, NumericListExpression.class); - } - - @Override - public void exitNumbersFromBooleanIteration(NumbersFromBooleanIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class, - NumericExpression.class, NumericListExpression.class); - } - - @Override - public void exitNumbersFromNumericIteration(NumbersFromNumericIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class, - NumericExpression.class, NumericListExpression.class); - } - - @Override - public void exitNumbersFromDateIteration(NumbersFromDateIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class, - NumericExpression.class, NumericListExpression.class); - } - - @Override - public void exitNumbersFromTimeIteration(NumbersFromTimeIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class, - NumericExpression.class, NumericListExpression.class); - } - - @Override - public void exitNumbersFromDurationIteration(NumbersFromDurationIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class, - NumericExpression.class, NumericListExpression.class); - } - - - @Override - public void exitDatesFromStringIteration(DatesFromStringIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class, - DateExpression.class, DateListExpression.class); - } - - @Override - public void exitDatesFromBooleanIteration(DatesFromBooleanIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class, - DateExpression.class, DateListExpression.class); - } - - @Override - public void exitDatesFromNumericIteration(DatesFromNumericIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class, - DateExpression.class, DateListExpression.class); - } - - @Override - public void exitDatesFromDateIteration(DatesFromDateIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class, - DateExpression.class, DateListExpression.class); - } - - @Override - public void exitDatesFromTimeIteration(DatesFromTimeIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class, - DateExpression.class, DateListExpression.class); - } - - @Override - public void exitDatesFromDurationIteration(DatesFromDurationIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class, - DateExpression.class, DateListExpression.class); - } - - @Override - public void exitTimesFromStringIteration(TimesFromStringIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class, - TimeExpression.class, TimeListExpression.class); - } - - @Override - public void exitTimesFromBooleanIteration(TimesFromBooleanIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class, - TimeExpression.class, TimeListExpression.class); - } - - @Override - public void exitTimesFromNumericIteration(TimesFromNumericIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class, - TimeExpression.class, TimeListExpression.class); - } - - @Override - public void exitTimesFromDateIteration(TimesFromDateIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class, - TimeExpression.class, TimeListExpression.class); - } - - @Override - public void exitTimesFromTimeIteration(TimesFromTimeIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class, - TimeExpression.class, TimeListExpression.class); - } - - @Override - public void exitTimesFromDurationIteration(TimesFromDurationIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class, - TimeExpression.class, TimeListExpression.class); - } - - @Override - public void exitDurationsFromStringIteration(DurationsFromStringIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class, - DurationExpression.class, DurationListExpression.class); - } - - @Override - public void exitDurationsFromBooleanIteration(DurationsFromBooleanIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class, - DurationExpression.class, DurationListExpression.class); - } - - @Override - public void exitDurationsFromNumericIteration(DurationsFromNumericIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class, - DurationExpression.class, DurationListExpression.class); - } - - @Override - public void exitDurationsFromDateIteration(DurationsFromDateIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class, - DurationExpression.class, DurationListExpression.class); - } - - @Override - public void exitDurationsFromTimeIteration(DurationsFromTimeIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class, - DurationExpression.class, DurationListExpression.class); - } - - @Override - public void exitDurationsFromDurationIteration(DurationsFromDurationIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class, - DurationExpression.class, DurationListExpression.class); - } - - public , T2 extends Expression, L2 extends ListExpression> void exitIterationExpression( - Class variableType, Class sourceListType, Class expressionType, - Class targetListType) { - T2 expression = this.stack.pop(expressionType); - L1 list = this.stack.pop(sourceListType); - T1 variable = this.stack.pop(variableType); - this.stack - .push(this.script.composeForExpression(variable.script, list, expression, targetListType)); - } - - /*** Literals ***/ - - @Override - public void exitNumericLiteral(NumericLiteralContext ctx) { - this.stack.push(this.script.getNumericLiteralEquivalent(ctx.getText())); - } - - @Override - public void exitStringLiteral(StringLiteralContext ctx) { - this.stack.push(this.script.getStringLiteralEquivalent(ctx.getText())); - } - - @Override - public void exitTrueBooleanLiteral(TrueBooleanLiteralContext ctx) { - this.stack.push(this.script.getBooleanEquivalent(true)); - } - - @Override - public void exitFalseBooleanLiteral(FalseBooleanLiteralContext ctx) { - this.stack.push(this.script.getBooleanEquivalent(false)); - } - - @Override - public void exitDateLiteral(DateLiteralContext ctx) { - this.stack.push(this.script.getDateLiteralEquivalent(ctx.DATE().getText())); - } - - @Override - public void exitTimeLiteral(TimeLiteralContext ctx) { - this.stack.push(this.script.getTimeLiteralEquivalent(ctx.TIME().getText())); - } - - @Override - public void exitDurationLiteral(DurationLiteralContext ctx) { - this.stack.push(this.script.getDurationLiteralEquivalent(ctx.getText())); - } - - /*** References ***/ - - @Override - public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { - this.stack.push( - this.symbols.getRelativePathOfNode(ctx.NodeId().getText(), this.efxContext.absolutePath())); - } - - @Override - public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx) { - this.stack.push( - symbols.getRelativePathOfField(ctx.FieldId().getText(), this.efxContext.absolutePath())); - } - - @Override - public void enterAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.push(null); - } - } - - @Override - public void exitAbsoluteFieldReference(EfxParser.AbsoluteFieldReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.pop(); - } - } - - @Override - public void enterAbsoluteNodeReference(EfxParser.AbsoluteNodeReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.push(null); - } - } - - @Override - public void exitAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { - if (ctx.Slash() != null) { - this.efxContext.pop(); - } - } - - - /*** References with Predicates ***/ - - @Override - public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx) { - if (ctx.predicate() != null) { - BooleanExpression predicate = this.stack.pop(BooleanExpression.class); - PathExpression nodeReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate, - PathExpression.class)); - } - } - - @Override - public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicateContext ctx) { - if (ctx.predicate() != null) { - BooleanExpression predicate = this.stack.pop(BooleanExpression.class); - PathExpression fieldReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeFieldReferenceWithPredicate(fieldReference, predicate, - PathExpression.class)); - } - } - - /** - * Any field references in the predicate must be resolved relative to the field on which the - * predicate is applied. Therefore we need to switch to the field's context while the predicate is - * being parsed. - */ - @Override - public void enterPredicate(EfxParser.PredicateContext ctx) { - final String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx.getParent()); - this.efxContext.pushFieldContext(fieldId); - } - - /** - * After the predicate is parsed we need to switch back to the previous context. - */ - @Override - public void exitPredicate(EfxParser.PredicateContext ctx) { - this.efxContext.pop(); - } - - /*** External References ***/ - - @Override - public void exitNoticeReference(EfxParser.NoticeReferenceContext ctx) { - this.stack.push(this.script.composeExternalReference(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNoticeContext ctx) { - if (ctx.noticeReference() != null) { - PathExpression field = this.stack.pop(PathExpression.class); - PathExpression notice = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeFieldInExternalReference(notice, field)); - } - } - - /*** Value References ***/ - - @Override - public void exitUntypedFieldValueReference(UntypedFieldValueReferenceContext ctx) { - - PathExpression path = this.stack.pop(PathExpression.class); - String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); - - if (parsedPath.hasAttribute()) { - this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), - parsedPath.getAttribute(), Expression.types.get(this.symbols.getTypeOfField(fieldId)))); - } else if (fieldId != null) { - this.stack.push(this.script.composeFieldValueReference(path, - Expression.types.get(this.symbols.getTypeOfField(fieldId)))); - } else { - this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); - } - } - - @Override - public void exitUntypedFieldValueSequence(UntypedFieldValueSequenceContext ctx) { - PathExpression path = this.stack.pop(PathExpression.class); - String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); - - if (parsedPath.hasAttribute()) { - this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), - parsedPath.getAttribute(), - Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); - } else if (fieldId != null) { - this.stack.push(this.script.composeFieldValueReference(path, - Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); - } else { - this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); - } - } - - @Override - public void exitUntypedAttributeValueReference(UntypedAttributeValueReferenceContext ctx) { - this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - ctx.Identifier().getText(), StringExpression.class)); - } - - /*** References with context override ***/ - - /** - * Handles expressions of the form ContextField::ReferencedField. Changes the context before the - * reference is resolved. - */ - @Override - public void exitFieldContext(FieldContextContext ctx) { - this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for - // the context field. - final String contextFieldId = ctx.context.reference.simpleFieldReference().FieldId().getText(); - this.efxContext - .push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), - this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); - } - - - /** - * Handles expressions of the form ContextField::ReferencedField. Changes the context before the - * reference is resolved. - */ - @Override - public void exitFieldReferenceWithFieldContextOverride( - FieldReferenceWithFieldContextOverrideContext ctx) { - if (ctx.context != null) { - final PathExpression field = this.stack.pop(PathExpression.class); - this.stack.push(this.script.joinPaths(this.efxContext.relativePath(), field)); - this.efxContext.pop(); // Restores the previous context - } - } - - /** - * Handles expressions of the form ContextNode::ReferencedField. Changes the context before the - * reference is resolved. - */ - @Override - public void exitNodeContext(NodeContextContext ctx) { - this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for - // the context node. - final String contextNodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx.context); - this.efxContext - .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); - } - - /** - * Handles expressions of the form ContextNode::ReferencedField. Restores the context after the - * reference is resolved. - */ - @Override - public void exitFieldReferenceWithNodeContextOverride( - FieldReferenceWithNodeContextOverrideContext ctx) { - if (ctx.context != null) { - final PathExpression field = this.stack.pop(PathExpression.class); - this.stack.push(this.script.joinPaths(this.efxContext.relativePath(), field)); - this.efxContext.pop(); // Restores the previous context - } - } - - /*** Other References ***/ - - @Override - public void exitCodelistReference(CodelistReferenceContext ctx) { - this.stack.push(this.script.composeList(this.symbols.expandCodelist(ctx.codeListId.getText()) - .stream().map(s -> this.script.getStringLiteralFromUnquotedString(s)) - .collect(Collectors.toList()), StringListExpression.class)); - } - - @Override - public void exitUntypedVariable(UntypedVariableContext ctx) { - this.stack.pushVariableReference(ctx.Variable().getText(), - this.script.composeVariableReference(ctx.Variable().getText(), Expression.class)); - } - - @Override - public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), StringExpression.class)); - } - - @Override - public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), BooleanExpression.class)); - } - - @Override - public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), NumericExpression.class)); - } - - @Override - public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), DateExpression.class)); - } - - @Override - public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), TimeExpression.class)); - } - - @Override - public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { - this.stack.pushVariableDeclaration(ctx.Variable().getText(), - this.script.composeVariableDeclaration(ctx.Variable().getText(), DurationExpression.class)); - } - - - /*** Boolean functions ***/ - - @Override - public void exitNotFunction(NotFunctionContext ctx) { - this.stack.push(this.script.composeLogicalNot(this.stack.pop(BooleanExpression.class))); - } - - @Override - public void exitContainsFunction(ContainsFunctionContext ctx) { - final StringExpression needle = this.stack.pop(StringExpression.class); - final StringExpression haystack = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeContainsCondition(haystack, needle)); - } - - @Override - public void exitStartsWithFunction(StartsWithFunctionContext ctx) { - final StringExpression startsWith = this.stack.pop(StringExpression.class); - final StringExpression text = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeStartsWithCondition(text, startsWith)); - } - - @Override - public void exitEndsWithFunction(EndsWithFunctionContext ctx) { - final StringExpression endsWith = this.stack.pop(StringExpression.class); - final StringExpression text = this.stack.pop(StringExpression.class); - this.stack.push(this.script.composeEndsWithCondition(text, endsWith)); - } - - /*** Numeric functions ***/ - - @Override - public void exitCountFunction(CountFunctionContext ctx) { - ListExpression expression = this.stack.pop(ListExpression.class); - this.stack.push(this.script.composeCountOperation(expression)); - } - - @Override - public void exitNumberFunction(NumberFunctionContext ctx) { - this.stack.push(this.script.composeToNumberConversion(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitSumFunction(SumFunctionContext ctx) { - this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericListExpression.class))); - } - - @Override - public void exitStringLengthFunction(StringLengthFunctionContext ctx) { - this.stack - .push(this.script.composeStringLengthCalculation(this.stack.pop(StringExpression.class))); - } - - /*** String functions ***/ - - @Override - public void exitSubstringFunction(SubstringFunctionContext ctx) { - final NumericExpression length = - ctx.length != null ? this.stack.pop(NumericExpression.class) : null; - final NumericExpression start = this.stack.pop(NumericExpression.class); - final StringExpression text = this.stack.pop(StringExpression.class); - if (length != null) { - this.stack.push(this.script.composeSubstringExtraction(text, start, length)); - } else { - this.stack.push(this.script.composeSubstringExtraction(text, start)); - } - } - - @Override - public void exitToStringFunction(ToStringFunctionContext ctx) { - this.stack.push(this.script.composeToStringConversion(this.stack.pop(NumericExpression.class))); - } - - @Override - public void exitConcatFunction(ConcatFunctionContext ctx) { - if (this.stack.empty() || ctx.stringExpression().size() == 0) { - this.stack.push(this.script.composeStringConcatenation(Collections.emptyList())); - return; - } - - List list = new ArrayList<>(); - for (int i = 0; i < ctx.stringExpression().size(); i++) { - list.add(0, this.stack.pop(StringExpression.class)); - } - this.stack.push(this.script.composeStringConcatenation(list)); - } - - @Override - public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { - final StringExpression format = this.stack.pop(StringExpression.class); - final NumericExpression number = this.stack.pop(NumericExpression.class); - this.stack.push(this.script.composeNumberFormatting(number, format)); - } - - /*** Date functions ***/ - - @Override - public void exitDateFromStringFunction(DateFromStringFunctionContext ctx) { - this.stack.push(this.script.composeToDateConversion(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitDatePlusMeasureFunction(DatePlusMeasureFunctionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DateExpression left = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeAddition(left, right)); - } - - @Override - public void exitDateMinusMeasureFunction(DateMinusMeasureFunctionContext ctx) { - DurationExpression right = this.stack.pop(DurationExpression.class); - DateExpression left = this.stack.pop(DateExpression.class); - this.stack.push(this.script.composeSubtraction(left, right)); - } - - /*** Time functions ***/ - - @Override - public void exitTimeFromStringFunction(TimeFromStringFunctionContext ctx) { - this.stack.push(this.script.composeToTimeConversion(this.stack.pop(StringExpression.class))); - } - - /*** Duration Functions ***/ - - @Override - public void exitDayTimeDurationFromStringFunction(DayTimeDurationFromStringFunctionContext ctx) { - this.stack.push( - this.script.composeToDayTimeDurationConversion(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitYearMonthDurationFromStringFunction( - YearMonthDurationFromStringFunctionContext ctx) { - this.stack.push( - this.script.composeToYearMonthDurationConversion(this.stack.pop(StringExpression.class))); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java deleted file mode 100644 index 9c7690e6..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java +++ /dev/null @@ -1,532 +0,0 @@ -package eu.europa.ted.efx.sdk0.v7; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import org.antlr.v4.runtime.BaseErrorListener; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.ParseTreeWalker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -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.model.ContentBlock; -import eu.europa.ted.efx.model.ContentBlockStack; -import eu.europa.ted.efx.model.Context; -import eu.europa.ted.efx.model.Context.FieldContext; -import eu.europa.ted.efx.model.Context.NodeContext; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Markup; -import eu.europa.ted.efx.model.VariableList; -import eu.europa.ted.efx.sdk0.v7.EfxParser.AssetIdContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.AssetTypeContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ContextDeclarationBlockContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.LabelTemplateContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.LabelTypeContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ShorthandBtLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ShorthandContextFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ShorthandContextFieldValueReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ShorthandContextLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ShorthandFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ShorthandFieldValueLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.StandardExpressionBlockContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.StandardLabelReferenceContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.TemplateFileContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.TemplateLineContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.TextTemplateContext; -import eu.europa.ted.efx.sdk0.v7.EfxParser.ValueTemplateContext; - -/** - * The EfxTemplateTranslator extends the {@link EfxExpressionTranslator07} to provide additional - * translation capabilities for EFX templates. If has been implemented as an extension to the - * EfxExpressionTranslator in order to keep things simpler when one only needs to translate EFX - * expressions (like the condition associated with a business rule). - */ -@SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.EFX_TEMPLATE_TRANSLATOR) -public class EfxTemplateTranslator07 extends EfxExpressionTranslator07 - implements EfxTemplateTranslator { - - private static final Logger logger = LoggerFactory.getLogger(EfxTemplateTranslator07.class); - - private static final String INCONSISTENT_INDENTATION_SPACES = - "Inconsistent indentation. Expected a multiple of %d spaces."; - private static final String INDENTATION_LEVEL_SKIPPED = "Indentation level skipped."; - private static final String START_INDENT_AT_ZERO = - "Incorrect indentation. Please do not indent the first level in your template."; - private static final String MIXED_INDENTATION = - "Do not mix indentation methods. Stick with either tabs or spaces."; - private static final String UNEXPECTED_INDENTATION = "Unexpected indentation tracker state."; - - private static final String LABEL_TYPE_VALUE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.LABEL_TYPE_VALUE).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_INDICATOR = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_INDICATOR).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_BT = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_BT).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_FIELD = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_FIELD).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_CODE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_CODE).replaceAll("^'|'$", ""); - - - /** - * Used to control the indentation style used in a template - */ - private enum Indent { - TABS, SPACES, UNDETERMINED - } - - private Indent indentWith = Indent.UNDETERMINED; - private int indentSpaces = -1; - - /** - * The MarkupGenerator is called to retrieve markup in the target template language when needed. - */ - MarkupGenerator markup; - - final ContentBlock rootBlock = ContentBlock.newRootBlock(); - - /** - * The block stack is used to keep track of the indentation of template lines and adjust the EFX - * context accordingly. A block is a template line together with the template lines nested - * (through indentation) under it. At the top of the blockStack is the template block that is - * currently being processed. The next block in the stack is its parent block, and so on. - */ - ContentBlockStack blockStack = new ContentBlockStack(); - - @SuppressWarnings("unused") - private EfxTemplateTranslator07() { - super(); - } - - public EfxTemplateTranslator07(final MarkupGenerator markupGenerator, - final SymbolResolver symbolResolver, final ScriptGenerator scriptGenerator, - final BaseErrorListener errorListener) { - super(symbolResolver, scriptGenerator, errorListener); - - this.markup = markupGenerator; - } - - /** - * Opens the indicated EFX file and translates the EFX template it contains. - */ - @Override - public String renderTemplate(final Path pathname) throws IOException { - - return renderTemplate(CharStreams.fromPath(pathname)); - } - - /** - * Translates the template contained in the string passed as a parameter. - */ - @Override - public String renderTemplate(final String template) { - return renderTemplate(CharStreams.fromString(template)); - } - - @Override - public String renderTemplate(final InputStream stream) throws IOException { - return renderTemplate(CharStreams.fromStream(stream)); - } - - private String renderTemplate(final CharStream charStream) { - logger.info("Rendering template"); - - final EfxLexer lexer = new EfxLexer(charStream); - final CommonTokenStream tokens = new CommonTokenStream(lexer); - final EfxParser parser = new EfxParser(tokens); - - if (errorListener != null) { - lexer.removeErrorListeners(); - lexer.addErrorListener(errorListener); - parser.removeErrorListeners(); - parser.addErrorListener(errorListener); - } - - final ParseTree tree = parser.templateFile(); - - final ParseTreeWalker walker = new ParseTreeWalker(); - walker.walk(this, tree); - - logger.info("Finished rendering template"); - - return getTranslatedMarkup(); - } - - /** - * Gets the translated code after the walker finished its walk. Every line in the template has now - * been translated and a {@link Markup} object is in the stack for each block in the template. The - * output template is built from the end towards the top, because the items are removed from the - * stack in reverse order. - * - * @return The translated code, trimmed - */ - private String getTranslatedMarkup() { - logger.debug("Getting translated markup."); - - final StringBuilder sb = new StringBuilder(64); - while (!this.stack.empty()) { - sb.insert(0, '\n').insert(0, this.stack.pop(Markup.class).script); - } - - logger.debug("Finished getting translated markup."); - - return sb.toString().trim(); - } - - /*** Template File ***/ - - @Override - public void enterTemplateFile(TemplateFileContext ctx) { - assert blockStack.isEmpty() : UNEXPECTED_INDENTATION; - } - - @Override - public void exitTemplateFile(TemplateFileContext ctx) { - this.blockStack.pop(); - - List templateCalls = new ArrayList<>(); - List templates = new ArrayList<>(); - for (ContentBlock rootBlock : this.rootBlock.getChildren()) { - templateCalls.add(rootBlock.renderCallTemplate(markup)); - rootBlock.renderTemplate(markup, templates); - } - Markup file = this.markup.composeOutputFile(templateCalls, templates); - this.stack.push(file); - } - - /*** Source template blocks ***/ - - @Override - public void exitTextTemplate(TextTemplateContext ctx) { - Markup template = - ctx.templateFragment() != null ? this.stack.pop(Markup.class) : Markup.empty(); - String text = ctx.text() != null ? ctx.text().getText() : ""; - this.stack.push(this.markup.renderFreeText(text).join(template)); - } - - @Override - public void exitLabelTemplate(LabelTemplateContext ctx) { - 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 exitValueTemplate(ValueTemplateContext 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)); - } - - - /*** Label Blocks #{...} ***/ - - @Override - public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { - StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(assetType, this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), assetId)))); - } - - @Override - public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) { - StringExpression assetId = this.script.getStringLiteralFromUnquotedString(ctx.BtId().getText()); - StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - 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)))); - } - - @Override - public void exitShorthandFieldLabelReference(ShorthandFieldLabelReferenceContext ctx) { - final String fieldId = ctx.FieldId().getText(); - StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) - : this.script.getStringLiteralFromUnquotedString(""); - - if (labelType.script.equals("value")) { - this.shorthandFieldValueLabelReference(fieldId); - } else { - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), - this.script.getStringLiteralFromUnquotedString("|"), labelType, - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(fieldId))))); - } - } - - @Override - public void exitShorthandFieldValueLabelReference(ShorthandFieldValueLabelReferenceContext ctx) { - this.shorthandFieldValueLabelReference(ctx.FieldId().getText()); - } - - private void shorthandFieldValueLabelReference(final String fieldId) { - final Context currentContext = this.efxContext.peek(); - final StringExpression valueReference = this.script.composeFieldValueReference( - symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), - StringExpression.class); - final String fieldType = this.symbols.getTypeOfField(fieldId); - switch (fieldType) { - case "indicator": - this.stack - .push(this.markup.renderLabelFromExpression(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_VALUE), - this.script.getStringLiteralFromUnquotedString("-"), valueReference, - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(fieldId))))); - break; - case "code": - case "internal-code": - this.stack - .push(this.markup.renderLabelFromExpression(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_VALUE), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString( - this.symbols.getRootCodelistOfField(fieldId)), - this.script.getStringLiteralFromUnquotedString("."), valueReference)))); - break; - default: - throw new ParseCancellationException(String.format( - "Unexpected field type '%s'. Expected a field of either type 'code' or 'indicator'.", - fieldType)); - } - } - - /** - * Handles the #{labelType} shorthand syntax which renders the label of the Field or Node declared - * as the context. - * - * If the labelType is 'value', then the label depends on the field's value and is rendered - * according to the field's type. The assetType is inferred from the Field or Node declared as - * context. - */ - @Override - public void exitShorthandContextLabelReference(ShorthandContextLabelReferenceContext ctx) { - final String labelType = ctx.LabelType().getText(); - if (this.efxContext.isFieldContext()) { - if (labelType.equals(LABEL_TYPE_VALUE)) { - this.shorthandFieldValueLabelReference(this.efxContext.symbol()); - } else { - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_FIELD), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(labelType), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); - } - } else if (this.efxContext.isNodeContext()) { - this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( - List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_BT), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(labelType), - this.script.getStringLiteralFromUnquotedString("|"), - this.script.getStringLiteralFromUnquotedString(this.efxContext.symbol()))))); - } - } - - /** - * Handles the #value shorthand syntax which renders the label corresponding to the value of the - * the field declared as the context of the current line of the template. This shorthand syntax is - * only supported for fields of type 'code' or 'indicator'. - */ - @Override - public void exitShorthandContextFieldLabelReference( - ShorthandContextFieldLabelReferenceContext ctx) { - if (!this.efxContext.isFieldContext()) { - throw new ParseCancellationException( - "The #value shorthand syntax can only be used in a field is declared as context."); - } - this.shorthandFieldValueLabelReference(this.efxContext.symbol()); - } - - @Override - public void exitAssetType(AssetTypeContext ctx) { - if (ctx.expressionBlock() == null) { - this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); - } - } - - @Override - public void exitLabelType(LabelTypeContext ctx) { - if (ctx.expressionBlock() == null) { - this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); - } - } - - @Override - public void exitAssetId(AssetIdContext ctx) { - if (ctx.expressionBlock() == null) { - this.stack.push(this.script.getStringLiteralFromUnquotedString(ctx.getText())); - } - } - - /*** Expression Blocks ${...} ***/ - - /** - * Handles a standard expression block in a template line. Most of the work is done by the base - * class Sdk7EfxExpressionTranslator. After the expression is translated, the result is passed - * through the renderer. - */ - @Override - public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { - this.stack.push(this.stack.pop(Expression.class)); - } - - /*** - * Handles the $value shorthand syntax which renders the value of the field declared as context in - * the current line of the template. - */ - @Override - public void exitShorthandContextFieldValueReference( - ShorthandContextFieldValueReferenceContext ctx) { - if (!this.efxContext.isFieldContext()) { - throw new ParseCancellationException( - "The $value shorthand syntax can only be used when a field is declared as the context."); - } - this.stack.push(this.script.composeFieldValueReference( - symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.absolutePath()), - Expression.class)); - } - - /*** Context Declaration Blocks {...} ***/ - - /** - * This method changes the current EFX context. - * - * The EFX context is always assumed to be either a Field or a Node. Any predicate included in the - * EFX context declaration is not relevant and is ignored. - */ - @Override - public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { - - final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - if (filedId != null) { - this.efxContext.push(new FieldContext(filedId, this.stack.pop(PathExpression.class))); - } else { - final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); - assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; - this.efxContext.push(new NodeContext(nodeId, this.stack.pop(PathExpression.class))); - } - } - - - /*** Template lines ***/ - - @Override - public void exitTemplateLine(TemplateLineContext ctx) { - final VariableList variables = new VariableList(); // template variables not supported prior to - // EFX 2 - final Context lineContext = this.efxContext.pop(); - final int indentLevel = this.getIndentLevel(ctx); - final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); - final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); - final Integer outlineNumber = - ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; - assert this.stack.empty() : "Stack should be empty at this point."; - - if (indentChange > 1) { - throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); - } else if (indentChange == 1) { - if (this.blockStack.isEmpty()) { - throw new ParseCancellationException(START_INDENT_AT_ZERO); - } - this.blockStack.pushChild(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); - } else if (indentChange < 0) { - // lower indent level - for (int i = indentChange; i < 0; i++) { - assert !this.blockStack.isEmpty() : UNEXPECTED_INDENTATION; - assert this.blockStack.currentIndentationLevel() > indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pop(); - } - assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pushSibling(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); - } else if (indentChange == 0) { - - if (blockStack.isEmpty()) { - assert indentLevel == 0 : UNEXPECTED_INDENTATION; - this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, - this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); - } else { - this.blockStack.pushSibling(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); - } - } - } - - private Context relativizeContext(Context childContext, Context parentContext) { - if (parentContext == null) { - return childContext; - } - - if (FieldContext.class.isAssignableFrom(childContext.getClass())) { - return new FieldContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); - } - - assert NodeContext.class.isAssignableFrom( - childContext.getClass()) : "Child context should be either a FieldContext NodeContext."; - - return new NodeContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); - } - - /*** Helpers ***/ - - private int getIndentLevel(TemplateLineContext ctx) { - if (ctx.MixedIndent() != null) { - throw new ParseCancellationException(MIXED_INDENTATION); - } - - if (ctx.Spaces() != null) { - if (this.indentWith == Indent.UNDETERMINED) { - this.indentWith = Indent.SPACES; - this.indentSpaces = ctx.Spaces().getText().length(); - } else if (this.indentWith == Indent.TABS) { - throw new ParseCancellationException(MIXED_INDENTATION); - } - - if (ctx.Spaces().getText().length() % this.indentSpaces != 0) { - throw new ParseCancellationException( - String.format(INCONSISTENT_INDENTATION_SPACES, this.indentSpaces)); - } - return ctx.Spaces().getText().length() / this.indentSpaces; - } else if (ctx.Tabs() != null) { - if (this.indentWith == Indent.UNDETERMINED) { - this.indentWith = Indent.TABS; - } else if (this.indentWith == Indent.SPACES) { - throw new ParseCancellationException(MIXED_INDENTATION); - } - - return ctx.Tabs().getText().length(); - } - return 0; - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java deleted file mode 100644 index bcb1f5e3..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkCodelist07.java +++ /dev/null @@ -1,21 +0,0 @@ -package eu.europa.ted.efx.sdk0.v7.entity; - -import java.util.List; -import java.util.Optional; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.eforms.sdk.entity.SdkCodelist; - -/** - * Representation of an SdkCodelist for usage in the symbols map. - * - * @author rouschr - */ -@SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.CODELIST) -public class SdkCodelist07 extends SdkCodelist { - - public SdkCodelist07(String codelistId, String codelistVersion, List codes, - Optional parentId) { - super(codelistId, codelistVersion, codes, parentId); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkField07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkField07.java deleted file mode 100644 index 045f1989..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkField07.java +++ /dev/null @@ -1,19 +0,0 @@ -package eu.europa.ted.efx.sdk0.v7.entity; - -import com.fasterxml.jackson.databind.JsonNode; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.eforms.sdk.entity.SdkField; - -@SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.FIELD) -public class SdkField07 extends SdkField { - - public SdkField07(String id, String type, String parentNodeId, String xpathAbsolute, - String xpathRelative, String codelistId) { - super(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId); - } - - public SdkField07(JsonNode field) { - super(field); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java deleted file mode 100644 index 9b16f16b..00000000 --- a/src/main/java/eu/europa/ted/efx/sdk0/v7/entity/SdkNode07.java +++ /dev/null @@ -1,22 +0,0 @@ -package eu.europa.ted.efx.sdk0.v7.entity; - -import com.fasterxml.jackson.databind.JsonNode; -import eu.europa.ted.eforms.sdk.component.SdkComponent; -import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.eforms.sdk.entity.SdkNode; - -/** - * A node is something like a section. Nodes can be parents of other nodes or parents of fields. - */ -@SdkComponent(versions = {"0.7"}, componentType = SdkComponentType.NODE) -public class SdkNode07 extends SdkNode { - - public SdkNode07(String id, String parentId, String xpathAbsolute, String xpathRelative, - boolean repeatable) { - super(id, parentId, xpathAbsolute, xpathRelative, repeatable); - } - - public SdkNode07(JsonNode node) { - super(node); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java index 25302965..d75bb77d 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java @@ -10,7 +10,7 @@ import eu.europa.ted.efx.xpath.XPathContextualizer; import eu.europa.ted.efx.xpath.XPathScriptGenerator; -@SdkComponent(versions = {"0.6", "0.7", "1"}, componentType = SdkComponentType.SCRIPT_GENERATOR) +@SdkComponent(versions = {"1"}, componentType = SdkComponentType.SCRIPT_GENERATOR) public class XPathScriptGeneratorV1 extends XPathScriptGenerator { public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { From 57d9fcbc23fcc44b5e26f4250c365358c0113792 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Thu, 22 Jun 2023 04:12:24 +0200 Subject: [PATCH 47/57] Removed support of deprecated SDKs 0.6 and 0.7. --- .../eu/europa/ted/efx/interfaces/ScriptGenerator.java | 6 ------ .../eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 10 ---------- 2 files changed, 16 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 92e80167..14ba2821 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -329,16 +329,10 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, * Numeric Functions */ - @Deprecated(since = "0.7.0", forRemoval = true) - public NumericExpression composeCountOperation(final PathExpression set); - public NumericExpression composeCountOperation(final ListExpression list); public NumericExpression composeToNumberConversion(StringExpression text); - @Deprecated(since = "0.7.0", forRemoval = true) - public NumericExpression composeSumOperation(PathExpression set); - public NumericExpression composeSumOperation(NumericListExpression list); public NumericExpression composeStringLengthCalculation(StringExpression text); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index b98c85cd..e935cd9e 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -386,11 +386,6 @@ public BooleanExpression composeSequenceEqualFunction(ListExpression list) { return new NumericExpression("count(" + list.script + ")"); @@ -401,11 +396,6 @@ public NumericExpression composeToNumberConversion(StringExpression text) { return new NumericExpression("number(" + text.script + ")"); } - @Override - public NumericExpression composeSumOperation(PathExpression nodeSet) { - return new NumericExpression("sum(" + nodeSet.script + ")"); - } - @Override public NumericExpression composeSumOperation(NumericListExpression nodeSet) { return new NumericExpression("sum(" + nodeSet.script + ")"); From 2d43d81b8d7a1f5948ca3c318c74b64aa5cea32d Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Thu, 29 Jun 2023 03:05:04 +0200 Subject: [PATCH 48/57] Improved contextualisation of XPath steps with the same step-text but different predicates. --- .../ted/efx/xpath/XPathContextualizer.java | 43 +++++++++++++++++-- .../ted/efx/XPathContextualizerTest.java | 14 +++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java index 3c7bb102..f3279cab 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -189,9 +189,27 @@ private static PathExpression getContextualizedXpath(Queue contextQueu // At this point there are no more matching nodes in the two queues. + // We look at the first of the remaining steps in both queues and look if + // they are "similar" (meaning that they share the same step-text but but + // the path has different predicates). In this case we want to use a dot step + // with the predicate. + if (!contextQueue.isEmpty() && !pathQueue.isEmpty() && pathQueue.peek().isSimilarTo(contextQueue.peek())) { + contextQueue.poll(); // consume the same step from the contextQueue + if (contextQueue.isEmpty()) { + // Since there are no more steps in the contextQueue, the relative xpath should + // start with a dot step to provide a context for the predicate. + relativeXpath += "." + pathQueue.poll().getPredicateText(); + } else { + // Since there are more steps in the contextQueue which we will need to navigate back to, + // using back-steps, we will use a back-step to provide context of the predicate. + // This avoids an output that looks like ../.[predicate] which is valid but silly. + contextQueue.poll(); // consume the step from the contextQueue + relativeXpath += ".." + pathQueue.poll().getPredicateText(); + } + } + // We start building the resulting relativeXpath by appending any nodes - // remaining in the - // pathQueue. + // remaining in the pathQueue. while (!pathQueue.isEmpty()) { final StepInfo step = pathQueue.poll(); relativeXpath += "/" + step.stepText + step.getPredicateText(); @@ -356,7 +374,7 @@ public Boolean isTheSameAs(final StepInfo contextStep) { // If one of the two steps has more predicates that the other, if (this.predicates.size() != contextStep.predicates.size()) { - // then the steps are the same is the path has no predicates + // then the steps are the same if the path has no predicates // or all the predicates of the path are also found in the context. return this.predicates.isEmpty() || contextStep.predicates.containsAll(this.predicates); } @@ -381,6 +399,25 @@ public Boolean isTheSameAs(final StepInfo contextStep) { return pathPredicates.equals(contextPredicates); } + public Boolean isSimilarTo(final StepInfo contextStep) { + + // First check the step texts are the different. + if (!Objects.equals(contextStep.stepText, this.stepText)) { + return false; + } + + // If one of the two steps has more predicates that the other, + if (this.predicates.size() != contextStep.predicates.size()) { + // then the steps are the same if either of them has no predicates + // or all the predicates of the path are also found in the context. + return this.predicates.isEmpty() || contextStep.predicates.isEmpty() + || contextStep.predicates.containsAll(this.predicates); + } + + assert !this.isTheSameAs(contextStep) : "You should not be calling isSimilarTo() without first checking isTheSameAs()"; + return false; + } + public Boolean isPartOf(Interval interval) { return this.a >= interval.a && this.b <= interval.b; } diff --git a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java index 78197d5f..32cb8d96 100644 --- a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java @@ -77,6 +77,16 @@ void testPredicateInXpathLeaf() { assertEquals("../d[x/y = 'z']", contextualize("/a/b/c", "/a/b/d[x/y = 'z']")); } + @Test + void testPredicateBeingTheOnlyDifference() { + assertEquals(".[x/y = 'z']", contextualize("/a/b/c", "/a/b/c[x/y = 'z']")); + } + + @Test + void testPredicatesBeingTheOnlyDifferences() { + assertEquals("..[x/y = 'z']/c[x/y = 'z']", contextualize("/a/b/c", "/a/b[x/y = 'z']/c[x/y = 'z']")); + } + @Test void testPredicateInContextLeaf() { assertEquals("../d", contextualize("/a/b/c[e/f = 'z']", "/a/b/d")); @@ -89,7 +99,7 @@ void testPredicateInBothLeaf() { @Test void testPredicateInXpathMiddle() { - assertEquals("../../b[x/y = 'z']/d", contextualize("/a/b/c", "/a/b[x/y = 'z']/d")); + assertEquals("..[x/y = 'z']/d", contextualize("/a/b/c", "/a/b[x/y = 'z']/d")); } @Test @@ -109,7 +119,7 @@ void testPredicateDifferentOnSameElement() { @Test void testPredicateDifferent() { - assertEquals("../c[x = 'y']/d", contextualize("/a/b[e = 'f']/c", "/a/b/c[x = 'y']/d")); + assertEquals(".[x = 'y']/d", contextualize("/a/b[e = 'f']/c", "/a/b/c[x = 'y']/d")); } @Test From 76449a95d41160fb2f54253f4375fc07ed88943b Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Thu, 29 Jun 2023 11:51:47 +0200 Subject: [PATCH 49/57] XPathContextualizerTest: Make a test less ambiguous Make the 2 predicates different, just to make sure they are not mixed up. --- src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java index 32cb8d96..ebb0b5c7 100644 --- a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java @@ -84,7 +84,7 @@ void testPredicateBeingTheOnlyDifference() { @Test void testPredicatesBeingTheOnlyDifferences() { - assertEquals("..[x/y = 'z']/c[x/y = 'z']", contextualize("/a/b/c", "/a/b[x/y = 'z']/c[x/y = 'z']")); + assertEquals("..[u/v = 'w']/c[x/y = 'z']", contextualize("/a/b/c", "/a/b[u/v = 'w']/c[x/y = 'z']")); } @Test From 028ba01088d83cb44e2e3b9a4a7407e55aa7c2ca Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Thu, 29 Jun 2023 12:28:17 +0200 Subject: [PATCH 50/57] XPathContextualizerTest: Add another test for predicates --- src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java index ebb0b5c7..19f92e9c 100644 --- a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java @@ -82,6 +82,11 @@ void testPredicateBeingTheOnlyDifference() { assertEquals(".[x/y = 'z']", contextualize("/a/b/c", "/a/b/c[x/y = 'z']")); } + @Test + void testPredicateInContextBeingTheOnlyDifference() { + assertEquals(".", contextualize("/a/b/c[e/f = 'z']", "/a/b/c")); + } + @Test void testPredicatesBeingTheOnlyDifferences() { assertEquals("..[u/v = 'w']/c[x/y = 'z']", contextualize("/a/b/c", "/a/b[u/v = 'w']/c[x/y = 'z']")); From 3e9467250314ff91b59e51351fee4816014f764a Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Mon, 24 Jul 2023 02:25:01 +0200 Subject: [PATCH 51/57] Refactored EFX type management. --- .../ted/eforms/sdk/SdkSymbolResolver.java | 84 ++- .../ted/efx/interfaces/MarkupGenerator.java | 12 +- .../ted/efx/interfaces/ScriptGenerator.java | 137 ++-- .../ted/efx/interfaces/SymbolResolver.java | 33 +- .../europa/ted/efx/model/BooleanVariable.java | 11 - .../eu/europa/ted/efx/model/CallStack.java | 236 +++---- .../europa/ted/efx/model/CallStackObject.java | 2 +- .../java/eu/europa/ted/efx/model/Context.java | 15 +- .../eu/europa/ted/efx/model/ContextStack.java | 3 +- .../eu/europa/ted/efx/model/DateVariable.java | 12 - .../ted/efx/model/DurationVariable.java | 12 - .../eu/europa/ted/efx/model/Expression.java | 324 --------- .../eu/europa/ted/efx/model/Identifier.java | 11 - .../europa/ted/efx/model/NumericVariable.java | 11 - .../europa/ted/efx/model/StringVariable.java | 11 - .../eu/europa/ted/efx/model/TimeVariable.java | 11 - .../eu/europa/ted/efx/model/Variable.java | 16 - .../eu/europa/ted/efx/model/VariableList.java | 29 - .../ted/efx/model/expressions/Expression.java | 85 +++ .../model/expressions/TypedExpression.java | 110 +++ .../iteration/IteratorExpression.java | 13 + .../iteration/IteratorListExpression.java | 13 + .../path/BooleanPathExpression.java | 12 + .../expressions/path/DatePathExpression.java | 12 + .../path/DurationPathExpression.java | 12 + .../MultilingualStringPathExpression.java | 12 + .../expressions/path/NodePathExpression.java | 12 + .../path/NumericPathExpression.java | 12 + .../expressions/path/PathExpression.java | 126 ++++ .../path/StringPathExpression.java | 16 + .../expressions/path/TimePathExpression.java | 12 + .../expressions/scalar/BooleanExpression.java | 23 + .../expressions/scalar/DateExpression.java | 23 + .../scalar/DurationExpression.java | 23 + .../scalar/MultilingualStringExpression.java | 16 + .../expressions/scalar/NumericExpression.java | 23 + .../expressions/scalar/ScalarExpression.java | 100 +++ .../expressions/scalar/StringExpression.java | 27 + .../expressions/scalar/TimeExpression.java | 23 + .../sequence/BooleanSequenceExpression.java | 19 + .../sequence/DateSequenceExpression.java | 19 + .../sequence/DurationSequenceExpression.java | 19 + .../MultilingualStringSequenceExpression.java | 12 + .../sequence/NumericSequenceExpression.java | 19 + .../sequence/SequenceExpression.java | 98 +++ .../sequence/StringSequenceExpression.java | 23 + .../sequence/TimeSequenceExpression.java | 18 + .../model/{ => templates}/ContentBlock.java | 21 +- .../{ => templates}/ContentBlockStack.java | 5 +- .../ted/efx/model/{ => templates}/Markup.java | 6 +- .../ted/efx/model/types/EfxDataType.java | 14 + .../model/types/EfxDataTypeAssociation.java | 14 + .../efx/model/types/EfxExpressionType.java | 7 + .../types/EfxExpressionTypeAssociation.java | 14 + .../ted/efx/model/types/FieldTypes.java | 47 ++ .../ted/efx/model/variables/Identifier.java | 20 + .../ted/efx/model/variables/Parameter.java | 14 + .../ted/efx/model/variables/Variable.java | 20 + .../ted/efx/model/variables/VariableList.java | 11 + .../efx/sdk1/EfxExpressionTranslatorV1.java | 649 +++++++++--------- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 189 ++--- .../sdk1/xpath/XPathScriptGeneratorV1.java | 14 +- .../efx/sdk2/EfxExpressionTranslatorV2.java | 606 ++++++++-------- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 278 ++++---- .../ted/efx/xpath/XPathAttributeLocator.java | 24 +- .../ted/efx/xpath/XPathContextualizer.java | 42 +- .../ted/efx/xpath/XPathScriptGenerator.java | 282 ++++---- .../ted/efx/XPathAttributeLocatorTest.java | 11 +- .../ted/efx/XPathContextualizerTest.java | 6 +- .../efx/mock/AbstractSymbolResolverMock.java | 8 +- .../ted/efx/mock/MarkupGeneratorMock.java | 28 +- .../efx/mock/sdk1/SymbolResolverMockV1.java | 19 + .../efx/mock/sdk2/SymbolResolverMockV2.java | 19 + .../sdk1/EfxExpressionTranslatorV1Test.java | 2 +- .../efx/sdk1/EfxTemplateTranslatorV1Test.java | 10 +- .../sdk2/EfxExpressionTranslatorV2Test.java | 2 +- .../efx/sdk2/EfxTemplateTranslatorV2Test.java | 10 +- 77 files changed, 2555 insertions(+), 1739 deletions(-) delete mode 100644 src/main/java/eu/europa/ted/efx/model/BooleanVariable.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/DateVariable.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/DurationVariable.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/Expression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/Identifier.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/NumericVariable.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/StringVariable.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/TimeVariable.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/Variable.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/VariableList.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/Expression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorListExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/BooleanPathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/DatePathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/DurationPathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/MultilingualStringPathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/NumericPathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/PathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/StringPathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/TimePathExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequenceExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceExpression.java rename src/main/java/eu/europa/ted/efx/model/{ => templates}/ContentBlock.java (89%) rename src/main/java/eu/europa/ted/efx/model/{ => templates}/ContentBlockStack.java (94%) rename src/main/java/eu/europa/ted/efx/model/{ => templates}/Markup.java (80%) create mode 100644 src/main/java/eu/europa/ted/efx/model/types/EfxDataType.java create mode 100644 src/main/java/eu/europa/ted/efx/model/types/EfxDataTypeAssociation.java create mode 100644 src/main/java/eu/europa/ted/efx/model/types/EfxExpressionType.java create mode 100644 src/main/java/eu/europa/ted/efx/model/types/EfxExpressionTypeAssociation.java create mode 100644 src/main/java/eu/europa/ted/efx/model/types/FieldTypes.java create mode 100644 src/main/java/eu/europa/ted/efx/model/variables/Identifier.java create mode 100644 src/main/java/eu/europa/ted/efx/model/variables/Parameter.java create mode 100644 src/main/java/eu/europa/ted/efx/model/variables/Variable.java create mode 100644 src/main/java/eu/europa/ted/efx/model/variables/VariableList.java diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java index 304b1c5b..2f49cb93 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -1,9 +1,12 @@ package eu.europa.ted.eforms.sdk; import java.nio.file.Path; +import java.util.HashMap; import java.util.List; import java.util.Map; + import org.antlr.v4.runtime.misc.ParseCancellationException; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.eforms.sdk.entity.SdkCodelist; @@ -14,11 +17,13 @@ import eu.europa.ted.eforms.sdk.repository.SdkNodeRepository; import eu.europa.ted.eforms.sdk.resource.SdkResourceLoader; import eu.europa.ted.efx.interfaces.SymbolResolver; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.expressions.path.NodePathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.types.FieldTypes; +import eu.europa.ted.efx.xpath.XPathAttributeLocator; import eu.europa.ted.efx.xpath.XPathContextualizer; -@SdkComponent(versions = {"1", "2"}, - componentType = SdkComponentType.SYMBOL_RESOLVER) +@SdkComponent(versions = { "1", "2" }, componentType = SdkComponentType.SYMBOL_RESOLVER) public class SdkSymbolResolver implements SymbolResolver { protected Map fieldById; @@ -27,11 +32,13 @@ public class SdkSymbolResolver implements SymbolResolver { protected Map codelistById; /** - * Builds EFX list from the passed codelist reference. This will lazily compute and cache the + * Builds EFX list from the passed codelist reference. This will lazily compute + * and cache the * result for reuse as the operation can be costly on some large lists. * * @param codelistId A reference to an SDK codelist. - * @return The EFX string representation of the list of all the codes of the referenced codelist. + * @return The EFX string representation of the list of all the codes of the + * referenced codelist. */ @Override public final List expandCodelist(final String codelistId) { @@ -45,7 +52,7 @@ public final List expandCodelist(final String codelistId) { /** * Private, use getInstance method instead. * - * @param sdkVersion The version of the SDK. + * @param sdkVersion The version of the SDK. * @param sdkRootPath The path to the root of the SDK. * @throws InstantiationException If the SDK version is not supported. */ @@ -92,7 +99,7 @@ public PathExpression getAbsolutePathOfField(final String fieldId) { throw new ParseCancellationException( String.format("Unknown field identifier '%s'.", fieldId)); } - return new PathExpression(sdkField.getXpathAbsolute()); + return PathExpression.instantiate(sdkField.getXpathAbsolute(), FieldTypes.fromString(sdkField.getType())); } /** @@ -105,13 +112,14 @@ public PathExpression getAbsolutePathOfNode(final String nodeId) { if (sdkNode == null) { throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); } - return new PathExpression(sdkNode.getXpathAbsolute()); + return new NodePathExpression(sdkNode.getXpathAbsolute()); } /** * Gets the xPath of the given field relative to the given context. * - * @param fieldId The id of the field for which we want to find the relative xPath. + * @param fieldId The id of the field for which we want to find the relative + * xPath. * @param contextPath xPath indicating the context. * @return The xPath of the given field relative to the given context. */ @@ -124,7 +132,8 @@ public PathExpression getRelativePathOfField(String fieldId, PathExpression cont /** * Gets the xPath of the given node relative to the given context. * - * @param nodeId The id of the node for which we want to find the relative xPath. + * @param nodeId The id of the node for which we want to find the relative + * xPath. * @param contextPath XPath indicating the context. * @return The XPath of the given node relative to the given context. */ @@ -166,4 +175,59 @@ public String getRootCodelistOfField(final String fieldId) { return sdkCodelist.getRootCodelistId(); } + + @Override + public Boolean isAttributeField(final String fieldId) { + if (!additionalFieldInfoMap.containsKey(fieldId)) { + this.cacheAdditionalFieldInfo(fieldId); + } + return additionalFieldInfoMap.get(fieldId).isAttribute; + } + + @Override + public String getAttributeOfField(final String fieldId) { + if (!additionalFieldInfoMap.containsKey(fieldId)) { + this.cacheAdditionalFieldInfo(fieldId); + } + return additionalFieldInfoMap.get(fieldId).attributeName; + } + + @Override + public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(final String fieldId) { + if (!additionalFieldInfoMap.containsKey(fieldId)) { + this.cacheAdditionalFieldInfo(fieldId); + } + return additionalFieldInfoMap.get(fieldId).pathWithoutAttribute; + } + + // #region Temporary helpers ------------------------------------------------ + + /** + * Temporary workaround to store additional info about fields. + * + * TODO: Move this additional info to SdkField class, and move the XPathAttributeLocator to the eforms-core-library. + */ + class AdditionalFieldInfo { + public Boolean isAttribute; + public String attributeName; + public PathExpression pathWithoutAttribute; + } + + /** + * Caches the results of xpath parsing to mitigate performance impact. + * This is a temporary solution until we move the additional info to the SdkField class. + */ + Map additionalFieldInfoMap = new HashMap<>(); + + private void cacheAdditionalFieldInfo(final String fieldId) { + if (additionalFieldInfoMap.containsKey(fieldId)) { + return; + } + var parsedPath = XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)); + var info = new AdditionalFieldInfo(); + info.isAttribute = parsedPath.hasAttribute(); + info.attributeName = parsedPath.getAttribute(); + info.pathWithoutAttribute = parsedPath.getPath(); + additionalFieldInfoMap.put(fieldId, info); + } } 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 757f1c13..af5dafd8 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -15,12 +15,14 @@ import java.util.List; import java.util.Set; + import org.apache.commons.lang3.tuple.Pair; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Markup; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.templates.Markup; /** * The role of this interface is to allow the reuse of the Sdk6EfxTemplateTranslator to generate diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 14ba2821..b74fc3be 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -14,19 +14,22 @@ package eu.europa.ted.efx.interfaces; import java.util.List; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.BooleanExpression; -import eu.europa.ted.efx.model.Expression.DateExpression; -import eu.europa.ted.efx.model.Expression.DurationExpression; -import eu.europa.ted.efx.model.Expression.IteratorExpression; -import eu.europa.ted.efx.model.Expression.IteratorListExpression; -import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.NumericListExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Expression.StringListExpression; -import eu.europa.ted.efx.model.Expression.TimeExpression; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorListExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.BooleanExpression; +import eu.europa.ted.efx.model.expressions.scalar.DateExpression; +import eu.europa.ted.efx.model.expressions.scalar.DurationExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.TimeExpression; +import eu.europa.ted.efx.model.expressions.sequence.NumericSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.SequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.StringSequenceExpression; /** * A ScriptGenerator is used by the EFX expression translator to translate specific computations to @@ -49,14 +52,12 @@ public interface ScriptGenerator { * difference between fields and nodes is that fields contain values, while nodes contain other * nodes and/or fields. * - * @param The type of the returned Expression. * @param nodeReference The PathExpression that points to the node. * @param predicate The predicate that should be used to match the subset of nodes. - * @param type The type of the returned Expression. * @return The target language script that matches the subset of nodes. */ - public T composeNodeReferenceWithPredicate( - final PathExpression nodeReference, final BooleanExpression predicate, Class type); + public PathExpression composeNodeReferenceWithPredicate( + final PathExpression nodeReference, final BooleanExpression predicate); /** * Given a PathExpression and a predicate, this method should return the target language script @@ -66,29 +67,24 @@ public T composeNodeReferenceWithPredicate( * difference between fields and nodes is that fields contain values, while nodes contain other * nodes and/or fields. * - * @param The type of the returned Expression. * @param fieldReference The PathExpression that points to the field. * @param predicate The predicate that should be used to match the subset of fields. - * @param type The type of the returned Expression. * @return The target language script that matches the subset of fields. */ - public T composeFieldReferenceWithPredicate( - final PathExpression fieldReference, final BooleanExpression predicate, Class type); + public PathExpression composeFieldReferenceWithPredicate( + final PathExpression fieldReference, final BooleanExpression predicate); - public T composeFieldReferenceWithAxis(final PathExpression fieldReference, - final String axis, Class type); + public PathExpression composeFieldReferenceWithAxis(final PathExpression fieldReference, + final String axis); /** * Given a PathExpression, this method should return the target language script for retrieving the * value of the field. * - * @param The type of the returned Expression. * @param fieldReference The PathExpression that points to the field. - * @param type The type of the returned Expression. * @return The target language script that retrieves the value of the field. */ - public T composeFieldValueReference(final PathExpression fieldReference, - Class type); + public PathExpression composeFieldValueReference(final PathExpression fieldReference); /** * Given a PathExpression and an attribute name, this method should return the target language @@ -100,7 +96,7 @@ public T composeFieldValueReference(final PathExpression * @param type The type of the returned Expression. * @return The target language script that retrieves the value of the attribute. */ - public T composeFieldAttributeReference( + public T composeFieldAttributeReference( final PathExpression fieldReference, String attribute, Class type); /** @@ -112,24 +108,23 @@ public T composeFieldAttributeReference( * @param type The type of the returned Expression. * @return The target language script that dereferences the variable. */ - public T composeVariableReference(String variableName, Class type); + public T composeVariableReference(String variableName, Class type); - public T composeVariableDeclaration(String variableName, Class type); + public T composeVariableDeclaration(String variableName, Class type); - public T composeParameterDeclaration(String parameterName, Class type); + public T composeParameterDeclaration(String parameterName, Class type); /** - * Takes a list of string expressions and returns the target language script that corresponds to a - * list of string expressions. + * Takes a list of expressions and returns the target language script that corresponds to a + * list of expressions. * - * @param The type of the returned Expression. - * @param The type of the returned ListExpression. - * @param list The list of string expressions. - * @param type The type of the returned Expression. - * @return The target language script that corresponds to a list of string expressions. + * @param The type of the returned {@link SequenceExpression}. + * @param list The list of {@link ScalarExpression}. + * @param type The type of the returned {@link SequenceExpression}. + * @return The target language script that corresponds to a list of expressions. */ - public > L composeList(List list, - Class type); + public T composeList(List list, + Class type); /** * Takes a Java Boolean value and returns the corresponding target language script. @@ -174,14 +169,12 @@ public BooleanExpression composeLogicalOr(final BooleanExpression leftOperand, * Returns the target language script that checks whether a given list of values (haystack) * contains a given value (needle). * - * @param The type of the returned Expression. - * @param The type of the returned ListExpression. * @param needle The value to be searched for. * @param haystack The list of values to be searched. * @return The target language script that checks whether a given list of values (haystack) */ - public > BooleanExpression composeContainsCondition( - final T needle, final L haystack); + public BooleanExpression composeContainsCondition( + final ScalarExpression needle, final SequenceExpression haystack); /** * Returns the target language script that checks whether a given string matches the given RegEx @@ -206,34 +199,30 @@ public BooleanExpression composePatternMatchCondition(final StringExpression exp @Deprecated(since = "1.0.0", forRemoval = true) - public BooleanExpression composeAllSatisfy(ListExpression list, + public BooleanExpression composeAllSatisfy(SequenceExpression list, String variableName, BooleanExpression booleanExpression); - public BooleanExpression composeAllSatisfy( + public BooleanExpression composeAllSatisfy( IteratorListExpression iterators, BooleanExpression booleanExpression); @Deprecated(since = "1.0.0", forRemoval = true) - public BooleanExpression composeAnySatisfies(ListExpression list, + public BooleanExpression composeAnySatisfies(SequenceExpression list, String variableName, BooleanExpression booleanExpression); - public BooleanExpression composeAnySatisfies( + public BooleanExpression composeAnySatisfies( IteratorListExpression iterators, BooleanExpression booleanExpression); - public T composeConditionalExpression(BooleanExpression condition, + public T composeConditionalExpression(BooleanExpression condition, T whenTrue, T whenFalse, Class type); @Deprecated(since = "1.0.0", forRemoval = true) - public , T2 extends Expression, L2 extends ListExpression> L2 composeForExpression( - String variableName, L1 sourceList, T2 expression, Class targetListType); - - public > L2 composeForExpression( - IteratorListExpression iterators, T2 expression, Class targetListType); + public T2 composeForExpression( + String variableName, T1 sourceList, ScalarExpression expression, Class targetListType); - public > IteratorExpression composeIteratorExpression( - String variableName, L sourceList); + public T composeForExpression( + IteratorListExpression iterators, ScalarExpression expression, Class targetListType); - public IteratorExpression composeIteratorExpression( - String variableName, PathExpression pathExpression); + public IteratorExpression composeIteratorExpression(Expression variableDeclarationExpression, SequenceExpression sourceList); public IteratorListExpression composeIteratorList(List iterators); @@ -286,8 +275,8 @@ public PathExpression composeFieldInExternalReference(final PathExpression exter * @param rightOperand The right operand of the comparison. * @return The target language script that performs the comparison. */ - public BooleanExpression composeComparisonOperation(Expression leftOperand, String operator, - Expression rightOperand); + public BooleanExpression composeComparisonOperation(ScalarExpression leftOperand, String operator, + ScalarExpression rightOperand); /** * Given a numeric operation, this method should return the target language script that performs @@ -329,11 +318,11 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, * Numeric Functions */ - public NumericExpression composeCountOperation(final ListExpression list); + public NumericExpression composeCountOperation(final SequenceExpression list); public NumericExpression composeToNumberConversion(StringExpression text); - public NumericExpression composeSumOperation(NumericListExpression list); + public NumericExpression composeSumOperation(NumericSequenceExpression list); public NumericExpression composeStringLengthCalculation(StringExpression text); @@ -343,7 +332,7 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public StringExpression composeStringConcatenation(List list); - public StringExpression composeStringJoin(StringListExpression list, StringExpression separator); + public StringExpression composeStringJoin(StringSequenceExpression list, StringExpression separator); public BooleanExpression composeEndsWithCondition(StringExpression text, StringExpression endsWith); @@ -424,8 +413,8 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public BooleanExpression composeUniqueValueCondition(PathExpression needle, PathExpression haystack); - public BooleanExpression composeSequenceEqualFunction(ListExpression one, - ListExpression two); + public BooleanExpression composeSequenceEqualFunction(SequenceExpression one, + SequenceExpression two); /* * Date Functions @@ -473,18 +462,18 @@ public DurationExpression composeSubtraction(final DurationExpression left, * Sequence Functions */ - public > L composeDistinctValuesFunction( - L list, Class listType); + public T composeDistinctValuesFunction( + T list, Class listType); - public > L composeUnionFunction(L listOne, - L listTwo, Class listType); + public T composeUnionFunction(T listOne, + T listTwo, Class listType); - public > L composeIntersectFunction(L listOne, - L listTwo, Class listType); + public T composeIntersectFunction(T listOne, + T listTwo, Class listType); - public > L composeExceptFunction(L listOne, - L listTwo, Class listType); + public T composeExceptFunction(T listOne, + T listTwo, Class listType); - public > T composeIndexer(L list, + public T composeIndexer(SequenceExpression list, NumericExpression index, Class type); } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java b/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java index c4310e04..36391408 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java @@ -14,7 +14,8 @@ package eu.europa.ted.efx.interfaces; import java.util.List; -import eu.europa.ted.efx.model.Expression.PathExpression; + +import eu.europa.ted.efx.model.expressions.path.PathExpression; /** * A SymbolResolver is a mechanism used by EFX translators to resolve symbols. @@ -111,6 +112,36 @@ public PathExpression getRelativePathOfNode(final String nodeId, */ public String getRootCodelistOfField(final String fieldId); + /** + * Gets a boolean value indicating whether or not the given field points to + * an @attribute. + * + * @param fieldId The identifier of the field to look for. + * @return True if the field points to an @attribute, false otherwise. + */ + public Boolean isAttributeField(final String fieldId); + + /** + * Gets the attribute name of the given field (if the field points to + * an @attribute). + * + * @param fieldId The identifier of the field to look for. + * @return The attribute name of the given field, without the @ prefix, or an + * empty string if the field does not point to an @attribute. + */ + public String getAttributeOfField(final String fieldId); + + /** + * Gets the absolute path of the given field, without the attribute part. + * This method is meant to be used with fields that point to an @attribute. + * If the given field does not point to an @attribute then this method returns + * the same as {@link #getAbsolutePathOfField} + * + * @param fieldId The identifier of the field to look for. + * @return The absolute path of the given field, without the attribute part. + */ + public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(final String fieldId); + /** * Gets the list of all codes in a given codelist as a list of strings. * diff --git a/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java b/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java deleted file mode 100644 index 22c64247..00000000 --- a/src/main/java/eu/europa/ted/efx/model/BooleanVariable.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.europa.ted.efx.model; - -import eu.europa.ted.efx.model.Expression.BooleanExpression; - -public class BooleanVariable extends Variable { - - public BooleanVariable(String variableName, BooleanExpression initializationExpression, - BooleanExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java index ead839e7..c5359a84 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -4,10 +4,18 @@ import java.util.Map; import java.util.Optional; import java.util.Stack; + import org.antlr.v4.runtime.misc.ParseCancellationException; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.variables.Identifier; +import eu.europa.ted.efx.model.variables.Parameter; + /** - * The call stack is a stack of stack frames. Each stack frame represents a scope. The top of the + * The call stack is a stack of stack frames. Each stack frame represents a + * scope. The top of the * stack is the current scope. The bottom of the stack is the global scope. */ public class CallStack { @@ -15,83 +23,59 @@ public class CallStack { private static final String TYPE_MISMATCH = "Type mismatch. Expected %s instead of %s."; private static final String UNDECLARED_IDENTIFIER = "Identifier not declared: "; private static final String IDENTIFIER_ALREADY_DECLARED = "Identifier already declared: "; - private static final String STACK_UNDERFLOW = - "Stack underflow. Return values were available in the dropped frame, but no stack frame is left to consume them."; + private static final String STACK_UNDERFLOW = "Stack underflow. Return values were available in the dropped frame, but no stack frame is left to consume them."; /** - * Stack frames are means of controlling the scope of variables and parameters. Certain - * sub-expressions are scoped, meaning that variables and parameters are only available within the + * Stack frames are means of controlling the scope of variables and parameters. + * Certain + * sub-expressions are scoped, meaning that variables and parameters are only + * available within the * scope of the sub-expression. */ class StackFrame extends Stack { /** - * Keeps a list of all identifiers declared in the current scope as well as their type. - */ - Map> typeRegister = - new HashMap>(); - - /** - * Keeps a list of all parameter values declared in the current scope. - */ - Map valueRegister = new HashMap(); - - /** - * Registers a parameter identifier and pushes a parameter declaration on the current stack - * frame. Also stores the parameter value. - * @param parameterName The name of the parameter. - * @param parameterDeclarationExpression The expression used to declare the parameter. - * @param parameterValue The value passed to the parameter. + * Keeps a list of all identifiers declared in the current scope as well as + * their type. */ - void pushParameterDeclaration(String parameterName, Expression parameterDeclarationExpression, - Expression parameterValue) { - this.declareIdentifier(parameterName, parameterDeclarationExpression.getClass()); - this.storeValue(parameterName, parameterValue); - this.push(parameterDeclarationExpression); - } + Map identifierRegistry = new HashMap(); /** - * Registers a variable identifier and pushes a variable declaration on the current stack frame. - * @param variableName The name of the variable. - * @param variableDeclarationExpression The expression used to declare the variable. - */ - void pushVariableDeclaration(String variableName, Expression variableDeclarationExpression) { - this.declareIdentifier(variableName, variableDeclarationExpression.getClass()); - this.push(variableDeclarationExpression); - } - - /** - * Registers an identifier in the current scope. This registration is later used to check if an + * Registers an identifier in the current scope. This registration is later used + * to check if an * identifier is declared in the current scope. + * * @param identifier The identifier to register. - * @param type The type of the identifier. - */ - void declareIdentifier(String identifier, Class type) { - this.typeRegister.put(identifier, type); - } - - /** - * Used to store parameter values. - * @param identifier The identifier of the parameter. - * @param value The value of the parameter. + * @param dataType The type of the identifier. */ - void storeValue(String identifier, Expression value) { - this.valueRegister.put(identifier, value); + void declareIdentifier(Identifier identifier) { + this.identifierRegistry.put(identifier.name, identifier); } /** - * Returns the object at the top of the stack and removes it from the stack. The object must be + * Returns the object at the top of the stack and removes it from the stack. The + * object must be * of the expected type. + * * @param expectedType The type that the returned object is expected to have. * @return The object removed from the top of the stack. */ synchronized T pop(Class expectedType) { Class actualType = this.peek().getClass(); - if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) { - throw new ParseCancellationException(String.format(TYPE_MISMATCH, - expectedType.getSimpleName(), actualType.getSimpleName())); + if (expectedType.isAssignableFrom(actualType)) { + return expectedType.cast(this.pop()); } - return expectedType.cast(this.pop()); + + if (TypedExpression.class.isAssignableFrom(actualType) && TypedExpression.class.isAssignableFrom(expectedType)) { + var actual = actualType.asSubclass(TypedExpression.class); + var expected = expectedType.asSubclass(TypedExpression.class); + if (TypedExpression.canConvert(actual, expected)) { + return expectedType.cast(TypedExpression.from((TypedExpression) this.pop(), expected)); + } + } + + throw new ParseCancellationException( + String.format(TYPE_MISMATCH, expectedType.getSimpleName(), actualType.getSimpleName())); } /** @@ -100,8 +84,7 @@ synchronized T pop(Class expectedType) { @Override public void clear() { super.clear(); - this.typeRegister.clear(); - this.valueRegister.clear(); + this.identifierRegistry.clear(); } } @@ -121,7 +104,8 @@ public CallStack() { /** * Creates a new stack frame and pushes it on top of the call stack. * - * This method is called at the begin boundary of scoped sub-expression to allow for the + * This method is called at the begin boundary of scoped sub-expression to allow + * for the * declaration of local variables. */ public void pushStackFrame() { @@ -129,10 +113,13 @@ public void pushStackFrame() { } /** - * Drops the current stack frame and passes the return values to the previous stack frame. + * Drops the current stack frame and passes the return values to the previous + * stack frame. * - * This method is called at the end boundary of scoped sub-expressions. Variables local to the - * sub-expression must go out of scope and the return values are passed to the parent expression. + * This method is called at the end boundary of scoped sub-expressions. + * Variables local to the + * sub-expression must go out of scope and the return values are passed to the + * parent expression. */ public void popStackFrame() { StackFrame droppedFrame = this.frames.pop(); @@ -148,54 +135,15 @@ public void popStackFrame() { } /** - * Pushes a parameter declaration on the current stack frame. Checks if another identifier with - * the same name is already declared in the current scope. + * Declares an identifier. * - * @param parameterName The name of the parameter. - * @param parameterDeclaration The expression used to declare the parameter. - * @param parameterValue The value passed to the parameter. - * @throws ParseCancellationException if another identifier with the same name is already - * declared in the current scope. + * @param identifier The identifier to declare. */ - public void pushParameterDeclaration(String parameterName, Expression parameterDeclaration, - Expression parameterValue) { - if (this.inScope(parameterName)) { - throw new ParseCancellationException( - IDENTIFIER_ALREADY_DECLARED + parameterDeclaration.script); + public void declareIdentifier(Identifier identifier) { + if (this.inScope(identifier.name)) { + throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + identifier.name); } - this.frames.peek().pushParameterDeclaration(parameterName, parameterDeclaration, - parameterValue); - } - - /** - * Pushes a variable declaration on the current stack frame. Checks if another identifier with the - * same name is already declared in the current scope. - * - * @param variableName The name of the variable. - * @param variableDeclaration The expression used to declare the variable. - */ - public void pushVariableDeclaration(String variableName, Expression variableDeclaration) { - if (this.inScope(variableName)) { - throw new ParseCancellationException( - IDENTIFIER_ALREADY_DECLARED + variableDeclaration.script); - } - this.frames.peek().pushVariableDeclaration(variableName, variableDeclaration); - } - - /** - * Declares a template variable. Template variables are tracked to ensure proper scoping. However, - * their declaration is not pushed on the stack as they are declared at the template level (in - * Markup) and not at the expression level (not in the target language script). - * - * @param variableName The name of the variable. - * @param variableType The type of the variable. - */ - public void declareTemplateVariable(String variableName, - Class variableType) { - if (this.inScope(variableName)) { - throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + variableName); - } - this.frames.peek().declareIdentifier(variableName, variableType); + this.frames.peek().declareIdentifier(identifier); } /** @@ -206,31 +154,35 @@ public void declareTemplateVariable(String variableName, */ boolean inScope(String identifier) { return this.frames.stream().anyMatch( - f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)); + f -> f.identifierRegistry.containsKey(identifier)); } /** * Returns the stack frame containing the given identifier. * * @param identifier The identifier to look for. - * @return The stack frame containing the given identifier or null if no such stack frame exists. + * @return The stack frame containing the given identifier or null if no such + * stack frame exists. */ StackFrame findFrameContaining(String identifier) { return this.frames.stream() .filter( - f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)) + f -> f.identifierRegistry.containsKey(identifier)) .findFirst().orElse(null); } /** * Gets the value of a parameter. * - * @param identifier The identifier of the parameter. + * @param parameterName The identifier of the parameter. * @return The value of the parameter. */ - Optional getParameter(String identifier) { - return this.frames.stream().filter(f -> f.valueRegister.containsKey(identifier)).findFirst() - .map(x -> x.valueRegister.get(identifier)); + Optional getParameter(String parameterName) { + return this.frames.stream() + .filter(f -> f.identifierRegistry.containsKey(parameterName) + && Parameter.class.isAssignableFrom(f.identifierRegistry.get(parameterName).getClass())) + .findFirst() + .map(x -> ((Parameter) x.identifierRegistry.get(parameterName)).parameterValue); } /** @@ -239,53 +191,43 @@ Optional getParameter(String identifier) { * @param identifier The identifier of the variable. * @return The type of the variable. */ - Optional> getVariable(String identifier) { - return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier)).findFirst() - .map(x -> x.typeRegister.get(identifier)); + Optional getIdentifier(String identifier) { + return this.frames.stream().filter(f -> f.identifierRegistry.containsKey(identifier)).findFirst() + .map(x -> x.identifierRegistry.get(identifier)); } /** * Gets the type of a variable. * - * @param identifier The identifier of the variable. + * @param identifierName The identifier of the variable. * @return The type of the variable. */ - public Class getTypeOfIdentifier(String identifier) { - Optional> type = this.getVariable(identifier); - if (!type.isPresent()) { - throw new ParseCancellationException(UNDECLARED_IDENTIFIER + identifier); + public Class getTypeOfIdentifier(String identifierName) { + Optional identifier = this.getIdentifier(identifierName); + if (!identifier.isPresent()) { + throw new ParseCancellationException(UNDECLARED_IDENTIFIER + identifierName); } - return type.get(); + return identifier.get().dataType; } /** - * Pushes a variable reference on the current stack frame. Makes sure there is no name collision + * Pushes a variable reference on the current stack frame. Makes sure there is + * no name collision * with other identifiers already in scope. * - * @param variableName The name of the variable. - * @param variableReference The variable to push on the stack. - * @throws ParseCancellationException if the variable is not declared in the current scope. + * @param identifierName The name of the variable. + * @throws ParseCancellationException if the variable is not declared in the + * current scope. */ - public void pushVariableReference(String variableName, Expression variableReference) { - getParameter(variableName).ifPresentOrElse(parameterValue -> this.push(parameterValue), - () -> getVariable(variableName).ifPresentOrElse( - variableType -> this.pushVariableReference(variableReference, variableType), + public void pushIdentifierReference(String identifierName) { + getParameter(identifierName).ifPresentOrElse(parameterValue -> this.push(parameterValue), + () -> getIdentifier(identifierName).ifPresentOrElse( + variable -> this.push(variable.referenceExpression), () -> { - throw new ParseCancellationException(UNDECLARED_IDENTIFIER + variableName); + throw new ParseCancellationException(UNDECLARED_IDENTIFIER + identifierName); })); } - /** - * Pushes a variable reference on the current stack frame. This method is private because it is - * only used for to improve the readability of its public counterpart. - * @param variableReference The variable to push on the stack. - * @param variableType The type of the variable. - */ - private void pushVariableReference(Expression variableReference, - Class variableType) { - this.frames.peek().push(Expression.instantiate(variableReference.script, variableType)); - } - /** * Pushes an object on the current stack frame. No checks, no questions asked. * @@ -295,19 +237,13 @@ public void push(CallStackObject item) { this.frames.peek().push(item); } - /** - * Gets the object at the top of the current stack frame and removes it from the stack. - * - * @param The type of the object at the top of the current stack frame. - * @param expectedType The that the returned object is expected to have. - * @return The object at the top of the current stack frame. - */ public synchronized T pop(Class expectedType) { return this.frames.peek().pop(expectedType); } /** - * Gets the object at the top of the current stack frame without removing it from the stack. + * Gets the object at the top of the current stack frame without removing it + * from the stack. * * @return The object at the top of the current stack frame. */ diff --git a/src/main/java/eu/europa/ted/efx/model/CallStackObject.java b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java index 6223f519..c4fd8ba1 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStackObject.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java @@ -6,6 +6,6 @@ * As the EfxExpressionTranslator translates EFX to a target language, the objects in the call-stack * are typically code snippets in the target language. */ -public abstract class CallStackObject { +public interface CallStackObject { } 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 a32dd397..e752d6fa 100644 --- a/src/main/java/eu/europa/ted/efx/model/Context.java +++ b/src/main/java/eu/europa/ted/efx/model/Context.java @@ -1,6 +1,7 @@ package eu.europa.ted.efx.model; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.variables.Variable; /** * Used to store an evaluation context. @@ -20,7 +21,7 @@ public abstract class Context { public static class FieldContext extends Context { public FieldContext(final String fieldId, final PathExpression absolutePath, - final PathExpression relativePath, final Variable variable) { + final PathExpression relativePath, final Variable variable) { super(fieldId, absolutePath, relativePath, variable); } @@ -30,7 +31,7 @@ public FieldContext(final String fieldId, final PathExpression absolutePath, } public FieldContext(final String fieldId, final PathExpression absolutePath, - final Variable variable) { + final Variable variable) { super(fieldId, absolutePath, variable); } @@ -57,10 +58,10 @@ public NodeContext(final String nodeId, final PathExpression absolutePath) { private final String symbol; private final PathExpression absolutePath; private final PathExpression relativePath; - private final Variable variable; + private final Variable variable; protected Context(final String symbol, final PathExpression absolutePath, - final PathExpression relativePath, final Variable variable) { + final PathExpression relativePath, final Variable variable) { this.variable = variable; this.symbol = symbol; this.absolutePath = absolutePath; @@ -73,7 +74,7 @@ protected Context(final String symbol, final PathExpression absolutePath, } protected Context(final String symbol, final PathExpression absolutePath, - final Variable variable) { + final Variable variable) { this(symbol, absolutePath, absolutePath, variable); } @@ -89,7 +90,7 @@ public Boolean isNodeContext() { return this.getClass().equals(NodeContext.class); } - public Variable variable() { + public Variable variable() { return variable; } diff --git a/src/main/java/eu/europa/ted/efx/model/ContextStack.java b/src/main/java/eu/europa/ted/efx/model/ContextStack.java index d789c9be..4d8a781e 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContextStack.java +++ b/src/main/java/eu/europa/ted/efx/model/ContextStack.java @@ -3,10 +3,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Stack; + import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; /** * Used to keep track of the current evaluation context. Extends Stack<Context> to provide diff --git a/src/main/java/eu/europa/ted/efx/model/DateVariable.java b/src/main/java/eu/europa/ted/efx/model/DateVariable.java deleted file mode 100644 index ab92916d..00000000 --- a/src/main/java/eu/europa/ted/efx/model/DateVariable.java +++ /dev/null @@ -1,12 +0,0 @@ -package eu.europa.ted.efx.model; - -import eu.europa.ted.efx.model.Expression.DateExpression; - -public class DateVariable extends Variable { - - public DateVariable(String variableName, DateExpression initializationExpression, - DateExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } - -} diff --git a/src/main/java/eu/europa/ted/efx/model/DurationVariable.java b/src/main/java/eu/europa/ted/efx/model/DurationVariable.java deleted file mode 100644 index 7e76637f..00000000 --- a/src/main/java/eu/europa/ted/efx/model/DurationVariable.java +++ /dev/null @@ -1,12 +0,0 @@ -package eu.europa.ted.efx.model; - -import eu.europa.ted.efx.model.Expression.DurationExpression; - -public class DurationVariable extends Variable { - - public DurationVariable(String variableName, DurationExpression initializationExpression, - DurationExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } - -} diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java deleted file mode 100644 index a8bb68ba..00000000 --- a/src/main/java/eu/europa/ted/efx/model/Expression.java +++ /dev/null @@ -1,324 +0,0 @@ -package eu.europa.ted.efx.model; - -import static java.util.Map.entry; -import java.lang.reflect.Constructor; -import java.util.Map; -import org.antlr.v4.runtime.misc.ParseCancellationException; - -/** - * This class represents an expression in the target scripting language. - * - * The class is used to restrict the parameter types and return types of the methods that the EFX - * translator calls. This makes it easier for users to understand the nature and usage of each - * parameter when implementing {@link eu.europa.ted.efx.interfaces.ScriptGenerator}, - * {@link eu.europa.ted.efx.interfaces.MarkupGenerator} and - * {@link eu.europa.ted.efx.interfaces.SymbolResolver} interfaces for translating to a new target - * language. It also enables to EFX translator to perform type checking of EFX expressions. - * - */ -public class Expression extends CallStackObject { - - /** - * eForms types are mapped to Expression types. - */ - public static final Map> types = - Map.ofEntries(entry("id", StringExpression.class), // - entry("id-ref", StringExpression.class), // - entry("text", StringExpression.class), // - entry("text-multilingual", MultilingualStringExpression.class), // - entry("indicator", BooleanExpression.class), // - entry("amount", NumericExpression.class), // - entry("number", NumericExpression.class), // - entry("measure", DurationExpression.class), // - entry("code", StringExpression.class), entry("internal-code", StringExpression.class), // - entry("integer", NumericExpression.class), // - entry("date", DateExpression.class), // - entry("zoned-date", DateExpression.class), // - entry("time", TimeExpression.class), // - entry("zoned-time", TimeExpression.class), // - entry("url", StringExpression.class), // - entry("phone", StringExpression.class), // - entry("email", StringExpression.class)); - - /** - * ListExpression types equivalent to eForms types. - */ - public static final Map> listTypes = Map.ofEntries( - entry("id", StringListExpression.class), // - entry("id-ref", StringListExpression.class), // - entry("text", StringListExpression.class), // - entry("text-multilingual", MultilingualStringListExpression.class), // - entry("indicator", BooleanListExpression.class), // - entry("amount", NumericListExpression.class), // - entry("number", NumericListExpression.class), // - entry("measure", DurationListExpression.class), // - entry("code", StringListExpression.class), entry("internal-code", StringListExpression.class), // - entry("integer", NumericListExpression.class), // - entry("date", DateListExpression.class), // - entry("zoned-date", DateListExpression.class), // - entry("time", TimeListExpression.class), // - entry("zoned-time", TimeListExpression.class), // - entry("url", StringListExpression.class), // - entry("phone", StringListExpression.class), // - entry("email", StringListExpression.class)); - - /** - * Stores the expression represented in the target language. - */ - public final String script; - - public final Boolean isLiteral; - - public Expression(final String script) { - this.script = script; - this.isLiteral = false; - } - - public Expression(final String script, final Boolean isLiteral) { - this.script = script; - this.isLiteral = isLiteral; - } - - public static T instantiate(String script, Class type) { - try { - Constructor constructor = type.getConstructor(String.class); - return constructor.newInstance(script); - } catch (Exception e) { - throw new ParseCancellationException(e); - } - } - - public static T empty(Class type) { - return instantiate("", type); - } - - public final Boolean isEmpty() { - return this.script.isEmpty(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - - if (Expression.class.isAssignableFrom(obj.getClass())) { - return this.script.equals(((Expression) obj).script); - } - - return false; - } - - /** - * An PathExpression points to a node in your data set. - * - * Typically the data set is a XML document and therefore, in this case, the path expression is a - * XPath expression. - */ - public static class PathExpression extends Expression { - - public PathExpression(final String script) { - super(script); - } - } - - public static class ContextExpression extends Expression { - - public ContextExpression(final String script) { - super(script); - } - } - - /** - * Represents a boolean expression, value or literal in the target language. - */ - public static class BooleanExpression extends Expression { - - public BooleanExpression(final String script) { - super(script); - } - - public BooleanExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral); - } - } - - /** - * Represents a numeric expression, value or literal in the target language. - */ - public static class NumericExpression extends Expression { - - public NumericExpression(final String script) { - super(script); - } - - public NumericExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral); - } - } - - /** - * Represents a string expression, value or literal in the target language. - */ - public static class StringExpression extends Expression { - - public StringExpression(final String script) { - super(script); - } - - public StringExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral); - } - } - - public static class MultilingualStringExpression extends StringExpression { - - public MultilingualStringExpression(final String script) { - super(script); - } - - public MultilingualStringExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral); - } - } - - /** - * Represents a date expression or value in the target language. - */ - public static class DateExpression extends Expression { - - public DateExpression(final String script) { - super(script); - } - - public DateExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral); - } - } - - /** - * Represents a time expression, value or literal in the target language. - */ - public static class TimeExpression extends Expression { - - public TimeExpression(final String script) { - super(script); - } - - public TimeExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral); - } - } - - /** - * Represents a duration expression, value or literal in the target language. - */ - public static class DurationExpression extends Expression { - - public DurationExpression(final String script) { - super(script); - } - - public DurationExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral); - } - } - - /** - * Used to represent a list of strings in the target language. - */ - public static class ListExpression extends Expression { - - public ListExpression(final String script) { - super(script); - } - } - - /** - * Used to represent a list of strings in the target language. - */ - public static class StringListExpression extends ListExpression { - - public StringListExpression(final String script) { - super(script); - } - } - - public static class MultilingualStringListExpression extends StringListExpression { - - public MultilingualStringListExpression(final String script) { - super(script); - } - } - - /** - * Used to represent a list of numbers in the target language. - */ - public static class NumericListExpression extends ListExpression { - - public NumericListExpression(final String script) { - super(script); - } - } - - /** - * Used to represent a list of dates in the target language. - */ - public static class DateListExpression extends ListExpression { - - public DateListExpression(final String script) { - super(script); - } - } - - /** - * Used to represent a list of times in the target language. - */ - public static class TimeListExpression extends ListExpression { - - public TimeListExpression(final String script) { - super(script); - } - } - - /** - * Used to represent a list of durations in the target language. - */ - public static class DurationListExpression extends ListExpression { - - public DurationListExpression(final String script) { - super(script); - } - } - - - /** - * Used to represent a list of booleans in the target language. - */ - public static class BooleanListExpression extends ListExpression { - - public BooleanListExpression(final String script) { - super(script); - } - } - - /** - * Used to represent iterators (for traversing a list using a variable) - */ - public static class IteratorExpression extends Expression { - - public IteratorExpression(final String script) { - super(script); - } - } - - /** - * Used to represent a collection of {@link IteratorExpression}. - */ - public static class IteratorListExpression extends ListExpression { - - public IteratorListExpression(final String script) { - super(script); - } - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/Identifier.java b/src/main/java/eu/europa/ted/efx/model/Identifier.java deleted file mode 100644 index 413e5008..00000000 --- a/src/main/java/eu/europa/ted/efx/model/Identifier.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.europa.ted.efx.model; - -public class Identifier { - public String name; - public T referenceExpression; - - public Identifier(String name, T referenceExpression) { - this.name = name; - this.referenceExpression = referenceExpression; - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/NumericVariable.java b/src/main/java/eu/europa/ted/efx/model/NumericVariable.java deleted file mode 100644 index 653915ee..00000000 --- a/src/main/java/eu/europa/ted/efx/model/NumericVariable.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.europa.ted.efx.model; - -import eu.europa.ted.efx.model.Expression.NumericExpression; - -public class NumericVariable extends Variable { - - public NumericVariable(String variableName, NumericExpression initializationExpression, - NumericExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/StringVariable.java b/src/main/java/eu/europa/ted/efx/model/StringVariable.java deleted file mode 100644 index 8d3704ee..00000000 --- a/src/main/java/eu/europa/ted/efx/model/StringVariable.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.europa.ted.efx.model; - -import eu.europa.ted.efx.model.Expression.StringExpression; - -public class StringVariable extends Variable { - - public StringVariable(String variableName, StringExpression initializationExpression, - StringExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/TimeVariable.java b/src/main/java/eu/europa/ted/efx/model/TimeVariable.java deleted file mode 100644 index 7a62e0b7..00000000 --- a/src/main/java/eu/europa/ted/efx/model/TimeVariable.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.europa.ted.efx.model; - -import eu.europa.ted.efx.model.Expression.TimeExpression; - -public class TimeVariable extends Variable { - - public TimeVariable(String variableName, TimeExpression initializationExpression, - TimeExpression referenceExpression) { - super(variableName, initializationExpression, referenceExpression); - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/Variable.java b/src/main/java/eu/europa/ted/efx/model/Variable.java deleted file mode 100644 index 12468548..00000000 --- a/src/main/java/eu/europa/ted/efx/model/Variable.java +++ /dev/null @@ -1,16 +0,0 @@ -package eu.europa.ted.efx.model; - -public class Variable extends Identifier { - public T initializationExpression; - - public Variable(String variableName, T initializationExpression, T referenceExpression) { - super(variableName, referenceExpression); - this.name = variableName; - this.initializationExpression = initializationExpression; - } - - public Variable(Identifier identifier, T initializationExpression) { - super(identifier.name, identifier.referenceExpression); - this.initializationExpression = initializationExpression; - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/VariableList.java b/src/main/java/eu/europa/ted/efx/model/VariableList.java deleted file mode 100644 index 8bda31b4..00000000 --- a/src/main/java/eu/europa/ted/efx/model/VariableList.java +++ /dev/null @@ -1,29 +0,0 @@ -package eu.europa.ted.efx.model; - -import java.util.LinkedList; -import java.util.List; - -public class VariableList extends CallStackObject { - - LinkedList> variables; - - public VariableList() { - this.variables = new LinkedList<>(); - } - - public void push(Variable variable) { - this.variables.push(variable); - } - - public synchronized Variable pop() { - return this.variables.pop(); - } - - public boolean isEmpty() { - return this.variables.isEmpty(); - } - - public List> asList() { - return this.variables; - } -} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java b/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java new file mode 100644 index 00000000..ed8e30d9 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java @@ -0,0 +1,85 @@ +package eu.europa.ted.efx.model.expressions; + +import java.lang.reflect.Constructor; + +import org.antlr.v4.runtime.misc.ParseCancellationException; + +import eu.europa.ted.efx.model.CallStackObject; + +public interface Expression extends CallStackObject { + + public String getScript(); + + public Boolean isLiteral(); + + static T instantiate(String script, Class type) { + return Expression.instantiate(script, false, type); + } + + static T from(Expression source, Class returnType) { + return Expression.instantiate(source.getScript(), source.isLiteral(), returnType); + } + + static T instantiate(String script, Boolean isLiteral, Class type) { + try { + if (isLiteral) { + Constructor constructor = type.getConstructor(String.class, Boolean.class); + return constructor.newInstance(script, isLiteral); + } else { + Constructor constructor = type.getConstructor(String.class); + return constructor.newInstance(script); + } + } catch (Exception e) { + throw new ParseCancellationException(e); + } + } + + static T empty(Class type) { + return instantiate("", type); + } + + /** + * Base class for all {@link Expression} implementations. + */ + public abstract class Impl implements Expression { + + private final String script; + private final Boolean isLiteral; + + @Override + public String getScript() { + return this.script; + } + + @Override + public Boolean isLiteral() { + return this.isLiteral; + } + + protected Impl(final String script) { + this(script, false); + } + + protected Impl(final String script, final Boolean isLiteral) { + this.script = script; + this.isLiteral = isLiteral; + } + + public final Boolean isEmpty() { + return this.script.isEmpty(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (Expression.class.isAssignableFrom(obj.getClass())) { + return this.script.equals(((Expression) obj).getScript()); + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java new file mode 100644 index 00000000..dbaf3fe7 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java @@ -0,0 +1,110 @@ +package eu.europa.ted.efx.model.expressions; + +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression; +import eu.europa.ted.efx.model.expressions.sequence.SequenceExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxExpressionType; +import eu.europa.ted.efx.model.types.EfxExpressionTypeAssociation; + +public interface TypedExpression extends Expression { + + public Class getExpressionType(); + + public Class getDataType(); + + public Boolean is(Class dataType); + + public Boolean is(Class referenceType, Class dataType); + + static Class getEfxDataType( + Class clazz, Class dataType1) { + EfxDataTypeAssociation annotation = clazz.getAnnotation(EfxDataTypeAssociation.class); + if (annotation == null) { + return EfxDataType.ANY.asSubclass(dataType1); // throw new IllegalArgumentException("Missing + // @EfxDataTypeAssociation annotation"); + } + return annotation.dataType().asSubclass(dataType1); + } + + static Class getEfxDataType(Class clazz) { + EfxDataTypeAssociation annotation = clazz.getAnnotation(EfxDataTypeAssociation.class); + if (annotation == null) { + throw new IllegalArgumentException("Missing @EfxDataTypeAssociation annotation"); + } + return annotation.dataType(); + } + + public static T from(TypedExpression source, Class targetType) { + if (PathExpression.class.isAssignableFrom(targetType)) { + return targetType.cast(PathExpression.from(source, targetType.asSubclass(PathExpression.class))); + } else if (SequenceExpression.class.isAssignableFrom(targetType)) { + return targetType.cast(SequenceExpression.from(source, targetType.asSubclass(SequenceExpression.class))); + } else if (ScalarExpression.class.isAssignableFrom(targetType)) { + return targetType.cast(ScalarExpression.from(source, targetType.asSubclass(ScalarExpression.class))); + } else { + throw new RuntimeException("Unknown expression type: " + targetType); + } + } + + public static TypedExpression instantiate(String script, + Class expressionType, Class dataType) { + if (PathExpression.class.isAssignableFrom(expressionType)) { + return PathExpression.instantiate(script, dataType); + } else if (SequenceExpression.class.isAssignableFrom(expressionType)) { + return SequenceExpression.instantiate(script, dataType); + } else if (ScalarExpression.class.isAssignableFrom(expressionType)) { + return ScalarExpression.instantiate(script, dataType); + } else { + throw new RuntimeException("Unknown expression type: " + expressionType); + } + } + + public static Boolean canConvert(Class from, Class to) { + var fromExpressionType = from.getAnnotation(EfxExpressionTypeAssociation.class).expressionType(); + var fromDataType = from.getAnnotation(EfxDataTypeAssociation.class).dataType(); + var toExpressionType = to.getAnnotation(EfxExpressionTypeAssociation.class).expressionType(); + var toDataType = to.getAnnotation(EfxDataTypeAssociation.class).dataType(); + + return toExpressionType.isAssignableFrom(fromExpressionType) && toDataType.isAssignableFrom(fromDataType); + } + + public abstract class Impl extends Expression.Impl implements TypedExpression { + + private Class expressionType; + private Class dataType; + + public Impl(final String script, Class expressionType, + Class dataType) { + this(script, false, expressionType, dataType); + } + + public Impl(final String script, final Boolean isLiteral, + Class expressionType, Class dataType) { + super(script, isLiteral); + this.expressionType = expressionType; + this.dataType = dataType; + } + + @Override + public Class getExpressionType() { + return this.expressionType; + } + + @Override + public Class getDataType() { + return this.dataType; + } + + @Override + public Boolean is(Class dataType) { + return dataType.isAssignableFrom(this.dataType); + } + + @Override + public Boolean is(Class referenceType, Class dataType) { + return referenceType.isAssignableFrom(this.expressionType) && dataType.isAssignableFrom(this.dataType); + } + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorExpression.java new file mode 100644 index 00000000..f04ec114 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorExpression.java @@ -0,0 +1,13 @@ +package eu.europa.ted.efx.model.expressions.iteration; + +import eu.europa.ted.efx.model.expressions.Expression; + +/** + * Used to represent iterators (for traversing a list using a variable) + */ +public class IteratorExpression extends Expression.Impl { + + public IteratorExpression(final String script) { + super(script); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorListExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorListExpression.java new file mode 100644 index 00000000..d1af7bbc --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/iteration/IteratorListExpression.java @@ -0,0 +1,13 @@ +package eu.europa.ted.efx.model.expressions.iteration; + +import eu.europa.ted.efx.model.expressions.Expression; + +/** + * Used to represent a collection of {@link IteratorExpression}. + */ +public class IteratorListExpression extends Expression.Impl { + + public IteratorListExpression(final String script) { + super(script); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/BooleanPathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/BooleanPathExpression.java new file mode 100644 index 00000000..edf55b56 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/BooleanPathExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +@EfxDataTypeAssociation(dataType = EfxDataType.Boolean.class) +public class BooleanPathExpression extends PathExpression.Impl { + + public BooleanPathExpression(final String script) { + super(script, EfxDataType.Boolean.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/DatePathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/DatePathExpression.java new file mode 100644 index 00000000..e7a8c761 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/DatePathExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +@EfxDataTypeAssociation(dataType = EfxDataType.Date.class) +public class DatePathExpression extends PathExpression.Impl { + + public DatePathExpression(final String script) { + super(script, EfxDataType.Date.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/DurationPathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/DurationPathExpression.java new file mode 100644 index 00000000..ea87912a --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/DurationPathExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +@EfxDataTypeAssociation(dataType = EfxDataType.Duration.class) +public class DurationPathExpression extends PathExpression.Impl { + + public DurationPathExpression(final String script) { + super(script, EfxDataType.Duration.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/MultilingualStringPathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/MultilingualStringPathExpression.java new file mode 100644 index 00000000..84c0f5f4 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/MultilingualStringPathExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +@EfxDataTypeAssociation(dataType = EfxDataType.MultilingualString.class) +public class MultilingualStringPathExpression extends StringPathExpression { + + public MultilingualStringPathExpression(final String script) { + super(script, EfxDataType.MultilingualString.class); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..9fe125dd --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +@EfxDataTypeAssociation(dataType = EfxDataType.Node.class) +public class NodePathExpression extends PathExpression.Impl { + + public NodePathExpression(final String script) { + super(script, EfxDataType.Node.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/NumericPathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/NumericPathExpression.java new file mode 100644 index 00000000..c404c935 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/NumericPathExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +@EfxDataTypeAssociation(dataType = EfxDataType.Number.class) +public class NumericPathExpression extends PathExpression.Impl { + + public NumericPathExpression(final String script) { + super(script, EfxDataType.Number.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/PathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/PathExpression.java new file mode 100644 index 00000000..7010f117 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/PathExpression.java @@ -0,0 +1,126 @@ +package eu.europa.ted.efx.model.expressions.path; + +import static java.util.Map.entry; + +import java.util.Map; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression; +import eu.europa.ted.efx.model.expressions.sequence.SequenceExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxExpressionType; +import eu.europa.ted.efx.model.types.EfxExpressionTypeAssociation; +import eu.europa.ted.efx.model.types.FieldTypes; + +/** + * A {@link PathExpression} is a {@link TypedExpression} that represents a + * pointer (a path) to an element in a structured document. Typically the + * document will be an XML document but it could also be a JSON + * object or any other structured data source. + * + * {@link PathExpression} objects are used to resolve eForms fields and Nodes. + * To develop an intuition on the behaviour of a {@link PathExpression}, you can + * think of it as being similar to an XPath expression. + * + * The {@link #getScript()} method should return an expression in the target + * language that resolves to a value. + */ +public interface PathExpression extends ScalarExpression, SequenceExpression, EfxExpressionType.Path { + + /** + * Maps {@link FieldTypes}, to concrete java classes that implement + * {@link PathExpression}. + */ + Map> fromFieldType = Map.ofEntries( + entry(FieldTypes.ID, StringPathExpression.class), // + entry(FieldTypes.ID_REF, StringPathExpression.class), // + entry(FieldTypes.TEXT, StringPathExpression.class), // + entry(FieldTypes.TEXT_MULTILINGUAL, MultilingualStringPathExpression.class), // + entry(FieldTypes.INDICATOR, BooleanPathExpression.class), // + entry(FieldTypes.AMOUNT, NumericPathExpression.class), // + entry(FieldTypes.NUMBER, NumericPathExpression.class), // + entry(FieldTypes.MEASURE, DurationPathExpression.class), // + entry(FieldTypes.CODE, StringPathExpression.class), + entry(FieldTypes.INTERNAL_CODE, StringPathExpression.class), // + entry(FieldTypes.INTEGER, NumericPathExpression.class), // + entry(FieldTypes.DATE, DatePathExpression.class), // + entry(FieldTypes.ZONED_DATE, DatePathExpression.class), // + entry(FieldTypes.TIME, TimePathExpression.class), // + entry(FieldTypes.ZONED_TIME, TimePathExpression.class), // + entry(FieldTypes.URL, StringPathExpression.class), // + entry(FieldTypes.PHONE, StringPathExpression.class), // + entry(FieldTypes.EMAIL, StringPathExpression.class)); + + /** + * Maps subclasses of {@link EfxDataType}, to concrete java classes that + * implement {@link PathExpression}. + */ + Map, Class> fromEfxDataType = Map.ofEntries( + entry(EfxDataType.String.class, StringPathExpression.class), // + entry(EfxDataType.MultilingualString.class, MultilingualStringPathExpression.class), // + entry(EfxDataType.Boolean.class, BooleanPathExpression.class), // + entry(EfxDataType.Number.class, NumericPathExpression.class), // + entry(EfxDataType.Date.class, DatePathExpression.class), // + entry(EfxDataType.Time.class, TimePathExpression.class), // + entry(EfxDataType.Duration.class, DurationPathExpression.class), // + entry(EfxDataType.Node.class, NodePathExpression.class) // + ); + + /** + * Creates an object that implements {@link PathExpression} and conforms to the + * given field type. + * + * @param script The target language script that resolves to a value. + * @param fieldType The type of field (or node) that the returned + * {@link PathExpression} points to. + * + * @return An object that implements {@link PathExpression} and conforms to the + * given field type. + */ + static PathExpression instantiate(String script, FieldTypes fieldType) { + return Expression.instantiate(script, fromFieldType.get(fieldType)); + } + + /** + * Creates an object that implements {@link PathExpression} and conforms to the + * given {@link EfxDataType}. + * + * @param script The target language script that resolves to a value. + * @param efxDataType The {@link EfxDataType} of the field (or node) that the + * returned {@link PathExpression} points to. + * + * @return An object that implements {@link PathExpression} and conforms to the + * given {@link EfxDataType}. + */ + static PathExpression instantiate(String script, Class efxDataType) { + return Expression.instantiate(script, fromEfxDataType.get(efxDataType)); + } + + /** + * Creates an object of the given type, by using the given + * {@link TypedExpression} as a source. + * + * @param The type of the returned object. + * @param source The source {@link TypedExpression} to copy. + * @param returnType The type of object to be returned. + * + * @return An object of the given type, having the same property values as the + * source. + */ + static T from(TypedExpression source, Class returnType) { + return Expression.from(source, returnType); + } + + /** + * A base class for {@link PathExpression} implementations. + */ + @EfxExpressionTypeAssociation(expressionType = EfxExpressionType.Path.class) + public abstract class Impl extends TypedExpression.Impl + implements PathExpression { + + protected Impl(final String script, Class dataType) { + super(script, EfxExpressionType.Path.class, dataType); + } + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/StringPathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/StringPathExpression.java new file mode 100644 index 00000000..4573b01c --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/StringPathExpression.java @@ -0,0 +1,16 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +@EfxDataTypeAssociation(dataType = EfxDataType.String.class) +public class StringPathExpression extends PathExpression.Impl { + + public StringPathExpression(final String script) { + super(script, EfxDataType.String.class); + } + + protected StringPathExpression(final String script, Class type) { + super(script, type); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/path/TimePathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/TimePathExpression.java new file mode 100644 index 00000000..30acecfc --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/path/TimePathExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.path; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +@EfxDataTypeAssociation(dataType = EfxDataType.Time.class) +public class TimePathExpression extends PathExpression.Impl { + + public TimePathExpression(final String script) { + super(script, EfxDataType.Time.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanExpression.java new file mode 100644 index 00000000..61ab75b3 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanExpression.java @@ -0,0 +1,23 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * Represents a boolean expression, value or literal in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Boolean.class) +public class BooleanExpression extends ScalarExpression.Impl { + + public BooleanExpression(final String script) { + super(script, EfxDataType.Boolean.class); + } + + public BooleanExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Boolean.class); + } + + public static BooleanExpression empty() { + return new BooleanExpression(""); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateExpression.java new file mode 100644 index 00000000..8975b7fe --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateExpression.java @@ -0,0 +1,23 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * Represents a date expression or value in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Date.class) +public class DateExpression extends ScalarExpression.Impl { + + public DateExpression(final String script) { + super(script, EfxDataType.Date.class); + } + + public DateExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Date.class); + } + + public static DateExpression empty() { + return new DateExpression(""); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationExpression.java new file mode 100644 index 00000000..d3c6b04a --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationExpression.java @@ -0,0 +1,23 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Represents a duration expression, value or literal in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Duration.class) +public class DurationExpression extends ScalarExpression.Impl { + + public DurationExpression(final String script) { + super(script, EfxDataType.Duration.class); + } + + public DurationExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Duration.class); + } + + public static DurationExpression empty() { + return new DurationExpression(""); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringExpression.java new file mode 100644 index 00000000..ba9c18b7 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringExpression.java @@ -0,0 +1,16 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +@EfxDataTypeAssociation(dataType = EfxDataType.MultilingualString.class) +public class MultilingualStringExpression extends StringExpression { + + public MultilingualStringExpression(final String script) { + super(script, false, EfxDataType.MultilingualString.class); + } + + public MultilingualStringExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.MultilingualString.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericExpression.java new file mode 100644 index 00000000..2be90118 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericExpression.java @@ -0,0 +1,23 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Represents a numeric expression, value or literal in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Number.class) +public class NumericExpression extends ScalarExpression.Impl { + + public NumericExpression(final String script) { + super(script, EfxDataType.Number.class); + } + + public NumericExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Number.class); + } + + public static NumericExpression empty() { + return new NumericExpression(""); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarExpression.java new file mode 100644 index 00000000..e54e5ca0 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarExpression.java @@ -0,0 +1,100 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import static java.util.Map.entry; + +import java.util.Map; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxExpressionType; +import eu.europa.ted.efx.model.types.EfxExpressionTypeAssociation; +import eu.europa.ted.efx.model.types.FieldTypes; + +public interface ScalarExpression extends TypedExpression, EfxExpressionType.Scalar { + + /** + * Maps {@link FieldTypes} to their corresponding {@link ScalarExpression} + * class. + */ + Map> fromFieldType = Map.ofEntries( + entry(FieldTypes.ID, StringExpression.class), // + entry(FieldTypes.ID_REF, StringExpression.class), // + entry(FieldTypes.TEXT, StringExpression.class), // + entry(FieldTypes.TEXT_MULTILINGUAL, MultilingualStringExpression.class), // + entry(FieldTypes.INDICATOR, BooleanExpression.class), // + entry(FieldTypes.AMOUNT, NumericExpression.class), // + entry(FieldTypes.NUMBER, NumericExpression.class), // + entry(FieldTypes.MEASURE, DurationExpression.class), // + entry(FieldTypes.CODE, StringExpression.class), entry(FieldTypes.INTERNAL_CODE, StringExpression.class), // + entry(FieldTypes.INTEGER, NumericExpression.class), // + entry(FieldTypes.DATE, DateExpression.class), // + entry(FieldTypes.ZONED_DATE, DateExpression.class), // + entry(FieldTypes.TIME, TimeExpression.class), // + entry(FieldTypes.ZONED_TIME, TimeExpression.class), // + entry(FieldTypes.URL, StringExpression.class), // + entry(FieldTypes.PHONE, StringExpression.class), // + entry(FieldTypes.EMAIL, StringExpression.class)); + + /** + * Maps {@link EfxDataType} to their corresponding {@link ScalarExpression} + * class. + */ + Map, Class> fromEfxDataType = Map + .ofEntries( + entry(EfxDataType.String.class, StringExpression.class), // + entry(EfxDataType.MultilingualString.class, MultilingualStringExpression.class), // + entry(EfxDataType.Boolean.class, BooleanExpression.class), // + entry(EfxDataType.Number.class, NumericExpression.class), // + entry(EfxDataType.Date.class, DateExpression.class), // + entry(EfxDataType.Time.class, TimeExpression.class), // + entry(EfxDataType.Duration.class, DurationExpression.class) // + ); + + /** + * Creates an object of the given type, by using the given + * {@link TypedExpression} as a source. + * + * @param The type of the returned object. + * @param source The source {@link TypedExpression} to copy. + * @param returnType The type of object to be returned. + * + * @return An object of the given type, having the same property values as the + * source. + */ + static T from(TypedExpression source, Class returnType) { + return Expression.from(source, returnType); + } + + /** + * Creates an object that implements {@link ScalarExpression} and conforms to + * the given {@link EfxDataType}. + * + * @param script The target language script that resolves to a value. + * @param efxDataType The {@link EfxDataType} of the field (or node) that the + * returned {@link ScalarExpression} points to. + * + * @return An object that implements {@link ScalarExpression} and conforms to + * the + * given {@link EfxDataType}. + */ + static ScalarExpression instantiate(String script, Class efxDataType) { + return Expression.instantiate(script, fromEfxDataType.get(efxDataType)); + } + + /** + * A base class for {@link ScalarExpression} implementations. + */ + @EfxExpressionTypeAssociation(expressionType = EfxExpressionType.Scalar.class) + public abstract class Impl extends TypedExpression.Impl + implements ScalarExpression { + + protected Impl(String script, Class dataType) { + super(script, EfxExpressionType.Scalar.class, dataType); + } + + protected Impl(String script, Boolean isLiteral, Class dataType) { + super(script, isLiteral, EfxExpressionType.Scalar.class, dataType); + } + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringExpression.java new file mode 100644 index 00000000..4690e728 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringExpression.java @@ -0,0 +1,27 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Represents a string expression, value or literal in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.String.class) +public class StringExpression extends ScalarExpression.Impl { + + public StringExpression(final String script) { + this(script, false); + } + + public StringExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.String.class); + } + + public StringExpression(final String script, final Boolean isLiteral, Class type) { + super(script, isLiteral, type); + } + + public static StringExpression empty() { + return new StringExpression(""); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeExpression.java new file mode 100644 index 00000000..6916d7b8 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeExpression.java @@ -0,0 +1,23 @@ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Represents a time expression, value or literal in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Time.class) +public class TimeExpression extends ScalarExpression.Impl { + + public TimeExpression(final String script) { + super(script, EfxDataType.Time.class); + } + + public TimeExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Time.class); + } + + public static TimeExpression empty() { + return new TimeExpression(""); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceExpression.java new file mode 100644 index 00000000..66bce7ba --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceExpression.java @@ -0,0 +1,19 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * Used to represent a list of booleans in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Boolean.class) +public class BooleanSequenceExpression extends SequenceExpression.Impl { + + public BooleanSequenceExpression(final String script) { + super(script, EfxDataType.Boolean.class); + } + + public BooleanSequenceExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral,EfxDataType.Boolean.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceExpression.java new file mode 100644 index 00000000..b9be9954 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceExpression.java @@ -0,0 +1,19 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Used to represent a list of dates in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Date.class) +public class DateSequenceExpression extends SequenceExpression.Impl { + + public DateSequenceExpression(final String script) { + super(script, EfxDataType.Date.class); + } + + public DateSequenceExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Date.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceExpression.java new file mode 100644 index 00000000..94f6318c --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceExpression.java @@ -0,0 +1,19 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Used to represent a list of durations in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Duration.class) +public class DurationSequenceExpression extends SequenceExpression.Impl { + + public DurationSequenceExpression(final String script) { + super(script, EfxDataType.Duration.class); + } + + public DurationSequenceExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Duration.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequenceExpression.java new file mode 100644 index 00000000..1aed6a5e --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequenceExpression.java @@ -0,0 +1,12 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +@EfxDataTypeAssociation(dataType = EfxDataType.MultilingualString.class) +public class MultilingualStringSequenceExpression extends StringSequenceExpression { + + public MultilingualStringSequenceExpression(final String script) { + super(script, false, EfxDataType.MultilingualString.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceExpression.java new file mode 100644 index 00000000..9844e8a2 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceExpression.java @@ -0,0 +1,19 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Used to represent a list of numbers in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Number.class) +public class NumericSequenceExpression extends SequenceExpression.Impl { + + public NumericSequenceExpression(final String script) { + super(script, EfxDataType.Number.class); + } + + public NumericSequenceExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Number.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceExpression.java new file mode 100644 index 00000000..5b1822c7 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceExpression.java @@ -0,0 +1,98 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import static java.util.Map.entry; + +import java.util.Map; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxExpressionType; +import eu.europa.ted.efx.model.types.EfxExpressionTypeAssociation; +import eu.europa.ted.efx.model.types.FieldTypes; + +public interface SequenceExpression extends TypedExpression, EfxExpressionType.Sequence { + + /** + * Maps {@link FieldTypes} to the corresponding {@link SequenceExpression}. + */ + Map> fromFieldType = Map.ofEntries( + entry(FieldTypes.ID, StringSequenceExpression.class), // + entry(FieldTypes.ID_REF, StringSequenceExpression.class), // + entry(FieldTypes.TEXT, StringSequenceExpression.class), // + entry(FieldTypes.TEXT_MULTILINGUAL, MultilingualStringSequenceExpression.class), // + entry(FieldTypes.INDICATOR, BooleanSequenceExpression.class), // + entry(FieldTypes.AMOUNT, NumericSequenceExpression.class), // + entry(FieldTypes.NUMBER, NumericSequenceExpression.class), // + entry(FieldTypes.MEASURE, DurationSequenceExpression.class), // + entry(FieldTypes.CODE, StringSequenceExpression.class), // + entry(FieldTypes.INTERNAL_CODE, StringSequenceExpression.class), // + entry(FieldTypes.INTEGER, NumericSequenceExpression.class), // + entry(FieldTypes.DATE, DateSequenceExpression.class), // + entry(FieldTypes.ZONED_DATE, DateSequenceExpression.class), // + entry(FieldTypes.TIME, TimeSequenceExpression.class), // + entry(FieldTypes.ZONED_TIME, TimeSequenceExpression.class), // + entry(FieldTypes.URL, StringSequenceExpression.class), // + entry(FieldTypes.PHONE, StringSequenceExpression.class), // + entry(FieldTypes.EMAIL, StringSequenceExpression.class)); + + /** + * Maps {@link EfxDataType} to the corresponding {@link SequenceExpression}. + */ + Map, Class> fromEfxDataType = Map + .ofEntries( + entry(EfxDataType.String.class, StringSequenceExpression.class), // + entry(EfxDataType.MultilingualString.class, MultilingualStringSequenceExpression.class), // + entry(EfxDataType.Boolean.class, BooleanSequenceExpression.class), // + entry(EfxDataType.Number.class, NumericSequenceExpression.class), // + entry(EfxDataType.Date.class, DateSequenceExpression.class), // + entry(EfxDataType.Time.class, TimeSequenceExpression.class), // + entry(EfxDataType.Duration.class, DurationSequenceExpression.class) // + ); + + /** + * Creates an object of the given type, by using the given + * {@link TypedExpression} as a source. + * + * @param The type of the returned object. + * @param source The source {@link TypedExpression} to copy. + * @param returnType The type of object to be returned. + * + * @return An object of the given type, having the same property values as the + * source. + */ + static T from(TypedExpression source, Class returnType) { + return Expression.from(source, returnType); + } + + /** + * Creates an object that implements {@link SequenceExpression} and conforms to the + * given {@link EfxDataType}. + * + * @param script The target language script that resolves to a value. + * @param efxDataType The {@link EfxDataType} of the field (or node) that the + * returned {@link SequenceExpression} points to. + * + * @return An object that implements {@link SequenceExpression} and conforms to the + * given {@link EfxDataType}. + */ + static SequenceExpression instantiate(String script, Class efxDataType) { + return Expression.instantiate(script, fromEfxDataType.get(efxDataType)); + } + + /** + * A base class for {@link SequenceExpression} implementations. + */ + @EfxExpressionTypeAssociation(expressionType = EfxExpressionType.Sequence.class) + public abstract class Impl extends TypedExpression.Impl + implements SequenceExpression { + + protected Impl(final String script, Class dataType) { + this(script, false, dataType); + } + + protected Impl(final String script, final Boolean isLiteral, Class dataType) { + super(script, isLiteral, EfxExpressionType.Sequence.class, dataType); + } + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceExpression.java new file mode 100644 index 00000000..176d9f97 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceExpression.java @@ -0,0 +1,23 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; + +/** + * Used to represent a list of strings in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.String.class) +public class StringSequenceExpression extends SequenceExpression.Impl { + + public StringSequenceExpression(final String script) { + this(script, false); + } + + public StringSequenceExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.String.class); + } + + protected StringSequenceExpression(final String script, final Boolean isLiteral, Class type) { + super(script, isLiteral, type); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceExpression.java new file mode 100644 index 00000000..c8aebc50 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceExpression.java @@ -0,0 +1,18 @@ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxDataType; +/** + * Used to represent a list of times in the target language. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.Time.class) +public class TimeSequenceExpression extends SequenceExpression.Impl { + + public TimeSequenceExpression(final String script) { + super(script, EfxDataType.Time.class); + } + + public TimeSequenceExpression(final String script, final Boolean isLiteral) { + super(script, isLiteral, EfxDataType.Time.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java similarity index 89% rename from src/main/java/eu/europa/ted/efx/model/ContentBlock.java rename to src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java index 40d1224b..b019ad1e 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java +++ b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java @@ -1,4 +1,4 @@ -package eu.europa.ted.efx.model; +package eu.europa.ted.efx.model.templates; import java.util.Comparator; import java.util.LinkedHashSet; @@ -7,9 +7,14 @@ import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; + import org.antlr.v4.runtime.misc.ParseCancellationException; import org.apache.commons.lang3.tuple.Pair; + import eu.europa.ted.efx.interfaces.MarkupGenerator; +import eu.europa.ted.efx.model.Context; +import eu.europa.ted.efx.model.variables.Variable; +import eu.europa.ted.efx.model.variables.VariableList; public class ContentBlock { private final ContentBlock parent; @@ -115,20 +120,20 @@ public Context getParentContext() { return this.parent.getContext(); } - public Set> getOwnVariables() { - Set> variables = new LinkedHashSet<>(); + public Set getOwnVariables() { + Set variables = new LinkedHashSet<>(); if (this.context != null && this.context.variable() != null) { variables.add(this.context.variable()); } - variables.addAll(this.variables.asList()); + variables.addAll(this.variables); return variables; } - public Set> getAllVariables() { + public Set getAllVariables() { if (this.parent == null) { return new LinkedHashSet<>(this.getOwnVariables()); } - final Set> merged = new LinkedHashSet<>(); + final Set merged = new LinkedHashSet<>(); merged.addAll(parent.getAllVariables()); merged.addAll(this.getOwnVariables()); return merged; @@ -159,10 +164,10 @@ public Markup renderCallTemplate(MarkupGenerator markupGenerator) { Set> variables = new LinkedHashSet<>(); if (this.parent != null) { variables.addAll(parent.getAllVariables().stream() - .map(v -> Pair.of(v.name, v.referenceExpression.script)).collect(Collectors.toList())); + .map(v -> Pair.of(v.name, v.referenceExpression.getScript())).collect(Collectors.toList())); } variables.addAll(this.getOwnVariables().stream() - .map(v -> Pair.of(v.name, v.initializationExpression.script)).collect(Collectors.toList())); + .map(v -> Pair.of(v.name, v.initializationExpression.getScript())).collect(Collectors.toList())); return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath(), variables); } diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlockStack.java similarity index 94% rename from src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java rename to src/main/java/eu/europa/ted/efx/model/templates/ContentBlockStack.java index 7d1ce21f..7704429b 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java +++ b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlockStack.java @@ -1,7 +1,10 @@ -package eu.europa.ted.efx.model; +package eu.europa.ted.efx.model.templates; import java.util.Stack; +import eu.europa.ted.efx.model.Context; +import eu.europa.ted.efx.model.variables.VariableList; + public class ContentBlockStack extends Stack { /** diff --git a/src/main/java/eu/europa/ted/efx/model/Markup.java b/src/main/java/eu/europa/ted/efx/model/templates/Markup.java similarity index 80% rename from src/main/java/eu/europa/ted/efx/model/Markup.java rename to src/main/java/eu/europa/ted/efx/model/templates/Markup.java index f525761a..972212dc 100644 --- a/src/main/java/eu/europa/ted/efx/model/Markup.java +++ b/src/main/java/eu/europa/ted/efx/model/templates/Markup.java @@ -1,9 +1,11 @@ -package eu.europa.ted.efx.model; +package eu.europa.ted.efx.model.templates; + +import eu.europa.ted.efx.model.CallStackObject; /** * Represents markup in the target template language. */ -public class Markup extends CallStackObject { +public class Markup implements CallStackObject { /** * Stores the markup script in the target language. diff --git a/src/main/java/eu/europa/ted/efx/model/types/EfxDataType.java b/src/main/java/eu/europa/ted/efx/model/types/EfxDataType.java new file mode 100644 index 00000000..bbcca76f --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/types/EfxDataType.java @@ -0,0 +1,14 @@ +package eu.europa.ted.efx.model.types; + +public interface EfxDataType { + Class ANY = EfxDataType.class; + + public interface Boolean extends EfxDataType {} + public interface String extends EfxDataType {} + public interface MultilingualString extends String {} + public interface Number extends EfxDataType {} + public interface Date extends EfxDataType {} + public interface Duration extends EfxDataType {} + public interface Time extends EfxDataType {} + public interface Node extends EfxDataType {} +} diff --git a/src/main/java/eu/europa/ted/efx/model/types/EfxDataTypeAssociation.java b/src/main/java/eu/europa/ted/efx/model/types/EfxDataTypeAssociation.java new file mode 100644 index 00000000..2fd4fd7b --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/types/EfxDataTypeAssociation.java @@ -0,0 +1,14 @@ +package eu.europa.ted.efx.model.types; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface EfxDataTypeAssociation { + Class dataType(); +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionType.java b/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionType.java new file mode 100644 index 00000000..1f2a7f29 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionType.java @@ -0,0 +1,7 @@ +package eu.europa.ted.efx.model.types; + +public interface EfxExpressionType { + public interface Scalar extends EfxExpressionType {} + public interface Sequence extends EfxExpressionType {} + public interface Path extends EfxExpressionType.Scalar, EfxExpressionType.Sequence {} +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionTypeAssociation.java b/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionTypeAssociation.java new file mode 100644 index 00000000..a6496c6c --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionTypeAssociation.java @@ -0,0 +1,14 @@ +package eu.europa.ted.efx.model.types; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface EfxExpressionTypeAssociation { + Class expressionType(); +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/types/FieldTypes.java b/src/main/java/eu/europa/ted/efx/model/types/FieldTypes.java new file mode 100644 index 00000000..52e47c81 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/types/FieldTypes.java @@ -0,0 +1,47 @@ +package eu.europa.ted.efx.model.types; + +import java.util.HashMap; +import java.util.Map; + +public enum FieldTypes { + ID("id"), // + ID_REF("id-ref"), // + TEXT("text"), // + TEXT_MULTILINGUAL("text-multilingual"), // + INDICATOR("indicator"), // + AMOUNT("amount"), // + NUMBER("number"), // + MEASURE("measure"), // + CODE("code"), // + INTERNAL_CODE("internal-code"), // + INTEGER("integer"), // + DATE("date"), // + ZONED_DATE("zoned-date"), // + TIME("time"), // + ZONED_TIME("zoned-time"), // + URL("url"), // + PHONE("phone"), // + EMAIL("email"); + + private static final Map lookup = new HashMap<>(); + + static { + for (FieldTypes fieldType : FieldTypes.values()) { + lookup.put(fieldType.getName(), fieldType); + } + } + + private String name; + + FieldTypes(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static FieldTypes fromString(String typeName) { + return lookup.get(typeName); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java b/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java new file mode 100644 index 00000000..71538660 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java @@ -0,0 +1,20 @@ +package eu.europa.ted.efx.model.variables; + +import eu.europa.ted.efx.model.CallStackObject; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.types.EfxDataType; + +public class Identifier implements CallStackObject { + final public String name; + final public Class dataType; + final public Expression declarationExpression; + final public TypedExpression referenceExpression; + + public Identifier(String name, Expression declarationExpression, TypedExpression referenceExpression) { + this.name = name; + this.dataType = referenceExpression.getDataType(); + this.declarationExpression = declarationExpression; + this.referenceExpression = referenceExpression; + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Parameter.java b/src/main/java/eu/europa/ted/efx/model/variables/Parameter.java new file mode 100644 index 00000000..299d480e --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/variables/Parameter.java @@ -0,0 +1,14 @@ +package eu.europa.ted.efx.model.variables; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; + +public class Parameter extends Identifier { + + public final TypedExpression parameterValue; + + public Parameter(String parameterName, Expression declarationExpression, TypedExpression referenceExpression, TypedExpression parameterValue) { + super(parameterName, declarationExpression, referenceExpression); + this.parameterValue = parameterValue; + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Variable.java b/src/main/java/eu/europa/ted/efx/model/variables/Variable.java new file mode 100644 index 00000000..746d18f6 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/variables/Variable.java @@ -0,0 +1,20 @@ +package eu.europa.ted.efx.model.variables; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; + +public class Variable extends Identifier { + final public TypedExpression initializationExpression; + + public Variable(String variableName, Expression declarationExpression, TypedExpression initializationExpression, TypedExpression referenceExpression) { + super(variableName, declarationExpression, referenceExpression); + this.initializationExpression = initializationExpression; + assert referenceExpression.getDataType() == initializationExpression.getDataType(); + } + + public Variable(Identifier identifier, TypedExpression initializationExpression) { + super(identifier.name, identifier.declarationExpression, identifier.referenceExpression); + this.initializationExpression = initializationExpression; + assert identifier.dataType == initializationExpression.getDataType(); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/variables/VariableList.java b/src/main/java/eu/europa/ted/efx/model/variables/VariableList.java new file mode 100644 index 00000000..b656a342 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/variables/VariableList.java @@ -0,0 +1,11 @@ +package eu.europa.ted.efx.model.variables; + +import java.util.LinkedList; + +import eu.europa.ted.efx.model.CallStackObject; + +public class VariableList extends LinkedList implements CallStackObject { + + public VariableList() { + } +} 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 1d295254..4a1e0369 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -6,6 +6,7 @@ import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; + import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; @@ -16,37 +17,43 @@ import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; import org.apache.commons.lang3.StringUtils; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.model.CallStack; -import eu.europa.ted.efx.model.CallStackObject; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; import eu.europa.ted.efx.model.ContextStack; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.BooleanExpression; -import eu.europa.ted.efx.model.Expression.BooleanListExpression; -import eu.europa.ted.efx.model.Expression.ContextExpression; -import eu.europa.ted.efx.model.Expression.DateExpression; -import eu.europa.ted.efx.model.Expression.DateListExpression; -import eu.europa.ted.efx.model.Expression.DurationExpression; -import eu.europa.ted.efx.model.Expression.DurationListExpression; -import eu.europa.ted.efx.model.Expression.IteratorExpression; -import eu.europa.ted.efx.model.Expression.IteratorListExpression; -import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.NumericListExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Expression.StringListExpression; -import eu.europa.ted.efx.model.Expression.TimeExpression; -import eu.europa.ted.efx.model.Expression.TimeListExpression; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorListExpression; +import eu.europa.ted.efx.model.expressions.path.NodePathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.path.StringPathExpression; +import eu.europa.ted.efx.model.expressions.scalar.BooleanExpression; +import eu.europa.ted.efx.model.expressions.scalar.DateExpression; +import eu.europa.ted.efx.model.expressions.scalar.DurationExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.TimeExpression; +import eu.europa.ted.efx.model.expressions.sequence.BooleanSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.DateSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.DurationSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.NumericSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.SequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.StringSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.TimeSequenceExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.FieldTypes; +import eu.europa.ted.efx.model.variables.Parameter; +import eu.europa.ted.efx.model.variables.Variable; import eu.europa.ted.efx.sdk1.EfxParser.*; -import eu.europa.ted.efx.xpath.XPathAttributeLocator; /** * The the goal of the EfxExpressionTranslator is to take an EFX expression and translate it to a @@ -174,7 +181,7 @@ private T translateParameter(final String parameterValue, private String getTranslatedScript() { final StringBuilder sb = new StringBuilder(this.stack.size() * 100); while (!this.stack.empty()) { - sb.insert(0, '\n').insert(0, this.stack.pop(Expression.class).script); + sb.insert(0, '\n').insert(0, this.stack.pop(Expression.class).getScript()); } return sb.toString().trim(); } @@ -183,8 +190,8 @@ private String getTranslatedScript() { * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleFieldReferenceContext} to locate a field identifier. * - * @param ctx the context to search in. - * @return the field identifier or null if none was found. + * @param ctx The context to start from. + * @return The field identifier, or null if none was found. */ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRuleContext ctx) { @@ -231,8 +238,8 @@ protected static String getFieldIdFromChildSimpleFieldReferenceContext(ParserRul * Helper method that starts from a given {@link ParserRuleContext} and recursively searches for a * {@link SimpleNodeReferenceContext} to locate a node identifier. * - * @param ctx the context to search in. - * @return the node identifier or null if none was found. + * @param ctx The context to start from. + * @return The node identifier, or null if none was found. */ protected static String getNodeIdFromChildSimpleNodeReferenceContext(ParserRuleContext ctx) { @@ -270,7 +277,7 @@ public void exitSingleExpression(SingleExpressionContext ctx) { this.efxContext.pop(); } - /*** Boolean expressions ***/ + // #region Boolean expressions ---------------------------------------------- @Override public void exitParenthesizedBooleanExpression( @@ -293,13 +300,13 @@ public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { this.stack.push(this.script.composeLogicalOr(left, right)); } - /*** Boolean expressions - Comparisons ***/ + // #region Boolean expressions - Comparisons -------------------------------- @Override public void exitFieldValueComparison(FieldValueComparisonContext ctx) { - Expression right = this.stack.pop(Expression.class); - Expression left = this.stack.pop(Expression.class); - if (!left.getClass().isAssignableFrom(right.getClass()) && !right.getClass().isAssignableFrom(left.getClass())) { + ScalarExpression right = this.stack.pop(ScalarExpression.class); + ScalarExpression left = this.stack.pop(ScalarExpression.class); + if (!left.getClass().isAssignableFrom(right.getClass()) && !right.getClass().isAssignableFrom(left.getClass()) && !left.getDataType().isAssignableFrom(right.getDataType()) && !right.getDataType().isAssignableFrom(left.getDataType()) && !right.getDataType().isAssignableFrom(left.getDataType()) && !left.getDataType().isAssignableFrom(right.getDataType())) { throw new ParseCancellationException(TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES + left.getClass() + " and " + right.getClass()); } @@ -348,13 +355,14 @@ public void exitDurationComparison(DurationComparisonContext ctx) { this.stack.push(this.script.composeComparisonOperation(left, ctx.operator.getText(), right)); } - /*** Boolean expressions - Conditions ***/ + // #endregion Boolean expressions - Comparisons ----------------------------- + + // #region Boolean expressions - Conditions -------------------------------- @Override public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); - String operator = - ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; + String operator = ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; this.stack.push(this.script.composeComparisonOperation(expression, operator, this.script.getStringLiteralFromUnquotedString(""))); } @@ -372,7 +380,7 @@ public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { @Override public void exitUniqueValueCondition(EfxParser.UniqueValueConditionContext ctx) { PathExpression haystack = this.stack.pop(PathExpression.class); - PathExpression needle = this.stack.pop(PathExpression.class); + PathExpression needle = this.stack.pop(haystack.getClass()); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { this.stack.push( @@ -385,51 +393,51 @@ public void exitUniqueValueCondition(EfxParser.UniqueValueConditionContext ctx) @Override public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); - - BooleanExpression condition = - this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); + BooleanExpression condition = this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { condition = this.script.composeLogicalNot(condition); } this.stack.push(condition); } - /*** Boolean expressions - List membership conditions ***/ + // #endregion Boolean expressions - Conditions ------------------------------ + + // #region Boolean expressions - List membership conditions ----------------- @Override public void exitStringInListCondition(EfxParser.StringInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, StringExpression.class, StringListExpression.class); + this.exitInListCondition(ctx.modifier, StringExpression.class, StringSequenceExpression.class); } @Override public void exitBooleanInListCondition(BooleanInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, BooleanExpression.class, BooleanListExpression.class); + this.exitInListCondition(ctx.modifier, BooleanExpression.class, BooleanSequenceExpression.class); } @Override public void exitNumberInListCondition(NumberInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, NumericExpression.class, NumericListExpression.class); + this.exitInListCondition(ctx.modifier, NumericExpression.class, NumericSequenceExpression.class); } @Override public void exitDateInListCondition(DateInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, DateExpression.class, DateListExpression.class); + this.exitInListCondition(ctx.modifier, DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeInListCondition(TimeInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, TimeExpression.class, TimeListExpression.class); + this.exitInListCondition(ctx.modifier, TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationInListCondition(DurationInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, DurationExpression.class, DurationListExpression.class); + this.exitInListCondition(ctx.modifier, DurationExpression.class, DurationSequenceExpression.class); } - private > void exitInListCondition( - Token modifier, Class expressionType, Class listType) { - ListExpression list = this.stack.pop(listType); - T expression = this.stack.pop(expressionType); + private void exitInListCondition( + Token modifier, Class expressionType, Class listType) { + SequenceExpression list = this.stack.pop(listType); + ScalarExpression expression = this.stack.pop(expressionType); BooleanExpression condition = this.script.composeContainsCondition(expression, list); if (modifier != null && modifier.getText().equals(NOT_MODIFIER)) { condition = this.script.composeLogicalNot(condition); @@ -437,7 +445,11 @@ private > void exitInListCondi this.stack.push(condition); } - /*** Quantified expressions ***/ + // #endregion Boolean expressions - List membership conditions ----------------- + + // #endregion Boolean expressions ------------------------------------------- + + // #region Quantified expressions ------------------------------------------- @Override public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { @@ -451,7 +463,9 @@ public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { } } - /*** Numeric expressions ***/ + // #endregion Quantified expressions ---------------------------------------- + + // #region Numeric expressions ---------------------------------------------- @Override public void exitAdditionExpression(EfxParser.AdditionExpressionContext ctx) { @@ -473,7 +487,9 @@ public void exitParenthesizedNumericExpression(ParenthesizedNumericExpressionCon this.stack.pop(NumericExpression.class), NumericExpression.class)); } - /*** Duration Expressions ***/ + // #endregion Numeric expressions ------------------------------------------- + + // #region Duration Expressions --------------------------------------------- @Override public void exitDurationAdditionExpression(DurationAdditionExpressionContext ctx) { @@ -515,47 +531,47 @@ public void exitDateSubtractionExpression(DateSubtractionExpressionContext ctx) @Override public void exitCodeList(CodeListContext ctx) { if (this.stack.empty()) { - this.stack.push(this.script.composeList(Collections.emptyList(), StringListExpression.class)); + this.stack.push(this.script.composeList(Collections.emptyList(), StringSequenceExpression.class)); } } @Override public void exitStringList(StringListContext ctx) { this.exitList(ctx.stringExpression().size(), StringExpression.class, - StringListExpression.class); + StringSequenceExpression.class); } @Override public void exitBooleanList(BooleanListContext ctx) { this.exitList(ctx.booleanExpression().size(), BooleanExpression.class, - BooleanListExpression.class); + BooleanSequenceExpression.class); } @Override public void exitNumericList(NumericListContext ctx) { this.exitList(ctx.numericExpression().size(), NumericExpression.class, - NumericListExpression.class); + NumericSequenceExpression.class); } @Override public void exitDateList(DateListContext ctx) { - this.exitList(ctx.dateExpression().size(), DateExpression.class, DateListExpression.class); + this.exitList(ctx.dateExpression().size(), DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeList(TimeListContext ctx) { - this.exitList(ctx.timeExpression().size(), TimeExpression.class, TimeListExpression.class); + this.exitList(ctx.timeExpression().size(), TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationList(DurationListContext ctx) { this.exitList(ctx.durationExpression().size(), DurationExpression.class, - DurationListExpression.class); + DurationSequenceExpression.class); } - private > void exitList(int listSize, - Class expressionType, Class listType) { + private void exitList(int listSize, + Class expressionType, Class listType) { if (this.stack.empty() || listSize == 0) { this.stack.push(this.script.composeList(Collections.emptyList(), listType)); return; @@ -568,22 +584,26 @@ private > void exitList(int li this.stack.push(this.script.composeList(list, listType)); } - /*** Conditional Expressions ***/ + // #endregion Duration Expressions ------------------------------------------ + + // #region Conditional Expressions ------------------------------------------ @Override public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) { - Class typeWhenFalse = this.stack.peek().getClass(); - if (typeWhenFalse == BooleanExpression.class) { + var topOfStack = this.stack.peek(); + assert topOfStack instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class typeWhenFalse = ((TypedExpression)topOfStack).getDataType(); + if (typeWhenFalse == EfxDataType.Boolean.class) { this.exitConditionalBooleanExpression(); - } else if (typeWhenFalse == NumericExpression.class) { + } else if (typeWhenFalse == EfxDataType.Number.class) { this.exitConditionalNumericExpression(); - } else if (typeWhenFalse == StringExpression.class) { + } else if (typeWhenFalse == EfxDataType.String.class) { this.exitConditionalStringExpression(); - } else if (typeWhenFalse == DateExpression.class) { + } else if (typeWhenFalse == EfxDataType.Date.class) { this.exitConditionalDateExpression(); - } else if (typeWhenFalse == TimeExpression.class) { + } else if (typeWhenFalse == EfxDataType.Time.class) { this.exitConditionalTimeExpression(); - } else if (typeWhenFalse == DurationExpression.class) { + } else if (typeWhenFalse == EfxDataType.Duration.class) { this.exitConditionalDurationExpression(); } else { throw new IllegalStateException("Unknown type " + typeWhenFalse); @@ -668,54 +688,63 @@ private void exitConditionalDurationExpression() { DurationExpression.class)); } - /*** Iterators ***/ + // #endregion Conditional Expressions --------------------------------------- + + // #region Iterators -------------------------------------------------------- @Override public void exitStringIteratorExpression(StringIteratorExpressionContext ctx) { - this.exitIteratorExpression(StringExpression.class, StringListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.stringVariableDeclaration()), StringExpression.class, StringSequenceExpression.class); } @Override public void exitBooleanIteratorExpression(BooleanIteratorExpressionContext ctx) { - this.exitIteratorExpression(BooleanExpression.class, BooleanListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.booleanVariableDeclaration()), BooleanExpression.class, BooleanSequenceExpression.class); } @Override public void exitNumericIteratorExpression(NumericIteratorExpressionContext ctx) { - this.exitIteratorExpression(NumericExpression.class, NumericListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.numericVariableDeclaration()), NumericExpression.class, NumericSequenceExpression.class); } @Override public void exitDateIteratorExpression(DateIteratorExpressionContext ctx) { - this.exitIteratorExpression(DateExpression.class, DateListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.dateVariableDeclaration()), DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeIteratorExpression(TimeIteratorExpressionContext ctx) { - this.exitIteratorExpression(TimeExpression.class, TimeListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.timeVariableDeclaration()), TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationIteratorExpression(DurationIteratorExpressionContext ctx) { - this.exitIteratorExpression(DurationExpression.class, DurationListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.durationVariableDeclaration()), DurationExpression.class, DurationSequenceExpression.class); } @Override public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); - ContextExpression variable = this.stack.pop(ContextExpression.class); - this.stack.push(this.script.composeIteratorExpression(variable.script, path)); + + var variableType = path.getClass(); + var variableName = getVariableName(ctx.contextVariableDeclaration()); + Variable variable = new Variable(variableName, + this.script.composeVariableDeclaration(variableName, variableType), + Expression.empty(variableType), + this.script.composeVariableReference(variableName, variableType)); + this.stack.declareIdentifier(variable); + + this.stack.push(this.script.composeIteratorExpression(variable.declarationExpression, path)); if (ctx.fieldContext() != null) { final String contextFieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx.fieldContext()); - this.efxContext.declareContextVariable(variable.script, + this.efxContext.declareContextVariable(variable.name, new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); - } else if (ctx.nodeContext() != null) { final String contextNodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx.nodeContext()); - this.efxContext.declareContextVariable(variable.script, + this.efxContext.declareContextVariable(variable.name, new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); } @@ -733,88 +762,94 @@ public void exitIteratorList(IteratorListContext ctx) { @Override public void exitParenthesizedStringsFromIteration(ParenthesizedStringsFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(StringListExpression.class), StringListExpression.class)); + this.stack.pop(StringSequenceExpression.class), StringSequenceExpression.class)); } @Override public void exitParenthesizedNumbersFromIteration(ParenthesizedNumbersFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(NumericListExpression.class), NumericListExpression.class)); + this.stack.pop(NumericSequenceExpression.class), NumericSequenceExpression.class)); } @Override public void exitParenthesizedBooleansFromIteration( ParenthesizedBooleansFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(BooleanListExpression.class), BooleanListExpression.class)); + this.stack.pop(BooleanSequenceExpression.class), BooleanSequenceExpression.class)); } @Override public void exitParenthesizedDatesFromIteration(ParenthesizedDatesFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(DateListExpression.class), DateListExpression.class)); + this.stack.pop(DateSequenceExpression.class), DateSequenceExpression.class)); } @Override public void exitParenthesizedTimesFromIteration(ParenthesizedTimesFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(TimeListExpression.class), TimeListExpression.class)); + this.stack.pop(TimeSequenceExpression.class), TimeSequenceExpression.class)); } @Override public void exitParenthesizedDurationsFromITeration( ParenthesizedDurationsFromITerationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(DurationListExpression.class), DurationListExpression.class)); + this.stack.pop(DurationSequenceExpression.class), DurationSequenceExpression.class)); } @Override public void exitStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class); + this.exitIterationExpression(StringExpression.class, StringSequenceExpression.class); } @Override public void exitNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class); + this.exitIterationExpression(NumericExpression.class, NumericSequenceExpression.class); } @Override public void exitBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class); + this.exitIterationExpression(BooleanExpression.class, BooleanSequenceExpression.class); } @Override public void exitDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class); + this.exitIterationExpression(DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class); + this.exitIterationExpression(TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class); + this.exitIterationExpression(DurationExpression.class, DurationSequenceExpression.class); } - public > void exitIteratorExpression( - Class variableType, Class listType) { - L list = this.stack.pop(listType); - T variable = this.stack.pop(variableType); - this.stack.push(this.script.composeIteratorExpression(variable.script, list)); + public void exitIteratorExpression(String variableName, + Class variableType, Class listType) { + Expression declarationExpression = this.script.composeVariableDeclaration(variableName, variableType); + SequenceExpression initialisationExpression = this.stack.pop(listType); + ScalarExpression referenceExpression = this.script.composeVariableReference(variableName, variableType); + Variable variable = new Variable(variableName, declarationExpression, initialisationExpression, + referenceExpression); + this.stack.declareIdentifier(variable); + this.stack.push(this.script.composeIteratorExpression(variable.declarationExpression, initialisationExpression)); } - public > void exitIterationExpression( + public void exitIterationExpression( Class expressionType, - Class targetListType) { + Class targetListType) { T expression = this.stack.pop(expressionType); IteratorListExpression iterators = this.stack.pop(IteratorListExpression.class); this.stack .push(this.script.composeForExpression(iterators, expression, targetListType)); } - /*** Literals ***/ + // #endregion Iterators ----------------------------------------------------- + + // #region Literals --------------------------------------------------------- @Override public void exitNumericLiteral(NumericLiteralContext ctx) { @@ -851,7 +886,9 @@ public void exitDurationLiteral(DurationLiteralContext ctx) { this.stack.push(this.script.getDurationLiteralEquivalent(ctx.getText())); } - /*** References ***/ + // #endregion Literals ------------------------------------------------------ + + // #region References ------------------------------------------------------- @Override public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { @@ -894,15 +931,14 @@ public void exitAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { } - /*** References with Predicates ***/ + // #region References with Predicates --------------------------------------- @Override public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx) { if (ctx.predicate() != null) { BooleanExpression predicate = this.stack.pop(BooleanExpression.class); - PathExpression nodeReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate, - PathExpression.class)); + PathExpression nodeReference = this.stack.pop(NodePathExpression.class); + this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate)); } } @@ -911,8 +947,7 @@ public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicat if (ctx.predicate() != null) { BooleanExpression predicate = this.stack.pop(BooleanExpression.class); PathExpression fieldReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeFieldReferenceWithPredicate(fieldReference, predicate, - PathExpression.class)); + this.stack.push(this.script.composeFieldReferenceWithPredicate(fieldReference, predicate)); } } @@ -946,11 +981,13 @@ public void exitPredicate(EfxParser.PredicateContext ctx) { public void exitFieldReferenceWithAxis(FieldReferenceWithAxisContext ctx) { if (ctx.axis() != null) { this.stack.push(this.script.composeFieldReferenceWithAxis( - this.stack.pop(PathExpression.class), ctx.axis().Axis().getText(), PathExpression.class)); + this.stack.pop(PathExpression.class), ctx.axis().Axis().getText())); } } - /*** External References ***/ + // #endregion References with Predicates ------------------------------------ + + // #region External References ---------------------------------------------- @Override public void exitNoticeReference(EfxParser.NoticeReferenceContext ctx) { @@ -977,25 +1014,23 @@ public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNotic } } - /*** Value References ***/ + // #endregion External References ------------------------------------------- + + // #region Value References ------------------------------------------------- @Override public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - // TODO: Use an interface for locating attributes. A PathExpression is not necessarily an - // XPath in every implementation. - XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); - - if (parsedPath.hasAttribute()) { - this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), - parsedPath.getAttribute(), Expression.types.get(this.symbols.getTypeOfField(fieldId)))); - } else if (fieldId != null) { - this.stack.push(this.script.composeFieldValueReference(path, - Expression.types.get(this.symbols.getTypeOfField(fieldId)))); + if (this.symbols.isAttributeField(fieldId)) { + this.stack.push(this.script.composeFieldAttributeReference( + this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), + this.efxContext.peek().absolutePath()), + this.symbols.getAttributeOfField(fieldId), + PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { - this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); + this.stack.push(this.script.composeFieldValueReference(path)); } } @@ -1003,29 +1038,32 @@ public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - // TODO: Use an interface for locating attributes. A PathExpression is not necessarily an - // XPath in every implementation. - XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); - - if (parsedPath.hasAttribute()) { - this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), - parsedPath.getAttribute(), - Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); - } else if (fieldId != null) { - this.stack.push(this.script.composeFieldValueReference(path, - Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); + if (this.symbols.isAttributeField(fieldId)) { + this.stack.push(this.script.composeFieldAttributeReference( + this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), + this.efxContext.peek().absolutePath()), + this.symbols.getAttributeOfField(fieldId), + PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { - this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); + this.stack.push(this.script.composeFieldValueReference(path)); } } @Override public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - ctx.attributeReference().Identifier().getText(), StringExpression.class)); + ctx.attributeReference().Identifier().getText(), StringPathExpression.class)); + } + + @Override + public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceContext ctx) { + this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), + ctx.attributeReference().Identifier().getText(), StringPathExpression.class)); } - /*** References with context override ***/ + // #endregion Value References ---------------------------------------------- + + // #region References with context override --------------------------------- /** * Handles expressions of the form ContextField::ReferencedField. Changes the context before the @@ -1086,8 +1124,7 @@ public void exitFieldReferenceWithNodeContextOverride( @Override public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { - ContextExpression variableName = this.stack.pop(ContextExpression.class); - Context variableContext = this.efxContext.getContextFromVariable(variableName.script); + Context variableContext = this.efxContext.getContextFromVariable(getVariableName(ctx.variableReference())); if (variableContext.isFieldContext()) { this.efxContext.push(new FieldContext(variableContext.symbol(), this.symbols.getAbsolutePathOfField(variableContext.symbol()), this.symbols @@ -1099,7 +1136,6 @@ public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { } else { throw new IllegalStateException("Variable context is neither a field nor a node context."); } - this.stack.push(variableName); } @Override @@ -1107,123 +1143,81 @@ public void exitFieldReferenceWithVariableContextOverride( FieldReferenceWithVariableContextOverrideContext ctx) { if (ctx.contextVariableSpecifier() != null) { final PathExpression field = this.stack.pop(PathExpression.class); - final ContextExpression variableName = this.stack.pop(ContextExpression.class); - this.stack.push(this.script.joinPaths(new PathExpression(variableName.script), field)); + final PathExpression contextVariable = this.stack.pop(PathExpression.class); + this.stack.push(this.script.joinPaths(contextVariable, field)); this.efxContext.pop(); // Restores the previous context } } - /*** Other References ***/ + // #endregion References with context override ------------------------------ + + // #region Other References ------------------------------------------------- @Override public void exitCodelistReference(CodelistReferenceContext ctx) { this.stack.push(this.script.composeList(this.symbols.expandCodelist(ctx.codeListId.getText()) .stream().map(s -> this.script.getStringLiteralFromUnquotedString(s)) - .collect(Collectors.toList()), StringListExpression.class)); + .collect(Collectors.toList()), StringSequenceExpression.class)); } @Override public void exitVariableReference(VariableReferenceContext ctx) { - this.stack.pushVariableReference(this.getVariableName(ctx), - this.script.composeVariableReference(this.getVariableName(ctx), Expression.class)); + String variableName = getVariableName(ctx); + this.stack.pushIdentifierReference(variableName); } + + // #endregion Other References ---------------------------------------------- + + // #endregion References ---------------------------------------------------- - /*** Parameter Declarations ***/ + // #region Parameter Declarations ------------------------------------------- @Override public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), StringExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), StringExpression.class); } @Override public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), NumericExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), NumericExpression.class); } @Override public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), BooleanExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), BooleanExpression.class); } @Override public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), DateExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), DateExpression.class); } @Override public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), TimeExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), TimeExpression.class); } @Override public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) { - this.exitParameterDeclaration(this.getVariableName(ctx), DurationExpression.class); + this.exitParameterDeclaration(getVariableName(ctx), DurationExpression.class); } - private void exitParameterDeclaration(String parameterName, - Class parameterType) { + private void exitParameterDeclaration(String parameterName, Class parameterType) { if (this.expressionParameters.isEmpty()) { throw new ParseCancellationException("No parameter passed for " + parameterName); } - this.stack.pushParameterDeclaration(parameterName, + Parameter parameter = new Parameter(parameterName, this.script.composeParameterDeclaration(parameterName, parameterType), + this.script.composeVariableReference(parameterName, parameterType), this.translateParameter(this.expressionParameters.pop(), parameterType)); + this.stack.declareIdentifier(parameter); } - /*** Variable Declarations ***/ - - @Override - public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, StringExpression.class)); - } - - @Override - public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, BooleanExpression.class)); - } - - @Override - public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, NumericExpression.class)); - } + // #endregion Parameter Declarations ---------------------------------------- - @Override - public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, DateExpression.class)); - } - - @Override - public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, TimeExpression.class)); - } - - @Override - public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, DurationExpression.class)); - } - - @Override - public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) { - String variableName = this.getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, ContextExpression.class)); - } - - /*** Boolean functions ***/ + // #region Boolean functions ------------------------------------------------ @Override public void exitNotFunction(NotFunctionContext ctx) { @@ -1253,16 +1247,18 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) { @Override public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { - final ListExpression two = this.stack.pop(ListExpression.class); - final ListExpression one = this.stack.pop(ListExpression.class); + final SequenceExpression two = this.stack.pop(SequenceExpression.class); + final SequenceExpression one = this.stack.pop(SequenceExpression.class); this.stack.push(this.script.composeSequenceEqualFunction(one, two)); } - /*** Numeric functions ***/ + // #endregion Boolean functions --------------------------------------------- + + // #region Numeric functions ------------------------------------------------ @Override public void exitCountFunction(CountFunctionContext ctx) { - final ListExpression expression = this.stack.pop(ListExpression.class); + final SequenceExpression expression = this.stack.pop(SequenceExpression.class); this.stack.push(this.script.composeCountOperation(expression)); } @@ -1273,7 +1269,7 @@ public void exitNumberFunction(NumberFunctionContext ctx) { @Override public void exitSumFunction(SumFunctionContext ctx) { - this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericListExpression.class))); + this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericSequenceExpression.class))); } @Override @@ -1282,7 +1278,9 @@ public void exitStringLengthFunction(StringLengthFunctionContext ctx) { .push(this.script.composeStringLengthCalculation(this.stack.pop(StringExpression.class))); } - /*** String functions ***/ + // #endregion Numeric functions --------------------------------------------- + + // #region String functions ------------------------------------------------- @Override public void exitSubstringFunction(SubstringFunctionContext ctx) { @@ -1323,7 +1321,9 @@ public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { this.stack.push(this.script.composeNumberFormatting(number, format)); } - /*** Date functions ***/ + // #endregion String functions ---------------------------------------------- + + // #region Date functions --------------------------------------------------- @Override public void exitDateFromStringFunction(DateFromStringFunctionContext ctx) { @@ -1344,14 +1344,18 @@ public void exitDateMinusMeasureFunction(DateMinusMeasureFunctionContext ctx) { this.stack.push(this.script.composeSubtraction(left, right)); } - /*** Time functions ***/ + // #endregion Date functions ------------------------------------------------ + + // #region Time functions --------------------------------------------------- @Override public void exitTimeFromStringFunction(TimeFromStringFunctionContext ctx) { this.stack.push(this.script.composeToTimeConversion(this.stack.pop(StringExpression.class))); } - /*** Duration Functions ***/ + // #endregion Time functions ---------------------------------------------- + + // #region Duration Functions ----------------------------------------------- @Override public void exitDayTimeDurationFromStringFunction(DayTimeDurationFromStringFunctionContext ctx) { @@ -1366,176 +1370,199 @@ public void exitYearMonthDurationFromStringFunction( this.script.composeToYearMonthDurationConversion(this.stack.pop(StringExpression.class))); } - /*** Sequence Functions ***/ + // #endregion Duration Functions -------------------------------------------- + + // #region Sequence Functions ----------------------------------------------- @Override public void exitDistinctValuesFunction(DistinctValuesFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitDistinctValuesFunction( - Class listType) { - final L list = this.stack.pop(listType); + private void exitDistinctValuesFunction( + Class listType) { + final T list = this.stack.pop(listType); this.stack.push(this.script.composeDistinctValuesFunction(list, listType)); } @Override public void exitUnionFunction(UnionFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitUnionFunction( - Class listType) { - final L two = this.stack.pop(listType); - final L one = this.stack.pop(listType); + private void exitUnionFunction( + Class listType) { + final T two = this.stack.pop(listType); + final T one = this.stack.pop(listType); this.stack.push(this.script.composeUnionFunction(one, two, listType)); } @Override public void exitIntersectFunction(IntersectFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitIntersectFunction( - Class listType) { - final L two = this.stack.pop(listType); - final L one = this.stack.pop(listType); + private void exitIntersectFunction( + Class listType) { + final T two = this.stack.pop(listType); + final T one = this.stack.pop(listType); this.stack.push(this.script.composeIntersectFunction(one, two, listType)); } @Override public void exitExceptFunction(ExceptFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitExceptFunction( - Class listType) { - final L two = this.stack.pop(listType); - final L one = this.stack.pop(listType); + private void exitExceptFunction( + Class listType) { + final T two = this.stack.pop(listType); + final T one = this.stack.pop(listType); this.stack.push(this.script.composeExceptFunction(one, two, listType)); } + + // #endregion Sequence Functions -------------------------------------------- - private String getVariableName(String efxVariableIdentifier) { + // #region Helpers ---------------------------------------------------------- + + protected static String getLexerSymbol(int tokenType) { + return EfxLexer.VOCABULARY.getLiteralName(tokenType).replaceAll("^'|'$", ""); + } + // #region Variable Names --------------------------------------------------- + + static private String getVariableName(String efxVariableIdentifier) { return StringUtils.stripStart(efxVariableIdentifier, "$"); } - private String getVariableName(VariableReferenceContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(VariableReferenceContext ctx) { + return getVariableName(ctx.Variable().getText()); } - protected String getVariableName(ContextVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static protected String getVariableName(ContextVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(StringVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(StringVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(NumericVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(NumericVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(BooleanVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(BooleanVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DateVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DateVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(TimeVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(TimeVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DurationVariableDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DurationVariableDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(StringParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(StringParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(NumericParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(NumericParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(BooleanParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(BooleanParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DateParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DateParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(TimeParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(TimeParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } - private String getVariableName(DurationParameterDeclarationContext ctx) { - return this.getVariableName(ctx.Variable().getText()); + static private String getVariableName(DurationParameterDeclarationContext ctx) { + return getVariableName(ctx.Variable().getText()); } + + // #endregion Variable Names --------------------------------------------------- + + // #endregion Helpers ------------------------------------------------------- } 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 0f5b7523..a303a7f5 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; + import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; @@ -14,25 +15,43 @@ import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; 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.model.ContentBlock; -import eu.europa.ted.efx.model.ContentBlockStack; import eu.europa.ted.efx.model.Context; import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Expression.StringListExpression; -import eu.europa.ted.efx.model.Markup; -import eu.europa.ted.efx.model.VariableList; -import eu.europa.ted.efx.sdk1.EfxParser.*; -import eu.europa.ted.efx.xpath.XPathAttributeLocator; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.path.StringPathExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.sequence.StringSequenceExpression; +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.variables.Variable; +import eu.europa.ted.efx.model.variables.VariableList; +import eu.europa.ted.efx.sdk1.EfxParser.AssetIdContext; +import eu.europa.ted.efx.sdk1.EfxParser.AssetTypeContext; +import eu.europa.ted.efx.sdk1.EfxParser.ContextDeclarationBlockContext; +import eu.europa.ted.efx.sdk1.EfxParser.ExpressionTemplateContext; +import eu.europa.ted.efx.sdk1.EfxParser.LabelTemplateContext; +import eu.europa.ted.efx.sdk1.EfxParser.LabelTypeContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandBtLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldValueReferenceFromContextFieldContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceFromContextFieldContext; +import eu.europa.ted.efx.sdk1.EfxParser.ShorthandLabelReferenceFromContextContext; +import eu.europa.ted.efx.sdk1.EfxParser.StandardExpressionBlockContext; +import eu.europa.ted.efx.sdk1.EfxParser.StandardLabelReferenceContext; +import eu.europa.ted.efx.sdk1.EfxParser.TemplateFileContext; +import eu.europa.ted.efx.sdk1.EfxParser.TemplateLineContext; +import eu.europa.ted.efx.sdk1.EfxParser.TextTemplateContext; /** * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV1} to provide additional @@ -55,23 +74,14 @@ public class EfxTemplateTranslatorV1 extends EfxExpressionTranslatorV1 "Do not mix indentation methods. Stick with either tabs or spaces."; private static final String UNEXPECTED_INDENTATION = "Unexpected indentation tracker state."; - private static final String LABEL_TYPE_NAME = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.LABEL_TYPE_NAME).replaceAll("^'|'$", ""); - private static final String LABEL_TYPE_WHEN = EfxLexer.VOCABULARY - .getLiteralName(EfxLexer.LABEL_TYPE_WHEN_TRUE).replaceAll("^'|'$", "").replace("-true", ""); - private static final String SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ValueKeyword).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_INDICATOR = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_INDICATOR).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_BT = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_BT).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_FIELD = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_FIELD).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_NODE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_NODE).replaceAll("^'|'$", ""); - private static final String ASSET_TYPE_CODE = - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ASSET_TYPE_CODE).replaceAll("^'|'$", ""); - + private static final String LABEL_TYPE_NAME = getLexerSymbol(EfxLexer.LABEL_TYPE_NAME); + private static final String LABEL_TYPE_WHEN = getLexerSymbol(EfxLexer.LABEL_TYPE_WHEN_TRUE).replace("-true", ""); + private static final String SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE = getLexerSymbol(EfxLexer.ValueKeyword); + private static final String ASSET_TYPE_INDICATOR = getLexerSymbol(EfxLexer.ASSET_TYPE_INDICATOR); + private static final String ASSET_TYPE_BT = getLexerSymbol(EfxLexer.ASSET_TYPE_BT); + private static final String ASSET_TYPE_FIELD = getLexerSymbol(EfxLexer.ASSET_TYPE_FIELD); + private static final String ASSET_TYPE_NODE = getLexerSymbol(EfxLexer.ASSET_TYPE_NODE); + private static final String ASSET_TYPE_CODE = getLexerSymbol(EfxLexer.ASSET_TYPE_CODE); /** * Used to control the indentation style used in a template @@ -178,7 +188,7 @@ private String getTranslatedMarkup() { return sb.toString().trim(); } - /*** Template File ***/ + // #region Template File ---------------------------------------------------- @Override public void enterTemplateFile(TemplateFileContext ctx) { @@ -199,7 +209,9 @@ public void exitTemplateFile(TemplateFileContext ctx) { this.stack.push(file); } - /*** Source template blocks ***/ + // #endregion Template File ------------------------------------------------- + + // #region Source template blocks ------------------------------------------- @Override public void exitTextTemplate(TextTemplateContext ctx) { @@ -226,7 +238,9 @@ public void exitExpressionTemplate(ExpressionTemplateContext ctx) { } - /*** Label Blocks #{...} ***/ + // #endregion Source template blocks ---------------------------------------- + + // #region Label Blocks #{...} ---------------------------------------------- @Override public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { @@ -236,6 +250,7 @@ public void exitStandardLabelReference(StandardLabelReferenceContext ctx) { : this.script.getStringLiteralFromUnquotedString(""); StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class) : this.script.getStringLiteralFromUnquotedString(""); + this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( List.of(assetType, this.script.getStringLiteralFromUnquotedString("|"), labelType, this.script.getStringLiteralFromUnquotedString("|"), assetId)))); @@ -258,7 +273,7 @@ public void exitShorthandFieldLabelReference(ShorthandFieldLabelReferenceContext StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class) : this.script.getStringLiteralFromUnquotedString(""); - if (labelType.script.equals("value")) { + if (labelType.getScript().equals("value")) { this.shorthandIndirectLabelReference(fieldId); } else { this.stack.push(this.markup.renderLabelFromKey(this.script.composeStringConcatenation( @@ -277,39 +292,37 @@ public void exitShorthandIndirectLabelReference(ShorthandIndirectLabelReferenceC private void shorthandIndirectLabelReference(final String fieldId) { final Context currentContext = this.efxContext.peek(); final String fieldType = this.symbols.getTypeOfField(fieldId); - final XPathAttributeLocator parsedPath = - XPathAttributeLocator.findAttribute(symbols.getAbsolutePathOfField(fieldId)); - final PathExpression valueReference = parsedPath.hasAttribute() + final PathExpression valueReference = this.symbols.isAttributeField(fieldId) ? this.script.composeFieldAttributeReference( - symbols.getRelativePath(parsedPath.getPath(), currentContext.absolutePath()), - parsedPath.getAttribute(), PathExpression.class) + this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), currentContext.absolutePath()), + this.symbols.getAttributeOfField(fieldId), StringPathExpression.class) : this.script.composeFieldValueReference( - symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()), - PathExpression.class); - final StringExpression loopVariable = - this.script.composeVariableReference("item", StringExpression.class); + this.symbols.getRelativePathOfField(fieldId, currentContext.absolutePath())); + Variable loopVariable = new Variable("item", + this.script.composeVariableDeclaration("item", StringExpression.class), StringExpression.empty(), + this.script.composeVariableReference("item", StringExpression.class)); switch (fieldType) { case "indicator": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( List.of( - this.script.composeIteratorExpression(loopVariable.script, valueReference))), + this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))), this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN), this.script.getStringLiteralFromUnquotedString("-"), - loopVariable, + new StringExpression(loopVariable.referenceExpression.getScript()), this.script.getStringLiteralFromUnquotedString("|"), this.script.getStringLiteralFromUnquotedString(fieldId))), - StringListExpression.class))); + StringSequenceExpression.class))); break; case "code": case "internal-code": this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression( this.script.composeIteratorList( List.of( - this.script.composeIteratorExpression(loopVariable.script, valueReference))), + this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))), this.script.composeStringConcatenation(List.of( this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), this.script.getStringLiteralFromUnquotedString("|"), @@ -318,8 +331,8 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.getStringLiteralFromUnquotedString( this.symbols.getRootCodelistOfField(fieldId)), this.script.getStringLiteralFromUnquotedString("."), - loopVariable)), - StringListExpression.class))); + new StringExpression(loopVariable.referenceExpression.getScript()))), + StringSequenceExpression.class))); break; default: throw new ParseCancellationException(String.format( @@ -337,8 +350,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { * context. */ @Override - public void exitShorthandLabelReferenceFromContext( - ShorthandLabelReferenceFromContextContext ctx) { + public void exitShorthandLabelReferenceFromContext(ShorthandLabelReferenceFromContextContext ctx) { final String labelType = ctx.LabelType().getText(); if (this.efxContext.isFieldContext()) { if (labelType.equals(SHORTHAND_CONTEXT_FIELD_LABEL_REFERENCE)) { @@ -371,7 +383,7 @@ public void exitShorthandIndirectLabelReferenceFromContextField( ShorthandIndirectLabelReferenceFromContextFieldContext ctx) { if (!this.efxContext.isFieldContext()) { throw new ParseCancellationException( - "The #value shorthand syntax can only be used in a field is declared as context."); + "The #value shorthand syntax can only be used if a field is declared as context."); } this.shorthandIndirectLabelReference(this.efxContext.symbol()); } @@ -397,7 +409,9 @@ public void exitAssetId(AssetIdContext ctx) { } } - /*** Expression Blocks ${...} ***/ + // #endregion Label Blocks #{...} ------------------------------------------- + + // #region Expression Blocks ${...} ----------------------------------------- /** * Handles a standard expression block in a template line. Most of the work is done by the base @@ -421,11 +435,12 @@ public void exitShorthandFieldValueReferenceFromContextField( "The $value shorthand syntax can only be used when a field is declared as the context."); } this.stack.push(this.script.composeFieldValueReference( - symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.absolutePath()), - Expression.class)); + this.symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.absolutePath()))); } - /*** Context Declaration Blocks {...} ***/ + // #endregion Expression Blocks ${...} -------------------------------------- + + // #region Context Declaration Blocks {...} --------------------------------- /** * This method changes the current EFX context. @@ -444,32 +459,49 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as context."; this.efxContext.push(new NodeContext(nodeId, this.stack.pop(PathExpression.class))); } - - // final PathExpression absolutePath = this.stack.pop(PathExpression.class); - // final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - // if (filedId != null) { - // this.efxContext.push(new FieldContext(filedId, absolutePath, - // this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); - // } else { - // final String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); - // assert nodeId != null : "We should have been able to locate the FieldId or NodeId declared as - // context."; - // this.efxContext.push(new NodeContext(nodeId, absolutePath, - // this.symbols.getRelativePath(absolutePath, this.blockStack.absolutePath()))); - // } } - /*** Template lines ***/ + // #endregion Context Declaration Blocks {...} ------------------------------ + + // #region Template lines -------------------------------------------------- + + @Override + public void enterTemplateLine(TemplateLineContext ctx) { + final int indentLevel = this.getIndentLevel(ctx); + final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); + if (indentChange > 1) { + throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED); + } else if (indentChange == 1) { + if (this.blockStack.isEmpty()) { + throw new ParseCancellationException(START_INDENT_AT_ZERO); + } + this.stack.pushStackFrame(); // Create a stack frame for the new template line. + } else if (indentChange < 0) { + for (int i = indentChange; i < 0; i++) { + assert !this.blockStack.isEmpty() : UNEXPECTED_INDENTATION; + assert this.blockStack.currentIndentationLevel() > indentLevel : UNEXPECTED_INDENTATION; + this.blockStack.pop(); + this.stack.popStackFrame(); // Each skipped indentation level must go out of scope. + } + this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation + // level). + this.stack.pushStackFrame(); // Create a stack frame for the new template line. + assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; + } else if (indentChange == 0) { + this.stack.popStackFrame(); // The previous sibling goes out of scope (same indentation + // level). + this.stack.pushStackFrame(); // Create a stack frame for the new template line. + } + } @Override public void exitTemplateLine(TemplateLineContext ctx) { - final VariableList variables = new VariableList(); // template variables not supported prior to - // EFX 2 final Context lineContext = this.efxContext.pop(); final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); + final VariableList variables = new VariableList(); // template variables not supported in EFX-1 final Integer outlineNumber = ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; assert this.stack.empty() : "Stack should be empty at this point."; @@ -484,13 +516,6 @@ public void exitTemplateLine(TemplateLineContext ctx) { this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { - // lower indent level - for (int i = indentChange; i < 0; i++) { - assert !this.blockStack.isEmpty() : UNEXPECTED_INDENTATION; - assert this.blockStack.currentIndentationLevel() > indentLevel : UNEXPECTED_INDENTATION; - this.blockStack.pop(); - } - assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION; this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { @@ -511,19 +536,20 @@ private Context relativizeContext(Context childContext, Context parentContext) { return childContext; } - if (FieldContext.class.isAssignableFrom(childContext.getClass())) { + if (childContext.isFieldContext()) { return new FieldContext(childContext.symbol(), childContext.absolutePath(), this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); } - assert NodeContext.class.isAssignableFrom( - childContext.getClass()) : "Child context should be either a FieldContext NodeContext."; + assert childContext.isNodeContext() : "Child context should be either a FieldContext NodeContext."; return new NodeContext(childContext.symbol(), childContext.absolutePath(), this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); } - /*** Helpers ***/ + // #endregion Template lines ----------------------------------------------- + + // #region Helpers ---------------------------------------------------------- private int getIndentLevel(TemplateLineContext ctx) { if (ctx.MixedIndent() != null) { @@ -554,4 +580,7 @@ private int getIndentLevel(TemplateLineContext ctx) { } return 0; } + + // #endregion Helpers ------------------------------------------------------- + } diff --git a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java index d75bb77d..bbf16e9d 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java @@ -3,10 +3,8 @@ import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.TranslatorOptions; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.MultilingualStringExpression; -import eu.europa.ted.efx.model.Expression.MultilingualStringListExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.types.EfxDataType; import eu.europa.ted.efx.xpath.XPathContextualizer; import eu.europa.ted.efx.xpath.XPathScriptGenerator; @@ -41,10 +39,10 @@ public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { * order of preference (visualisation language followed by notice language(s)). */ @Override - public T composeFieldValueReference(PathExpression fieldReference, Class type) { - if ((MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) { - return Expression.instantiate("efx:preferred-language-text(" + fieldReference.script + ")", type); + public PathExpression composeFieldValueReference(PathExpression fieldReference) { + if (fieldReference.is(EfxDataType.MultilingualString.class) && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) { + return PathExpression.instantiate("efx:preferred-language-text(" + fieldReference.getScript() + ")", fieldReference.getDataType()); } - return super.composeFieldValueReference(fieldReference, type); + return super.composeFieldValueReference(fieldReference); } } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 4940c161..e6aa3c8c 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -33,26 +33,33 @@ import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; import eu.europa.ted.efx.model.ContextStack; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.BooleanExpression; -import eu.europa.ted.efx.model.Expression.BooleanListExpression; -import eu.europa.ted.efx.model.Expression.ContextExpression; -import eu.europa.ted.efx.model.Expression.DateExpression; -import eu.europa.ted.efx.model.Expression.DateListExpression; -import eu.europa.ted.efx.model.Expression.DurationExpression; -import eu.europa.ted.efx.model.Expression.DurationListExpression; -import eu.europa.ted.efx.model.Expression.IteratorExpression; -import eu.europa.ted.efx.model.Expression.IteratorListExpression; -import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.NumericListExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Expression.StringListExpression; -import eu.europa.ted.efx.model.Expression.TimeExpression; -import eu.europa.ted.efx.model.Expression.TimeListExpression; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorListExpression; +import eu.europa.ted.efx.model.expressions.path.MultilingualStringPathExpression; +import eu.europa.ted.efx.model.expressions.path.NodePathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.path.StringPathExpression; +import eu.europa.ted.efx.model.expressions.scalar.BooleanExpression; +import eu.europa.ted.efx.model.expressions.scalar.DateExpression; +import eu.europa.ted.efx.model.expressions.scalar.DurationExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.TimeExpression; +import eu.europa.ted.efx.model.expressions.sequence.BooleanSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.DateSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.DurationSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.NumericSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.SequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.StringSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.TimeSequenceExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.FieldTypes; +import eu.europa.ted.efx.model.variables.Parameter; +import eu.europa.ted.efx.model.variables.Variable; import eu.europa.ted.efx.sdk2.EfxParser.*; -import eu.europa.ted.efx.xpath.XPathAttributeLocator; /** * The the goal of the EfxExpressionTranslator is to take an EFX expression and translate it to a @@ -126,9 +133,11 @@ public EfxExpressionTranslatorV2(final SymbolResolver symbolResolver, public String translateExpression(final String expression, final String... parameters) { this.expressionParameters.addAll(Arrays.asList(parameters)); + // New in EFX-2: expression preprocessing final ExpressionPreprocessor preprocessor = this.new ExpressionPreprocessor(expression); final String preprocessedExpression = preprocessor.processExpression(); + // Now parse the preprocessed expression final EfxLexer lexer = new EfxLexer(CharStreams.fromString(preprocessedExpression)); final CommonTokenStream tokens = new CommonTokenStream(lexer); @@ -184,7 +193,7 @@ private T translateParameter(final String parameterValue, private String getTranslatedScript() { final StringBuilder sb = new StringBuilder(this.stack.size() * 100); while (!this.stack.empty()) { - sb.insert(0, '\n').insert(0, this.stack.pop(Expression.class).script); + sb.insert(0, '\n').insert(0, this.stack.pop(Expression.class).getScript()); } return sb.toString().trim(); } @@ -354,8 +363,7 @@ public void exitDurationComparison(DurationComparisonContext ctx) { @Override public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); - String operator = - ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; + String operator = ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; this.stack.push(this.script.composeComparisonOperation(expression, operator, this.script.getStringLiteralFromUnquotedString(""))); } @@ -373,7 +381,7 @@ public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { @Override public void exitUniqueValueCondition(EfxParser.UniqueValueConditionContext ctx) { PathExpression haystack = this.stack.pop(PathExpression.class); - PathExpression needle = this.stack.pop(PathExpression.class); + PathExpression needle = this.stack.pop(haystack.getClass()); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { this.stack.push( @@ -386,9 +394,7 @@ public void exitUniqueValueCondition(EfxParser.UniqueValueConditionContext ctx) @Override public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); - - BooleanExpression condition = - this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); + BooleanExpression condition = this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { condition = this.script.composeLogicalNot(condition); } @@ -401,38 +407,38 @@ public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) @Override public void exitStringInListCondition(EfxParser.StringInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, StringExpression.class, StringListExpression.class); + this.exitInListCondition(ctx.modifier, StringExpression.class, StringSequenceExpression.class); } @Override public void exitBooleanInListCondition(BooleanInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, BooleanExpression.class, BooleanListExpression.class); + this.exitInListCondition(ctx.modifier, BooleanExpression.class, BooleanSequenceExpression.class); } @Override public void exitNumberInListCondition(NumberInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, NumericExpression.class, NumericListExpression.class); + this.exitInListCondition(ctx.modifier, NumericExpression.class, NumericSequenceExpression.class); } @Override public void exitDateInListCondition(DateInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, DateExpression.class, DateListExpression.class); + this.exitInListCondition(ctx.modifier, DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeInListCondition(TimeInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, TimeExpression.class, TimeListExpression.class); + this.exitInListCondition(ctx.modifier, TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationInListCondition(DurationInListConditionContext ctx) { - this.exitInListCondition(ctx.modifier, DurationExpression.class, DurationListExpression.class); + this.exitInListCondition(ctx.modifier, DurationExpression.class, DurationSequenceExpression.class); } - private > void exitInListCondition( - Token modifier, Class expressionType, Class listType) { - ListExpression list = this.stack.pop(listType); - T expression = this.stack.pop(expressionType); + private void exitInListCondition( + Token modifier, Class expressionType, Class listType) { + SequenceExpression list = this.stack.pop(listType); + ScalarExpression expression = this.stack.pop(expressionType); BooleanExpression condition = this.script.composeContainsCondition(expression, list); if (modifier != null && modifier.getText().equals(NOT_MODIFIER)) { condition = this.script.composeLogicalNot(condition); @@ -534,47 +540,47 @@ public void exitDateSubtractionExpression(DateSubtractionExpressionContext ctx) @Override public void exitCodeList(CodeListContext ctx) { if (this.stack.empty()) { - this.stack.push(this.script.composeList(Collections.emptyList(), StringListExpression.class)); + this.stack.push(this.script.composeList(Collections.emptyList(), StringSequenceExpression.class)); } } @Override public void exitStringList(StringListContext ctx) { this.exitList(ctx.stringExpression().size(), StringExpression.class, - StringListExpression.class); + StringSequenceExpression.class); } @Override public void exitBooleanList(BooleanListContext ctx) { this.exitList(ctx.booleanExpression().size(), BooleanExpression.class, - BooleanListExpression.class); + BooleanSequenceExpression.class); } @Override public void exitNumericList(NumericListContext ctx) { this.exitList(ctx.numericExpression().size(), NumericExpression.class, - NumericListExpression.class); + NumericSequenceExpression.class); } @Override public void exitDateList(DateListContext ctx) { - this.exitList(ctx.dateExpression().size(), DateExpression.class, DateListExpression.class); + this.exitList(ctx.dateExpression().size(), DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeList(TimeListContext ctx) { - this.exitList(ctx.timeExpression().size(), TimeExpression.class, TimeListExpression.class); + this.exitList(ctx.timeExpression().size(), TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationList(DurationListContext ctx) { this.exitList(ctx.durationExpression().size(), DurationExpression.class, - DurationListExpression.class); + DurationSequenceExpression.class); } - private > void exitList(int listSize, - Class expressionType, Class listType) { + private void exitList(int listSize, + Class expressionType, Class listType) { if (this.stack.empty() || listSize == 0) { this.stack.push(this.script.composeList(Collections.emptyList(), listType)); return; @@ -675,49 +681,57 @@ private void exitConditionalDurationExpression() { @Override public void exitStringIteratorExpression(StringIteratorExpressionContext ctx) { - this.exitIteratorExpression(StringExpression.class, StringListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.stringVariableDeclaration()), StringExpression.class, StringSequenceExpression.class); } @Override public void exitBooleanIteratorExpression(BooleanIteratorExpressionContext ctx) { - this.exitIteratorExpression(BooleanExpression.class, BooleanListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.booleanVariableDeclaration()), BooleanExpression.class, BooleanSequenceExpression.class); } @Override public void exitNumericIteratorExpression(NumericIteratorExpressionContext ctx) { - this.exitIteratorExpression(NumericExpression.class, NumericListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.numericVariableDeclaration()), NumericExpression.class, NumericSequenceExpression.class); } @Override public void exitDateIteratorExpression(DateIteratorExpressionContext ctx) { - this.exitIteratorExpression(DateExpression.class, DateListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.dateVariableDeclaration()), DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeIteratorExpression(TimeIteratorExpressionContext ctx) { - this.exitIteratorExpression(TimeExpression.class, TimeListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.timeVariableDeclaration()), TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationIteratorExpression(DurationIteratorExpressionContext ctx) { - this.exitIteratorExpression(DurationExpression.class, DurationListExpression.class); + this.exitIteratorExpression(getVariableName(ctx.durationVariableDeclaration()), DurationExpression.class, DurationSequenceExpression.class); } @Override public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); - ContextExpression variable = this.stack.pop(ContextExpression.class); - this.stack.push(this.script.composeIteratorExpression(variable.script, path)); + + var variableType = path.getClass(); + var variableName = getVariableName(ctx.contextVariableDeclaration()); + Variable variable = new Variable(variableName, + this.script.composeVariableDeclaration(variableName, variableType), + Expression.empty(variableType), + this.script.composeVariableReference(variableName, variableType)); + this.stack.declareIdentifier(variable); + + this.stack.push(this.script.composeIteratorExpression(variable.declarationExpression, path)); if (ctx.fieldContext() != null) { final String contextFieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx.fieldContext()); - this.efxContext.declareContextVariable(variable.script, + this.efxContext.declareContextVariable(variable.name, new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); } else if (ctx.nodeContext() != null) { final String contextNodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx.nodeContext()); - this.efxContext.declareContextVariable(variable.script, + this.efxContext.declareContextVariable(variable.name, new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); } @@ -735,39 +749,39 @@ public void exitIteratorList(IteratorListContext ctx) { @Override public void exitParenthesizedStringsFromIteration(ParenthesizedStringsFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(StringListExpression.class), StringListExpression.class)); + this.stack.pop(StringSequenceExpression.class), StringSequenceExpression.class)); } @Override public void exitParenthesizedNumbersFromIteration(ParenthesizedNumbersFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(NumericListExpression.class), NumericListExpression.class)); + this.stack.pop(NumericSequenceExpression.class), NumericSequenceExpression.class)); } @Override public void exitParenthesizedBooleansFromIteration( ParenthesizedBooleansFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(BooleanListExpression.class), BooleanListExpression.class)); + this.stack.pop(BooleanSequenceExpression.class), BooleanSequenceExpression.class)); } @Override public void exitParenthesizedDatesFromIteration(ParenthesizedDatesFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(DateListExpression.class), DateListExpression.class)); + this.stack.pop(DateSequenceExpression.class), DateSequenceExpression.class)); } @Override public void exitParenthesizedTimesFromIteration(ParenthesizedTimesFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(TimeListExpression.class), TimeListExpression.class)); + this.stack.pop(TimeSequenceExpression.class), TimeSequenceExpression.class)); } @Override public void exitParenthesizedDurationsFromIteration( ParenthesizedDurationsFromIterationContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( - this.stack.pop(DurationListExpression.class), DurationListExpression.class)); + this.stack.pop(DurationSequenceExpression.class), DurationSequenceExpression.class)); } @Override @@ -777,7 +791,7 @@ public void enterStringSequenceFromIteration(StringSequenceFromIterationContext @Override public void exitStringSequenceFromIteration(StringSequenceFromIterationContext ctx) { - this.exitIterationExpression(StringExpression.class, StringListExpression.class); + this.exitIterationExpression(StringExpression.class, StringSequenceExpression.class); this.stack.popStackFrame(); // Iteration variables are local to the iteration } @@ -788,7 +802,7 @@ public void enterNumericSequenceFromIteration(NumericSequenceFromIterationContex @Override public void exitNumericSequenceFromIteration(NumericSequenceFromIterationContext ctx) { - this.exitIterationExpression(NumericExpression.class, NumericListExpression.class); + this.exitIterationExpression(NumericExpression.class, NumericSequenceExpression.class); this.stack.popStackFrame(); // Iteration variables are local to the iteration } @@ -799,7 +813,7 @@ public void enterBooleanSequenceFromIteration(BooleanSequenceFromIterationContex @Override public void exitBooleanSequenceFromIteration(BooleanSequenceFromIterationContext ctx) { - this.exitIterationExpression(BooleanExpression.class, BooleanListExpression.class); + this.exitIterationExpression(BooleanExpression.class, BooleanSequenceExpression.class); this.stack.popStackFrame(); // Iteration variables are local to the iteration } @@ -810,7 +824,7 @@ public void enterDateSequenceFromIteration(DateSequenceFromIterationContext ctx) @Override public void exitDateSequenceFromIteration(DateSequenceFromIterationContext ctx) { - this.exitIterationExpression(DateExpression.class, DateListExpression.class); + this.exitIterationExpression(DateExpression.class, DateSequenceExpression.class); this.stack.popStackFrame(); // Iteration variables are local to the iteration } @@ -821,7 +835,7 @@ public void enterTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) @Override public void exitTimeSequenceFromIteration(TimeSequenceFromIterationContext ctx) { - this.exitIterationExpression(TimeExpression.class, TimeListExpression.class); + this.exitIterationExpression(TimeExpression.class, TimeSequenceExpression.class); this.stack.popStackFrame(); // Iteration variables are local to the iteration } @@ -832,20 +846,24 @@ public void enterDurationSequenceFromIteration(DurationSequenceFromIterationCont @Override public void exitDurationSequenceFromIteration(DurationSequenceFromIterationContext ctx) { - this.exitIterationExpression(DurationExpression.class, DurationListExpression.class); + this.exitIterationExpression(DurationExpression.class, DurationSequenceExpression.class); this.stack.popStackFrame(); // Iteration variables are local to the iteration } - public > void exitIteratorExpression( - Class variableType, Class listType) { - L list = this.stack.pop(listType); - T variable = this.stack.pop(variableType); - this.stack.push(this.script.composeIteratorExpression(variable.script, list)); + public void exitIteratorExpression(String variableName, + Class variableType, Class listType) { + Expression declarationExpression = this.script.composeVariableDeclaration(variableName, variableType); + SequenceExpression initialisationExpression = this.stack.pop(listType); + ScalarExpression referenceExpression = this.script.composeVariableReference(variableName, variableType); + Variable variable = new Variable(variableName, declarationExpression, initialisationExpression, + referenceExpression); + this.stack.declareIdentifier(variable); + this.stack.push(this.script.composeIteratorExpression(variable.declarationExpression, initialisationExpression)); } - public > void exitIterationExpression( + public void exitIterationExpression( Class expressionType, - Class targetListType) { + Class targetListType) { T expression = this.stack.pop(expressionType); IteratorListExpression iterators = this.stack.pop(IteratorListExpression.class); this.stack @@ -942,9 +960,8 @@ public void exitAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx) { if (ctx.predicate() != null) { BooleanExpression predicate = this.stack.pop(BooleanExpression.class); - PathExpression nodeReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate, - PathExpression.class)); + PathExpression nodeReference = this.stack.pop(NodePathExpression.class); + this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate)); } } @@ -953,8 +970,7 @@ public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicat if (ctx.predicate() != null) { BooleanExpression predicate = this.stack.pop(BooleanExpression.class); PathExpression fieldReference = this.stack.pop(PathExpression.class); - this.stack.push(this.script.composeFieldReferenceWithPredicate(fieldReference, predicate, - PathExpression.class)); + this.stack.push(this.script.composeFieldReferenceWithPredicate(fieldReference, predicate)); } } @@ -962,6 +978,8 @@ public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicat * Any field references in the predicate must be resolved relative to the node or field on which * the predicate is applied. Therefore we need to switch to that context while the predicate is * being parsed. + * + * @param ctx The predicate context */ @Override public void enterPredicate(EfxParser.PredicateContext ctx) { @@ -986,7 +1004,7 @@ public void exitPredicate(EfxParser.PredicateContext ctx) { public void exitFieldReferenceWithAxis(FieldReferenceWithAxisContext ctx) { if (ctx.axis() != null) { this.stack.push(this.script.composeFieldReferenceWithAxis( - this.stack.pop(PathExpression.class), ctx.axis().Axis().getText(), PathExpression.class)); + this.stack.pop(PathExpression.class), ctx.axis().Axis().getText())); } } @@ -1028,18 +1046,14 @@ public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - // TODO: Use an interface for locating attributes. A PathExpression is not necessarily an - // XPath in every implementation. - XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); - - if (parsedPath.hasAttribute()) { - this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), - parsedPath.getAttribute(), Expression.types.get(this.symbols.getTypeOfField(fieldId)))); - } else if (fieldId != null) { - this.stack.push(this.script.composeFieldValueReference(path, - Expression.types.get(this.symbols.getTypeOfField(fieldId)))); + if (this.symbols.isAttributeField(fieldId)) { + this.stack.push(this.script.composeFieldAttributeReference( + this.symbols.getRelativePath( + this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), + this.symbols.getAttributeOfField(fieldId), + PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { - this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); + this.stack.push(this.script.composeFieldValueReference(path)); } } @@ -1047,32 +1061,27 @@ public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); - // TODO: Use an interface for locating attributes. A PathExpression is not necessarily an - // XPath in every implementation. - XPathAttributeLocator parsedPath = XPathAttributeLocator.findAttribute(path); - - if (parsedPath.hasAttribute()) { - this.stack.push(this.script.composeFieldAttributeReference(parsedPath.getPath(), - parsedPath.getAttribute(), - Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); - } else if (fieldId != null) { - this.stack.push(this.script.composeFieldValueReference(path, - Expression.listTypes.get(this.symbols.getTypeOfField(fieldId)))); + if (this.symbols.isAttributeField(fieldId)) { + this.stack.push(this.script.composeFieldAttributeReference( + this.symbols.getRelativePath( + this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), + this.symbols.getAttributeOfField(fieldId), + PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { - this.stack.push(this.script.composeFieldValueReference(path, PathExpression.class)); + this.stack.push(this.script.composeFieldValueReference(path)); } } @Override public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - this.getAttributeName(ctx), StringExpression.class)); + this.getAttributeName(ctx), StringPathExpression.class)); } @Override public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - this.getAttributeName(ctx), StringListExpression.class)); + this.getAttributeName(ctx), StringPathExpression.class)); } // #endregion Value References ---------------------------------------------- @@ -1138,8 +1147,7 @@ public void exitFieldReferenceWithNodeContextOverride( @Override public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { - ContextExpression variableName = this.stack.pop(ContextExpression.class); - Context variableContext = this.efxContext.getContextFromVariable(variableName.script); + Context variableContext = this.efxContext.getContextFromVariable(getVariableName(ctx.variableReference())); if (variableContext.isFieldContext()) { this.efxContext.push(new FieldContext(variableContext.symbol(), this.symbols.getAbsolutePathOfField(variableContext.symbol()), this.symbols @@ -1151,7 +1159,6 @@ public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { } else { throw new IllegalStateException("Variable context is neither a field nor a node context."); } - this.stack.push(variableName); } @Override @@ -1159,8 +1166,8 @@ public void exitFieldReferenceWithVariableContextOverride( FieldReferenceWithVariableContextOverrideContext ctx) { if (ctx.contextVariableSpecifier() != null) { final PathExpression field = this.stack.pop(PathExpression.class); - final ContextExpression variableName = this.stack.pop(ContextExpression.class); - this.stack.push(this.script.joinPaths(new PathExpression(variableName.script), field)); + final PathExpression contextVariable = this.stack.pop(PathExpression.class); + this.stack.push(this.script.joinPaths(contextVariable, field)); this.efxContext.pop(); // Restores the previous context } } @@ -1173,60 +1180,59 @@ public void exitFieldReferenceWithVariableContextOverride( public void exitCodelistReference(CodelistReferenceContext ctx) { this.stack.push(this.script.composeList(this.symbols.expandCodelist(this.getCodelistName(ctx)) .stream().map(s -> this.script.getStringLiteralFromUnquotedString(s)) - .collect(Collectors.toList()), StringListExpression.class)); + .collect(Collectors.toList()), StringSequenceExpression.class)); } @Override public void exitVariableReference(VariableReferenceContext ctx) { String variableName = getVariableName(ctx); - this.stack.pushVariableReference(variableName, - this.script.composeVariableReference(variableName, Expression.class)); + this.stack.pushIdentifierReference(variableName); } // #endregion Other References ---------------------------------------------- // #endregion References ---------------------------------------------------- - // #region Indexers --------------------------------------------------------- + // #region New in EFX-2: Indexers ------------------------------------------ @Override public void exitStringAtSequenceIndex(StringAtSequenceIndexContext ctx) { - this.exitSequenceAtIndex(StringExpression.class, StringListExpression.class); + this.exitSequenceAtIndex(StringExpression.class, StringSequenceExpression.class); } @Override public void exitNumericAtSequenceIndex(NumericAtSequenceIndexContext ctx) { - this.exitSequenceAtIndex(NumericExpression.class, NumericListExpression.class); + this.exitSequenceAtIndex(NumericExpression.class, NumericSequenceExpression.class); } @Override public void exitBooleanAtSequenceIndex(BooleanAtSequenceIndexContext ctx) { - this.exitSequenceAtIndex(BooleanExpression.class, BooleanListExpression.class); + this.exitSequenceAtIndex(BooleanExpression.class, BooleanSequenceExpression.class); } @Override public void exitDateAtSequenceIndex(DateAtSequenceIndexContext ctx) { - this.exitSequenceAtIndex(DateExpression.class, DateListExpression.class); + this.exitSequenceAtIndex(DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeAtSequenceIndex(TimeAtSequenceIndexContext ctx) { - this.exitSequenceAtIndex(TimeExpression.class, TimeListExpression.class); + this.exitSequenceAtIndex(TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationAtSequenceIndex(DurationAtSequenceIndexContext ctx) { - this.exitSequenceAtIndex(DurationExpression.class, DurationListExpression.class); + this.exitSequenceAtIndex(DurationExpression.class, DurationSequenceExpression.class); } - private > void exitSequenceAtIndex( - Class itemType, Class listType) { + private void exitSequenceAtIndex( + Class itemType, Class listType) { NumericExpression index = this.stack.pop(NumericExpression.class); - L list = this.stack.pop(listType); + T list = this.stack.pop(listType); this.stack.push(this.script.composeIndexer(list, index, itemType)); } - // #endregion Indexers ------------------------------------------------------ + // #endregion New in EFX-2: Indexers --------------------------------------- // #region Parameter Declarations ------------------------------------------- @@ -1261,72 +1267,20 @@ public void exitDurationParameterDeclaration(DurationParameterDeclarationContext this.exitParameterDeclaration(getVariableName(ctx), DurationExpression.class); } - private void exitParameterDeclaration(String parameterName, - Class parameterType) { + private void exitParameterDeclaration(String parameterName, Class parameterType) { if (this.expressionParameters.isEmpty()) { throw new ParseCancellationException("No parameter passed for " + parameterName); } - this.stack.pushParameterDeclaration(parameterName, + Parameter parameter = new Parameter(parameterName, this.script.composeParameterDeclaration(parameterName, parameterType), + this.script.composeVariableReference(parameterName, parameterType), this.translateParameter(this.expressionParameters.pop(), parameterType)); + this.stack.declareIdentifier(parameter); } // #endregion Parameter Declarations ---------------------------------------- - // #region Variable Declarations -------------------------------------------- - - @Override - public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) { - String variableName = getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, StringExpression.class)); - } - - @Override - public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) { - String variableName = getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, BooleanExpression.class)); - } - - @Override - public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) { - String variableName = getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, NumericExpression.class)); - } - - @Override - public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) { - String variableName = getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, DateExpression.class)); - } - - @Override - public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) { - String variableName = getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, TimeExpression.class)); - } - - @Override - public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) { - String variableName = getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, DurationExpression.class)); - } - - @Override - public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) { - String variableName = getVariableName(ctx); - this.stack.pushVariableDeclaration(variableName, - this.script.composeVariableDeclaration(variableName, ContextExpression.class)); - } - - // #endregion Variable Declarations ----------------------------------------- - // #region Boolean functions ------------------------------------------------ @Override @@ -1357,8 +1311,8 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) { @Override public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { - final ListExpression two = this.stack.pop(ListExpression.class); - final ListExpression one = this.stack.pop(ListExpression.class); + final SequenceExpression two = this.stack.pop(SequenceExpression.class); + final SequenceExpression one = this.stack.pop(SequenceExpression.class); this.stack.push(this.script.composeSequenceEqualFunction(one, two)); } @@ -1368,7 +1322,7 @@ public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { @Override public void exitCountFunction(CountFunctionContext ctx) { - final ListExpression expression = this.stack.pop(ListExpression.class); + final SequenceExpression expression = this.stack.pop(SequenceExpression.class); this.stack.push(this.script.composeCountOperation(expression)); } @@ -1379,7 +1333,7 @@ public void exitNumberFunction(NumberFunctionContext ctx) { @Override public void exitSumFunction(SumFunctionContext ctx) { - this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericListExpression.class))); + this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericSequenceExpression.class))); } @Override @@ -1392,16 +1346,6 @@ public void exitStringLengthFunction(StringLengthFunctionContext ctx) { // #region String functions ------------------------------------------------- - @Override - public void exitUpperCaseFunction(UpperCaseFunctionContext ctx) { - this.stack.push(this.script.composeToUpperCaseConversion(this.stack.pop(StringExpression.class))); - } - - @Override - public void exitLowerCaseFunction(LowerCaseFunctionContext ctx) { - this.stack.push(this.script.composeToLowerCaseConversion(this.stack.pop(StringExpression.class))); - } - @Override public void exitSubstringFunction(SubstringFunctionContext ctx) { final NumericExpression length = @@ -1434,13 +1378,6 @@ public void exitConcatFunction(ConcatFunctionContext ctx) { this.stack.push(this.script.composeStringConcatenation(list)); } - @Override - public void exitStringJoinFunction(StringJoinFunctionContext ctx) { - final StringExpression separator = this.stack.pop(StringExpression.class); - final StringListExpression list = this.stack.pop(StringListExpression.class); - this.stack.push(this.script.composeStringJoin(list, separator)); - } - @Override public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { final StringExpression format = this.stack.pop(StringExpression.class); @@ -1448,16 +1385,37 @@ public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { this.stack.push(this.script.composeNumberFormatting(number, format)); } + // #region New in EFX-2 ----------------------------------------------------- + + @Override + public void exitUpperCaseFunction(UpperCaseFunctionContext ctx) { + this.stack.push(this.script.composeToUpperCaseConversion(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitLowerCaseFunction(LowerCaseFunctionContext ctx) { + this.stack.push(this.script.composeToLowerCaseConversion(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitStringJoinFunction(StringJoinFunctionContext ctx) { + final StringExpression separator = this.stack.pop(StringExpression.class); + final StringSequenceExpression list = this.stack.pop(StringSequenceExpression.class); + this.stack.push(this.script.composeStringJoin(list, separator)); + } + @Override public void exitPreferredLanguageFunction(PreferredLanguageFunctionContext ctx) { - this.stack.push(this.script.getPreferredLanguage(this.stack.pop(PathExpression.class))); + this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringPathExpression.class))); } @Override public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionContext ctx) { - this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(PathExpression.class))); + this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringPathExpression.class))); } + // #endregion New in EFX-2 -------------------------------------------------- + // #endregion String functions ---------------------------------------------- // #region Date functions --------------------------------------------------- @@ -1513,112 +1471,120 @@ public void exitYearMonthDurationFromStringFunction( @Override public void exitDistinctValuesFunction(DistinctValuesFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitDistinctValuesFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitDistinctValuesFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitDistinctValuesFunction( - Class listType) { - final L list = this.stack.pop(listType); + private void exitDistinctValuesFunction( + Class listType) { + final T list = this.stack.pop(listType); this.stack.push(this.script.composeDistinctValuesFunction(list, listType)); } @Override public void exitUnionFunction(UnionFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitUnionFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitUnionFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitUnionFunction( - Class listType) { - final L two = this.stack.pop(listType); - final L one = this.stack.pop(listType); + private void exitUnionFunction( + Class listType) { + final T two = this.stack.pop(listType); + final T one = this.stack.pop(listType); this.stack.push(this.script.composeUnionFunction(one, two, listType)); } @Override public void exitIntersectFunction(IntersectFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitIntersectFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitIntersectFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitIntersectFunction( - Class listType) { - final L two = this.stack.pop(listType); - final L one = this.stack.pop(listType); + private void exitIntersectFunction( + Class listType) { + final T two = this.stack.pop(listType); + final T one = this.stack.pop(listType); this.stack.push(this.script.composeIntersectFunction(one, two, listType)); } @Override public void exitExceptFunction(ExceptFunctionContext ctx) { - final Class sequenceType = this.stack.peek().getClass(); - if (StringListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(StringListExpression.class); - } else if (NumericListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(NumericListExpression.class); - } else if (BooleanListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(BooleanListExpression.class); - } else if (DateListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(DateListExpression.class); - } else if (TimeListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(TimeListExpression.class); - } else if (DurationListExpression.class.isAssignableFrom(sequenceType)) { - this.exitExceptFunction(DurationListExpression.class); + var sequence = this.stack.peek(); + assert sequence instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; + final Class sequenceType = ((TypedExpression)sequence).getDataType(); + if (EfxDataType.String.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(StringSequenceExpression.class); + } else if (EfxDataType.Number.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(NumericSequenceExpression.class); + } else if (EfxDataType.Boolean.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(BooleanSequenceExpression.class); + } else if (EfxDataType.Date.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(DateSequenceExpression.class); + } else if (EfxDataType.Time.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(TimeSequenceExpression.class); + } else if (EfxDataType.Duration.class.isAssignableFrom(sequenceType)) { + this.exitExceptFunction(DurationSequenceExpression.class); } else { throw new IllegalArgumentException( "Unsupported sequence type: " + sequenceType.getSimpleName()); } } - private > void exitExceptFunction( - Class listType) { - final L two = this.stack.pop(listType); - final L one = this.stack.pop(listType); + private void exitExceptFunction( + Class listType) { + final T two = this.stack.pop(listType); + final T one = this.stack.pop(listType); this.stack.push(this.script.composeExceptFunction(one, two, listType)); } @@ -1629,7 +1595,9 @@ private > void exitExceptFunct protected static String getLexerSymbol(int tokenType) { return EfxLexer.VOCABULARY.getLiteralName(tokenType).replaceAll("^'|'$", ""); } - + + // #region Codelist Names --------------------------------------------------- + protected String getCodelistName(String efxCodelistIdentifier) { return StringUtils.substringAfter(efxCodelistIdentifier, CODELIST_PREFIX); } @@ -1638,6 +1606,10 @@ private String getCodelistName(CodelistReferenceContext ctx) { return this.getCodelistName(ctx.CodelistId().getText()); } + // #endregion Codelist Names ------------------------------------------------ + + // #region Attribute Names -------------------------------------------------- + protected String getAttributeName(String efxAttributeIdentifier) { return StringUtils.substringAfter(efxAttributeIdentifier, ATTRIBUTE_PREFIX); } @@ -1650,6 +1622,10 @@ private String getAttributeName(ScalarFromAttributeReferenceContext ctx) { return this.getAttributeName(ctx.attributeReference().Attribute().getText()); } + // #endregion Attribute Names ----------------------------------------------- + + // #region Variable Names --------------------------------------------------- + static protected String getVariableName(String efxVariableIdentifier) { return StringUtils.substringAfter(efxVariableIdentifier, VARIABLE_PREFIX); } @@ -1710,6 +1686,8 @@ static private String getVariableName(DurationParameterDeclarationContext ctx) { return getVariableName(ctx.Variable().getText()); } + // #endregion Variable Names --------------------------------------------------- + // #endregion Helpers ------------------------------------------------------- // #region Pre-processing --------------------------------------------------- @@ -1731,32 +1709,33 @@ public void exitLateBoundScalar(LateBoundScalarContext ctx) { private static String timeTypeName = getLexerSymbol(EfxLexer.Time); private static String durationTypeName = getLexerSymbol(EfxLexer.Measure); - private static final Map eFormsToEfxTypeMap = Map.ofEntries(entry("id", textTypeName), // - entry("id-ref", textTypeName), // - entry("text", textTypeName), // - entry("text-multilingual", textTypeName), // - entry("indicator", booleanTypeName), // - entry("amount", numericTypeName), // - entry("number", numericTypeName), // - entry("measure", durationTypeName), // - entry("code", textTypeName), // - entry("internal-code", textTypeName), // - entry("integer", numericTypeName), // - entry("date", dateTypeName), // - entry("zoned-date", dateTypeName), // - entry("time", timeTypeName), // - entry("zoned-time", timeTypeName), // - entry("url", textTypeName), // - entry("phone", textTypeName), // - entry("email", textTypeName)); - - private static final Map, String> javaToEfxTypeMap = Map.ofEntries( - entry(StringExpression.class, textTypeName), // - entry(BooleanExpression.class, booleanTypeName), // - entry(NumericExpression.class, numericTypeName), // - entry(DurationExpression.class, durationTypeName), // - entry(DateExpression.class, dateTypeName), // - entry(TimeExpression.class, timeTypeName)); + private static final Map eFormsToEfxTypeMap = Map.ofEntries( // + entry(FieldTypes.ID.getName(), textTypeName), // + entry(FieldTypes.ID_REF.getName(), textTypeName), // + entry(FieldTypes.TEXT.getName(), textTypeName), // + entry(FieldTypes.TEXT_MULTILINGUAL.getName(), textTypeName), // + entry(FieldTypes.INDICATOR.getName(), booleanTypeName), // + entry(FieldTypes.AMOUNT.getName(), numericTypeName), // + entry(FieldTypes.NUMBER.getName(), numericTypeName), // + entry(FieldTypes.MEASURE.getName(), durationTypeName), // + entry(FieldTypes.CODE.getName(), textTypeName), // + entry(FieldTypes.INTERNAL_CODE.getName(), textTypeName), // + entry(FieldTypes.INTEGER.getName(), numericTypeName), // + entry(FieldTypes.DATE.getName(), dateTypeName), // + entry(FieldTypes.ZONED_DATE.getName(), dateTypeName), // + entry(FieldTypes.TIME.getName(), timeTypeName), // + entry(FieldTypes.ZONED_TIME.getName(), timeTypeName), // + entry(FieldTypes.URL.getName(), textTypeName), // + entry(FieldTypes.PHONE.getName(), textTypeName), // + entry(FieldTypes.EMAIL.getName(), textTypeName)); + + private static final Map, String> javaToEfxTypeMap = Map.ofEntries( + entry(EfxDataType.String.class, textTypeName), // + entry(EfxDataType.Boolean.class, booleanTypeName), // + entry(EfxDataType.Number.class, numericTypeName), // + entry(EfxDataType.Duration.class, durationTypeName), // + entry(EfxDataType.Date.class, dateTypeName), // + entry(EfxDataType.Time.class, timeTypeName)); /** * The EFX expression pre-processor is used to remove expression ambiguities @@ -1895,43 +1874,43 @@ boolean hasParentContextOfType(ParserRuleContext ctx, Class contextVariable = this.getContextVariable(ctx, contextPath); + Variable contextVariable = this.getContextVariable(ctx, contextPath); this.exitFieldContextDeclaration(fieldId, contextPath, contextVariable); } else { String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); @@ -525,16 +554,15 @@ private void exitParentContextDeclaration() { } private void exitRootContextDeclaration() { - PathExpression contextPath = new PathExpression("/*"); + PathExpression contextPath = new NodePathExpression("/*"); String symbol = "ND-Root"; this.exitNodeContextDeclaration(symbol, contextPath); } - private void exitFieldContextDeclaration(String fieldId, PathExpression contextPath, Variable contextVariable) { + private void exitFieldContextDeclaration(String fieldId, PathExpression contextPath, Variable contextVariable) { this.efxContext.push(new FieldContext(fieldId, contextPath, contextVariable)); if (contextVariable != null) { - this.stack.declareTemplateVariable(contextVariable.name, - Expression.types.get(this.symbols.getTypeOfField(fieldId))); + this.stack.declareIdentifier(contextVariable); } } @@ -542,14 +570,20 @@ private void exitNodeContextDeclaration(String nodeId, PathExpression contextPat this.efxContext.push(new NodeContext(nodeId, contextPath)); } - private Variable getContextVariable(ContextDeclarationContext ctx, + private Variable getContextVariable(ContextDeclarationContext ctx, PathExpression contextPath) { if (ctx.contextVariableInitializer() == null) { return null; } + final String variableName = getVariableName(ctx.contextVariableInitializer()); - return new Variable<>(variableName, XPathContextualizer.contextualize(contextPath, contextPath), - this.script.composeVariableReference(variableName, PathExpression.class)); + final Class variableType = contextPath.getClass(); + + return new Variable(variableName, + this.script.composeVariableDeclaration(variableName, variableType), + this.symbols.getRelativePath(contextPath, contextPath), + this.script.composeVariableReference(variableName, variableType)); + } @Override @@ -557,58 +591,73 @@ public void enterTemplateVariableList(TemplateVariableListContext ctx) { this.stack.push(new VariableList()); } + // #region Variable Initializers -------------------------------------------- + @Override public void exitStringVariableInitializer(StringVariableInitializerContext ctx) { - this.exitVariableInitializer(getVariableName(ctx), StringVariable.class, - StringExpression.class); + this.exitVariableInitializer(getVariableName(ctx), StringExpression.class); } @Override public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) { - this.exitVariableInitializer(getVariableName(ctx), BooleanVariable.class, - BooleanExpression.class); + this.exitVariableInitializer(getVariableName(ctx), BooleanExpression.class); } @Override public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) { - this.exitVariableInitializer(getVariableName(ctx), NumericVariable.class, - NumericExpression.class); + this.exitVariableInitializer(getVariableName(ctx), NumericExpression.class); } @Override public void exitDateVariableInitializer(DateVariableInitializerContext ctx) { - this.exitVariableInitializer(getVariableName(ctx), DateVariable.class, - DateExpression.class); + this.exitVariableInitializer(getVariableName(ctx), DateExpression.class); } @Override public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { - this.exitVariableInitializer(getVariableName(ctx), TimeVariable.class, - TimeExpression.class); + this.exitVariableInitializer(getVariableName(ctx), TimeExpression.class); } @Override public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { - this.exitVariableInitializer(getVariableName(ctx), DurationVariable.class, - DurationExpression.class); + this.exitVariableInitializer(getVariableName(ctx), DurationExpression.class); } - private > void exitVariableInitializer( - String variableName, Class variableType, Class expressionType) { - T expression = this.stack.pop(expressionType); + private void exitVariableInitializer( + String variableName, Class expressionType) { + var expression = this.stack.pop(expressionType); VariableList variables = this.stack.pop(VariableList.class); try { - Constructor constructor = - variableType.getConstructor(String.class, expressionType, expressionType); - variables.push(constructor.newInstance(variableName, expression, - this.script.composeVariableReference(variableName, expressionType))); + var variable = new Variable(variableName, + this.script.composeVariableDeclaration(variableName, expression.getClass()), + expression, + this.script.composeVariableReference(variableName, expression.getClass())); + variables.push(variable); this.stack.push(variables); - this.stack.declareTemplateVariable(variableName, expressionType); + this.stack.declareIdentifier(variable); } catch (Exception e) { throw new ParseCancellationException(e); } } + // #endregion Variable Initializers ----------------------------------------- + + // #endregion New in EFX-2 -------------------------------------------------- + + /** + * This method changes the current EFX context. + * + * The EFX context is always assumed to be either a Field or a Node. Any predicate included in the + * EFX context declaration is not relevant and is ignored. + */ + @Override + public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { + if (ctx.templateVariableList() == null) { + this.stack.push(new VariableList()); + } + } + + // #endregion Context Declaration Blocks {...} ------------------------------ // #region Template lines -------------------------------------------------- @@ -648,7 +697,7 @@ public void exitTemplateLine(TemplateLineContext ctx) { final int indentLevel = this.getIndentLevel(ctx); final int indentChange = indentLevel - this.blockStack.currentIndentationLevel(); final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup(""); - final VariableList templateVariables = this.stack.pop(VariableList.class); + final VariableList variables = this.stack.pop(VariableList.class); final Integer outlineNumber = ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1; assert this.stack.empty() : "Stack should be empty at this point."; @@ -660,20 +709,19 @@ public void exitTemplateLine(TemplateLineContext ctx) { throw new ParseCancellationException(START_INDENT_AT_ZERO); } this.blockStack.pushChild(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.currentContext()), templateVariables); + this.relativizeContext(lineContext, this.blockStack.currentContext()), variables); } else if (indentChange < 0) { this.blockStack.pushSibling(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.parentContext()), templateVariables); + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } else if (indentChange == 0) { if (blockStack.isEmpty()) { assert indentLevel == 0 : UNEXPECTED_INDENTATION; this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, - this.relativizeContext(lineContext, this.rootBlock.getContext()), templateVariables)); + this.relativizeContext(lineContext, this.rootBlock.getContext()), variables)); } else { this.blockStack.pushSibling(outlineNumber, content, - this.relativizeContext(lineContext, this.blockStack.parentContext()), - templateVariables); + this.relativizeContext(lineContext, this.blockStack.parentContext()), variables); } } } @@ -683,14 +731,12 @@ private Context relativizeContext(Context childContext, Context parentContext) { return childContext; } - if (FieldContext.class.isAssignableFrom(childContext.getClass())) { + if (childContext.isFieldContext()) { return new FieldContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath()), - childContext.variable()); + this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath()), childContext.variable()); } - assert NodeContext.class.isAssignableFrom( - childContext.getClass()) : "Child context should be either a FieldContext NodeContext."; + assert childContext.isNodeContext() : "Child context should be either a FieldContext NodeContext."; return new NodeContext(childContext.symbol(), childContext.absolutePath(), this.symbols.getRelativePath(childContext.absolutePath(), parentContext.absolutePath())); @@ -787,40 +833,40 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) { if (filedId != null) { final ContextVariableInitializerContext initializer = ctx.contextVariableInitializer(); if (initializer != null) { - this.stack.declareTemplateVariable(getVariableName(initializer), - Expression.types.get(this.symbols.getTypeOfField(filedId))); + var t = FieldTypes.fromString(this.symbols.getTypeOfField(filedId)); + this.stack.declareIdentifier(new Variable(getVariableName(initializer), PathExpression.instantiate("", t), PathExpression.instantiate("", t), PathExpression.instantiate("", t))); } } } @Override public void exitStringVariableInitializer(StringVariableInitializerContext ctx) { - this.stack.declareTemplateVariable(getVariableName(ctx), StringExpression.class); + this.stack.declareIdentifier(new Variable(getVariableName(ctx), StringExpression.empty(), StringExpression.empty(), StringExpression.empty())); } @Override public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) { - this.stack.declareTemplateVariable(getVariableName(ctx), BooleanExpression.class); + this.stack.declareIdentifier(new Variable(getVariableName(ctx), BooleanExpression.empty(), BooleanExpression.empty(), BooleanExpression.empty())); } @Override public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) { - this.stack.declareTemplateVariable(getVariableName(ctx), NumericExpression.class); + this.stack.declareIdentifier(new Variable(getVariableName(ctx), NumericExpression.empty(), NumericExpression.empty(), NumericExpression.empty())); } @Override public void exitDateVariableInitializer(DateVariableInitializerContext ctx) { - this.stack.declareTemplateVariable(getVariableName(ctx), DateExpression.class); + this.stack.declareIdentifier(new Variable(getVariableName(ctx), DateExpression.empty(), DateExpression.empty(), DateExpression.empty())); } @Override public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { - this.stack.declareTemplateVariable(getVariableName(ctx), TimeExpression.class); + this.stack.declareIdentifier(new Variable(getVariableName(ctx), TimeExpression.empty(), TimeExpression.empty(), TimeExpression.empty())); } @Override public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { - this.stack.declareTemplateVariable(getVariableName(ctx), DurationExpression.class); + this.stack.declareIdentifier(new Variable(getVariableName(ctx), DurationExpression.empty(), DurationExpression.empty(), DurationExpression.empty())); } // #endregion Template Variables ------------------------------------------ diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java index b5e4adcb..7482ef89 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java @@ -5,7 +5,10 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; -import eu.europa.ted.efx.model.Expression.PathExpression; + +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.path.NodePathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; import eu.europa.ted.efx.xpath.XPath20Parser.AbbrevforwardstepContext; import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext; @@ -25,11 +28,11 @@ public class XPathAttributeLocator extends XPath20BaseListener { private int inPredicate = 0; private int splitPosition = -1; - private PathExpression path; + private String path; private String attribute; - public PathExpression getPath() { - return path; + public NodePathExpression getPath() { + return Expression.instantiate(path, NodePathExpression.class); } public String getAttribute() { @@ -57,18 +60,21 @@ public void exitAbbrevforwardstep(AbbrevforwardstepContext ctx) { this.attribute = ctx.nodetest().getText(); } } - public static XPathAttributeLocator findAttribute(final PathExpression xpath) { + return findAttribute(xpath.getScript()); + } + + public static XPathAttributeLocator findAttribute(final String xpath) { final XPathAttributeLocator locator = new XPathAttributeLocator(); - if (!xpath.script.contains("@")) { + if (!xpath.contains("@")) { locator.path = xpath; locator.attribute = null; return locator; } - final CharStream inputStream = CharStreams.fromString(xpath.script); + final CharStream inputStream = CharStreams.fromString(xpath); final XPath20Lexer lexer = new XPath20Lexer(inputStream); final CommonTokenStream tokens = new CommonTokenStream(lexer); final XPath20Parser parser = new XPath20Parser(tokens); @@ -79,11 +85,11 @@ public static XPathAttributeLocator findAttribute(final PathExpression xpath) { if (locator.splitPosition > -1) { // The attribute we are looking for is at splitPosition - String path = xpath.script.substring(0, locator.splitPosition); + String path = xpath.substring(0, locator.splitPosition); while (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } - locator.path = new PathExpression(path); + locator.path = path; } else { // the XPAth does not point to an attribute locator.path = xpath; diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java index f3279cab..499285ad 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -18,7 +18,7 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; import eu.europa.ted.efx.xpath.XPath20Parser.AxisstepContext; import eu.europa.ted.efx.xpath.XPath20Parser.FilterexprContext; import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext; @@ -38,7 +38,7 @@ public XPathContextualizer(CharStream inputStream) { * XPath is comprised of. */ private static Queue getSteps(PathExpression xpath) { - return getSteps(xpath.script); + return getSteps(xpath.getScript()); } /** @@ -64,15 +64,25 @@ private static Queue getSteps(String xpath) { * Makes the given xpath relative to the given context xpath. * * @param contextXpath the context xpath - * @param xpath the xpath to contextualize + * @param xpath the xpath to contextualize * @return the contextualized xpath */ public static PathExpression contextualize(final PathExpression contextXpath, final PathExpression xpath) { + // If we are asked to contextualise against a null or empty context + // then we must return the original xpath (instead of throwing an exception). + if (contextXpath == null || contextXpath.getScript().isEmpty()) { + return xpath; + } + return PathExpression.instantiate(contextualize(contextXpath.getScript(), xpath.getScript()), xpath.getDataType()); + } + + public static String contextualize(final String contextXpath, + final String xpath) { // If we are asked to contextualise against a null or empty context // then we must return the original xpath (instead of throwing an exception). - if (contextXpath == null || contextXpath.script.isEmpty()) { + if (contextXpath == null || contextXpath.isEmpty()) { return xpath; } @@ -83,7 +93,7 @@ public static PathExpression contextualize(final PathExpression contextXpath, } public static boolean hasPredicate(final PathExpression xpath, String match) { - return hasPredicate(xpath.script, match); + return hasPredicate(xpath.getScript(), match); } public static boolean hasPredicate(final String xpath, String match) { @@ -91,7 +101,7 @@ public static boolean hasPredicate(final String xpath, String match) { } public static PathExpression addPredicate(final PathExpression pathExpression, final String predicate) { - return new PathExpression(addPredicate(pathExpression.script, predicate)); + return PathExpression.instantiate(addPredicate(pathExpression.getScript(), predicate), pathExpression.getDataType()); } /** @@ -147,18 +157,18 @@ private static StepInfo getLastAxisStep(LinkedList steps) { public static PathExpression join(final PathExpression first, final PathExpression second) { - if (first == null || first.script.trim().isEmpty()) { + if (first == null || first.getScript().trim().isEmpty()) { return second; } - if (second == null || second.script.trim().isEmpty()) { + if (second == null || second.getScript().trim().isEmpty()) { return first; } LinkedList firstPartSteps = new LinkedList<>(getSteps(first)); LinkedList secondPartSteps = new LinkedList<>(getSteps(second)); - return getJoinedXPath(firstPartSteps, secondPartSteps); + return PathExpression.instantiate(getJoinedXPath(firstPartSteps, secondPartSteps), second.getDataType()); } public static PathExpression addAxis(String axis, PathExpression path) { @@ -168,11 +178,11 @@ public static PathExpression addAxis(String axis, PathExpression path) { steps.removeFirst(); } - return new PathExpression( - axis + "::" + steps.stream().map(s -> s.stepText).collect(Collectors.joining("/"))); + return PathExpression.instantiate( + axis + "::" + steps.stream().map(s -> s.stepText).collect(Collectors.joining("/")), path.getDataType()); } - private static PathExpression getContextualizedXpath(Queue contextQueue, + private static String getContextualizedXpath(Queue contextQueue, final Queue pathQueue) { // We will store the relative xPath here as we build it. @@ -240,11 +250,11 @@ private static PathExpression getContextualizedXpath(Queue contextQueu } } - return new PathExpression(relativeXpath); + return relativeXpath; } - private static PathExpression getJoinedXPath(LinkedList first, + private static String getJoinedXPath(LinkedList first, final LinkedList second) { List dotSteps = Arrays.asList("..", "."); while (second.getFirst().stepText.equals("..") @@ -253,8 +263,8 @@ private static PathExpression getJoinedXPath(LinkedList first, first.removeLast(); } - return new PathExpression(first.stream().map(f -> f.stepText).collect(Collectors.joining("/")) - + "/" + second.stream().map(s -> s.stepText).collect(Collectors.joining("/"))); + return first.stream().map(f -> f.stepText).collect(Collectors.joining("/")) + + "/" + second.stream().map(s -> s.stepText).collect(Collectors.joining("/")); } /** diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index e935cd9e..ee6eda78 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -16,22 +16,23 @@ import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.TranslatorOptions; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.BooleanExpression; -import eu.europa.ted.efx.model.Expression.DateExpression; -import eu.europa.ted.efx.model.Expression.DateListExpression; -import eu.europa.ted.efx.model.Expression.DurationExpression; -import eu.europa.ted.efx.model.Expression.DurationListExpression; -import eu.europa.ted.efx.model.Expression.IteratorExpression; -import eu.europa.ted.efx.model.Expression.IteratorListExpression; -import eu.europa.ted.efx.model.Expression.ListExpression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.NumericListExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Expression.StringListExpression; -import eu.europa.ted.efx.model.Expression.TimeExpression; -import eu.europa.ted.efx.model.Expression.TimeListExpression; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorExpression; +import eu.europa.ted.efx.model.expressions.iteration.IteratorListExpression; +import eu.europa.ted.efx.model.expressions.path.NodePathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.BooleanExpression; +import eu.europa.ted.efx.model.expressions.scalar.DateExpression; +import eu.europa.ted.efx.model.expressions.scalar.DurationExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.TimeExpression; +import eu.europa.ted.efx.model.expressions.sequence.NumericSequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.SequenceExpression; +import eu.europa.ted.efx.model.expressions.sequence.StringSequenceExpression; +import eu.europa.ted.efx.model.types.EfxDataType; @SdkComponent(versions = {"2"}, componentType = SdkComponentType.SCRIPT_GENERATOR) @@ -59,40 +60,39 @@ public XPathScriptGenerator(TranslatorOptions translatorOptions) { } @Override - public T composeNodeReferenceWithPredicate(PathExpression nodeReference, - BooleanExpression predicate, Class type) { - return Expression.instantiate(nodeReference.script + '[' + predicate.script + ']', type); + public PathExpression composeNodeReferenceWithPredicate(PathExpression nodeReference, + BooleanExpression predicate) { + return PathExpression.instantiate(nodeReference.getScript() + '[' + predicate.getScript() + ']', EfxDataType.Node.class); } @Override - public T composeFieldReferenceWithPredicate(PathExpression fieldReference, - BooleanExpression predicate, Class type) { - return Expression.instantiate(fieldReference.script + '[' + predicate.script + ']', type); + public PathExpression composeFieldReferenceWithPredicate(PathExpression fieldReference, + BooleanExpression predicate) { + return PathExpression.instantiate(fieldReference.getScript() + '[' + predicate.getScript() + ']', fieldReference.getDataType()); } @Override - public T composeFieldReferenceWithAxis(final PathExpression fieldReference, - final String axis, Class type) { - return Expression.instantiate(XPathContextualizer.addAxis(axis, fieldReference).script, type); + public PathExpression composeFieldReferenceWithAxis(final PathExpression fieldReference, + final String axis) { + return PathExpression.instantiate(XPathContextualizer.addAxis(axis, fieldReference).getScript(), fieldReference.getDataType()); } @Override - public T composeFieldValueReference(PathExpression fieldReference, - Class type) { - if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) { - return Expression.instantiate(fieldReference.script + "/normalize-space(text())", type); + public PathExpression composeFieldValueReference(PathExpression fieldReference) { + if (fieldReference.is(EfxDataType.String.class)) { + return PathExpression.instantiate(fieldReference.getScript() + "/normalize-space(text())", fieldReference.getDataType()); } - if (NumericExpression.class.isAssignableFrom(type) || NumericListExpression.class.isAssignableFrom(type)) { - return Expression.instantiate(fieldReference.script + "/number()", type); + if (fieldReference.is(EfxDataType.Number.class)) { + return PathExpression.instantiate(fieldReference.getScript() + "/number()", fieldReference.getDataType()); } - if (DateExpression.class.isAssignableFrom(type) || DateListExpression.class.isAssignableFrom(type)) { - return Expression.instantiate(fieldReference.script + "/xs:date(text())", type); + if (fieldReference.is(EfxDataType.Date.class)) { + return PathExpression.instantiate(fieldReference.getScript() + "/xs:date(text())", fieldReference.getDataType()); } - if (TimeExpression.class.isAssignableFrom(type) || TimeListExpression.class.isAssignableFrom(type)) { - return Expression.instantiate(fieldReference.script + "/xs:time(text())", type); + if (fieldReference.is(EfxDataType.Time.class)) { + return PathExpression.instantiate(fieldReference.getScript() + "/xs:time(text())", fieldReference.getDataType()); } - if (DurationExpression.class.isAssignableFrom(type) || DurationListExpression.class.isAssignableFrom(type)) { - return Expression.instantiate("(for $F in " + fieldReference.script + " return (if ($F/@unitCode='WEEK')" + // + if (fieldReference.is(EfxDataType.Duration.class)) { + return PathExpression.instantiate("(for $F in " + fieldReference.getScript() + " return (if ($F/@unitCode='WEEK')" + // " then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D'))" + // " else if ($F/@unitCode='DAY')" + // " then xs:dayTimeDuration(concat('P', $F/number(), 'D'))" + // @@ -102,46 +102,46 @@ public T composeFieldValueReference(PathExpression fieldR " then xs:yearMonthDuration(concat('P', $F/number(), 'M'))" + // // " else if (" + fieldReference.script + ")" + // // " then fn:error('Invalid @unitCode')" + // - " else ()))", type); + " else ()))", fieldReference.getDataType()); } - return Expression.instantiate(fieldReference.script, type); + return PathExpression.instantiate(fieldReference.getScript(), fieldReference.getDataType()); } @Override - public T composeFieldAttributeReference(PathExpression fieldReference, + public T composeFieldAttributeReference(PathExpression fieldReference, String attribute, Class type) { return Expression.instantiate( - fieldReference.script + (fieldReference.script.isEmpty() ? "" : "/") + "@" + attribute, + fieldReference.getScript() + (fieldReference.getScript().isEmpty() ? "" : "/") + "@" + attribute, type); } @Override - public T composeVariableReference(String variableName, Class type) { + public T composeVariableReference(String variableName, Class type) { return Expression.instantiate("$" + variableName, type); } @Override - public T composeVariableDeclaration(String variableName, Class type) { + public T composeVariableDeclaration(String variableName, Class type) { return Expression.instantiate("$" + variableName, type); } @Override - public T composeParameterDeclaration(String parameterName, + public T composeParameterDeclaration(String parameterName, Class type) { return Expression.empty(type); } @Override - public > L composeList(List list, - Class type) { + public T composeList(List list, + Class type) { if (list == null || list.isEmpty()) { return Expression.instantiate("()", type); } final StringJoiner joiner = new StringJoiner(",", "(", ")"); - for (final T item : list) { - joiner.add(item.script); + for (final ScalarExpression item : list) { + joiner.add(item.getScript()); } return Expression.instantiate(joiner.toString(), type); } @@ -185,92 +185,91 @@ public DurationExpression getDurationLiteralEquivalent(final String literal) { } @Override - public > BooleanExpression composeContainsCondition( - T needle, L haystack) { - return new BooleanExpression(String.format("%s = %s", needle.script, haystack.script)); + public BooleanExpression composeContainsCondition( + ScalarExpression needle, SequenceExpression haystack) { + return new BooleanExpression(String.format("%s = %s", needle.getScript(), haystack.getScript())); } @Override public BooleanExpression composePatternMatchCondition(StringExpression expression, String pattern) { return new BooleanExpression( - String.format("fn:matches(normalize-space(%s), %s)", expression.script, pattern)); + String.format("fn:matches(normalize-space(%s), %s)", expression.getScript(), pattern)); } @Override - public BooleanExpression composeAllSatisfy(ListExpression list, + public BooleanExpression composeAllSatisfy(SequenceExpression list, String variableName, BooleanExpression booleanExpression) { return new BooleanExpression( - "every " + variableName + " in " + list.script + " satisfies " + booleanExpression.script); + "every " + variableName + " in " + list.getScript() + " satisfies " + booleanExpression.getScript()); } @Override - public BooleanExpression composeAllSatisfy( + public BooleanExpression composeAllSatisfy( IteratorListExpression iterators, BooleanExpression booleanExpression) { return new BooleanExpression( - "every " + iterators.script + " satisfies " + booleanExpression.script); + "every " + iterators.getScript() + " satisfies " + booleanExpression.getScript()); } @Override - public BooleanExpression composeAnySatisfies(ListExpression list, + public BooleanExpression composeAnySatisfies(SequenceExpression list, String variableName, BooleanExpression booleanExpression) { return new BooleanExpression( - "some " + variableName + " in " + list.script + " satisfies " + booleanExpression.script); + "some " + variableName + " in " + list.getScript() + " satisfies " + booleanExpression.getScript()); } @Override - public BooleanExpression composeAnySatisfies( + public BooleanExpression composeAnySatisfies( IteratorListExpression iterators, BooleanExpression booleanExpression) { return new BooleanExpression( - "some " + iterators.script + " satisfies " + booleanExpression.script); + "some " + iterators.getScript() + " satisfies " + booleanExpression.getScript()); } @Override - public T composeConditionalExpression(BooleanExpression condition, + public T composeConditionalExpression(BooleanExpression condition, T whenTrue, T whenFalse, Class type) { return Expression.instantiate( - "(if " + condition.script + " then " + whenTrue.script + " else " + whenFalse.script + ")", + "(if " + condition.getScript() + " then " + whenTrue.getScript() + " else " + whenFalse.getScript() + ")", type); } @Override - public , T2 extends Expression, L2 extends ListExpression> L2 composeForExpression( - String variableName, L1 sourceList, T2 expression, Class targetListType) { + public T2 composeForExpression( + String variableName, T1 sourceList, ScalarExpression expression, Class targetListType) { return Expression.instantiate( - "for " + variableName + " in " + sourceList.script + " return " + expression.script, + "for " + variableName + " in " + sourceList.getScript() + " return " + expression.getScript(), targetListType); } @Override - public > L2 composeForExpression( - IteratorListExpression iterators, T2 expression, Class targetListType) { - return Expression.instantiate("for " + iterators.script + " return " + expression.script, + public T composeForExpression( + IteratorListExpression iterators, ScalarExpression expression, Class targetListType) { + return Expression.instantiate("for " + iterators.getScript() + " return " + expression.getScript(), targetListType); } @Override - public > IteratorExpression composeIteratorExpression( - String variableName, L sourceList) { - return new IteratorExpression(variableName + " in " + sourceList.script); + public IteratorExpression composeIteratorExpression(Expression variableDeclarationExpression, SequenceExpression sourceList) { + return new IteratorExpression(variableDeclarationExpression.getScript() + " in " + sourceList.getScript()); } - @Override - public IteratorExpression composeIteratorExpression( - String variableName, PathExpression pathExpression) { - return new IteratorExpression(variableName + " in " + pathExpression.script); - } + // @Override + // public IteratorExpression composeIteratorExpression( + // String variableName, EfxPathExpression pathExpression) { + // return new IteratorExpression(variableName + " in " + pathExpression.getScript()); + // } @Override public IteratorListExpression composeIteratorList(List iterators) { return new IteratorListExpression( - iterators.stream().map(i -> i.script).collect(Collectors.joining(", ", "", ""))); + iterators.stream().map(i -> i.getScript()).collect(Collectors.joining(", ", "", ""))); } @Override public T composeParenthesizedExpression(T expression, Class type) { try { Constructor ctor = type.getConstructor(String.class); - return ctor.newInstance("(" + expression.script + ")"); + return ctor.newInstance("(" + expression.getScript() + ")"); } catch (Exception e) { throw new ParseCancellationException(e); } @@ -278,15 +277,15 @@ public T composeParenthesizedExpression(T expression, Cla @Override public PathExpression composeExternalReference(StringExpression externalReference) { - return new PathExpression( - "fn:doc(concat($urlPrefix, " + externalReference.script + "))"); + return new NodePathExpression( + "fn:doc(concat($urlPrefix, " + externalReference.getScript() + "))"); } @Override public PathExpression composeFieldInExternalReference(PathExpression externalReference, PathExpression fieldReference) { - return new PathExpression(externalReference.script + fieldReference.script); + return PathExpression.instantiate(externalReference.getScript() + fieldReference.getScript(), fieldReference.getDataType()); } @@ -298,10 +297,9 @@ public PathExpression joinPaths(final PathExpression first, final PathExpression //#region Indexers ---------------------------------------------------------- @Override - public > T composeIndexer(L list, - NumericExpression index, - Class type) { - return Expression.instantiate(String.format("%s[%s]", list.script, index.script), type); + public T composeIndexer(SequenceExpression list, + NumericExpression index, Class type) { + return Expression.instantiate(String.format("%s[%s]", list.getScript(), index.getScript()), type); } //#endregion Indexers ------------------------------------------------------- @@ -313,30 +311,30 @@ public > T composeIndexer(L li public BooleanExpression composeLogicalAnd(BooleanExpression leftOperand, BooleanExpression rightOperand) { return new BooleanExpression( - String.format("%s and %s", leftOperand.script, rightOperand.script)); + String.format("%s and %s", leftOperand.getScript(), rightOperand.getScript())); } @Override public BooleanExpression composeLogicalOr(BooleanExpression leftOperand, BooleanExpression rightOperand) { return new BooleanExpression( - String.format("%s or %s", leftOperand.script, rightOperand.script)); + String.format("%s or %s", leftOperand.getScript(), rightOperand.getScript())); } @Override public BooleanExpression composeLogicalNot(BooleanExpression condition) { - return new BooleanExpression(String.format("not(%s)", condition.script)); + return new BooleanExpression(String.format("not(%s)", condition.getScript())); } @Override public BooleanExpression composeExistsCondition(PathExpression reference) { - return new BooleanExpression(reference.script); + return new BooleanExpression(reference.getScript()); } @Override public BooleanExpression composeUniqueValueCondition(PathExpression needle, PathExpression haystack) { - return new BooleanExpression("count(for $x in " + needle.script + ", $y in " + haystack.script + return new BooleanExpression("count(for $x in " + needle.getScript() + ", $y in " + haystack.getScript() + "[. = $x] return $y) = 1"); } @@ -347,39 +345,39 @@ public BooleanExpression composeUniqueValueCondition(PathExpression needle, @Override public BooleanExpression composeContainsCondition(StringExpression haystack, StringExpression needle) { - return new BooleanExpression("contains(" + haystack.script + ", " + needle.script + ")"); + return new BooleanExpression("contains(" + haystack.getScript() + ", " + needle.getScript() + ")"); } @Override public BooleanExpression composeStartsWithCondition(StringExpression text, StringExpression startsWith) { - return new BooleanExpression("starts-with(" + text.script + ", " + startsWith.script + ")"); + return new BooleanExpression("starts-with(" + text.getScript() + ", " + startsWith.getScript() + ")"); } @Override public BooleanExpression composeEndsWithCondition(StringExpression text, StringExpression endsWith) { - return new BooleanExpression("ends-with(" + text.script + ", " + endsWith.script + ")"); + return new BooleanExpression("ends-with(" + text.getScript() + ", " + endsWith.getScript() + ")"); } @Override - public BooleanExpression composeComparisonOperation(Expression leftOperand, String operator, - Expression rightOperand) { - if (DurationExpression.class.isAssignableFrom(leftOperand.getClass())) { + public BooleanExpression composeComparisonOperation(ScalarExpression leftOperand, String operator, + ScalarExpression rightOperand) { + if (leftOperand.is(EfxDataType.Duration.class)) { // TODO: Improve this implementation; Check if both are dayTime or yearMonth and compare // directly, otherwise, compare by adding to current-date() return new BooleanExpression( - "boolean(for $T in (current-date()) return ($T + " + leftOperand.script + " " - + operators.get(operator) + " $T + " + rightOperand.script + "))"); + "boolean(for $T in (current-date()) return ($T + " + leftOperand.getScript() + " " + + operators.get(operator) + " $T + " + rightOperand.getScript() + "))"); } return new BooleanExpression( - leftOperand.script + " " + operators.get(operator) + " " + rightOperand.script); + leftOperand.getScript() + " " + operators.get(operator) + " " + rightOperand.getScript()); } @Override - public BooleanExpression composeSequenceEqualFunction(ListExpression one, - ListExpression two) { - return new BooleanExpression("deep-equal(sort(" + one.script + "), sort(" + two.script + "))"); + public BooleanExpression composeSequenceEqualFunction(SequenceExpression one, + SequenceExpression two) { + return new BooleanExpression("deep-equal(sort(" + one.getScript() + "), sort(" + two.getScript() + "))"); } //#endregion Boolean functions ---------------------------------------------- @@ -387,30 +385,30 @@ public BooleanExpression composeSequenceEqualFunction(ListExpression list) { - return new NumericExpression("count(" + list.script + ")"); + public NumericExpression composeCountOperation(SequenceExpression list) { + return new NumericExpression("count(" + list.getScript() + ")"); } @Override public NumericExpression composeToNumberConversion(StringExpression text) { - return new NumericExpression("number(" + text.script + ")"); + return new NumericExpression("number(" + text.getScript() + ")"); } @Override - public NumericExpression composeSumOperation(NumericListExpression nodeSet) { - return new NumericExpression("sum(" + nodeSet.script + ")"); + public NumericExpression composeSumOperation(NumericSequenceExpression nodeSet) { + return new NumericExpression("sum(" + nodeSet.getScript() + ")"); } @Override public NumericExpression composeStringLengthCalculation(StringExpression text) { - return new NumericExpression("string-length(" + text.script + ")"); + return new NumericExpression("string-length(" + text.getScript() + ")"); } @Override public NumericExpression composeNumericOperation(NumericExpression leftOperand, String operator, NumericExpression rightOperand) { return new NumericExpression( - leftOperand.script + " " + operators.get(operator) + " " + rightOperand.script); + leftOperand.getScript() + " " + operators.get(operator) + " " + rightOperand.getScript()); } //#endregion Numeric functions ---------------------------------------------- @@ -421,48 +419,48 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public StringExpression composeSubstringExtraction(StringExpression text, NumericExpression start, NumericExpression length) { return new StringExpression( - "substring(" + text.script + ", " + start.script + ", " + length.script + ")"); + "substring(" + text.getScript() + ", " + start.getScript() + ", " + length.getScript() + ")"); } @Override public StringExpression composeSubstringExtraction(StringExpression text, NumericExpression start) { - return new StringExpression("substring(" + text.script + ", " + start.script + ")"); + return new StringExpression("substring(" + text.getScript() + ", " + start.getScript() + ")"); } @Override public StringExpression composeToStringConversion(NumericExpression number) { String formatString = this.translatorOptions.getDecimalFormat().adaptFormatString("0.##########"); - return new StringExpression("format-number(" + number.script + ", '" + formatString + "')"); + return new StringExpression("format-number(" + number.getScript() + ", '" + formatString + "')"); } @Override public StringExpression composeToUpperCaseConversion(StringExpression text) { - return new StringExpression("upper-case(" + text.script + ")"); + return new StringExpression("upper-case(" + text.getScript() + ")"); } @Override public StringExpression composeToLowerCaseConversion(StringExpression text) { - return new StringExpression("lower-case(" + text.script + ")"); + return new StringExpression("lower-case(" + text.getScript() + ")"); } @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( - "concat(" + list.stream().map(i -> i.script).collect(Collectors.joining(", ")) + ")"); + "concat(" + list.stream().map(i -> i.getScript()).collect(Collectors.joining(", ")) + ")"); } @Override - public StringExpression composeStringJoin(StringListExpression list, StringExpression separator) { + public StringExpression composeStringJoin(StringSequenceExpression list, StringExpression separator) { return new StringExpression( - "string-join(" + list.script + ", " + separator.script + ")"); + "string-join(" + list.getScript() + ", " + separator.getScript() + ")"); } @Override public StringExpression composeNumberFormatting(NumericExpression number, StringExpression format) { - String formatString = format.isLiteral ? this.translatorOptions.getDecimalFormat().adaptFormatString(format.script) : format.script; - return new StringExpression("format-number(" + number.script + ", " + formatString + ")"); + String formatString = format.isLiteral() ? this.translatorOptions.getDecimalFormat().adaptFormatString(format.getScript()) : format.getScript(); + return new StringExpression("format-number(" + number.getScript() + ", " + formatString + ")"); } @Override @@ -472,12 +470,12 @@ public StringExpression getStringLiteralFromUnquotedString(String value) { @Override public StringExpression getPreferredLanguage(PathExpression fieldReference) { - return new StringExpression("efx:preferred-language(" + fieldReference.script + ")"); + return new StringExpression("efx:preferred-language(" + fieldReference.getScript() + ")"); } @Override public StringExpression getTextInPreferredLanguage(PathExpression fieldReference) { - return new StringExpression("efx:preferred-language-text(" + fieldReference.script + ")"); + return new StringExpression("efx:preferred-language-text(" + fieldReference.getScript() + ")"); } //#endregion String functions ----------------------------------------------- @@ -486,17 +484,17 @@ public StringExpression getTextInPreferredLanguage(PathExpression fieldReference @Override public DateExpression composeToDateConversion(StringExpression date) { - return new DateExpression("xs:date(" + date.script + ")"); + return new DateExpression("xs:date(" + date.getScript() + ")"); } @Override public DateExpression composeAddition(DateExpression date, DurationExpression duration) { - return new DateExpression("(" + date.script + " + " + duration.script + ")"); + return new DateExpression("(" + date.getScript() + " + " + duration.getScript() + ")"); } @Override public DateExpression composeSubtraction(DateExpression date, DurationExpression duration) { - return new DateExpression("(" + date.script + " - " + duration.script + ")"); + return new DateExpression("(" + date.getScript() + " - " + duration.getScript() + ")"); } //#endregion Date functions ------------------------------------------------- @@ -505,7 +503,7 @@ public DateExpression composeSubtraction(DateExpression date, DurationExpression @Override public TimeExpression composeToTimeConversion(StringExpression time) { - return new TimeExpression("xs:time(" + time.script + ")"); + return new TimeExpression("xs:time(" + time.getScript() + ")"); } //#endregion Time functions ------------------------------------------------- @@ -514,58 +512,58 @@ public TimeExpression composeToTimeConversion(StringExpression time) { @Override public DurationExpression composeToDayTimeDurationConversion(StringExpression text) { - return new DurationExpression("xs:dayTimeDuration(" + text.script + ")"); + return new DurationExpression("xs:dayTimeDuration(" + text.getScript() + ")"); } @Override public DurationExpression composeToYearMonthDurationConversion(StringExpression text) { - return new DurationExpression("xs:yearMonthDuration(" + text.script + ")"); + return new DurationExpression("xs:yearMonthDuration(" + text.getScript() + ")"); } @Override public DurationExpression composeSubtraction(DateExpression startDate, DateExpression endDate) { - return new DurationExpression("xs:dayTimeDuration(" + endDate.script + " " + operators.get("-") - + " " + startDate.script + ")"); + return new DurationExpression("xs:dayTimeDuration(" + endDate.getScript() + " " + operators.get("-") + + " " + startDate.getScript() + ")"); } @Override public DurationExpression composeMultiplication(NumericExpression number, DurationExpression duration) { - return new DurationExpression("(" + number.script + " * " + duration.script + ")"); + return new DurationExpression("(" + number.getScript() + " * " + duration.getScript() + ")"); } @Override public DurationExpression composeAddition(DurationExpression left, DurationExpression right) { - return new DurationExpression("(" + left.script + " + " + right.script + ")"); + return new DurationExpression("(" + left.getScript() + " + " + right.getScript() + ")"); } @Override public DurationExpression composeSubtraction(DurationExpression left, DurationExpression right) { - return new DurationExpression("(" + left.script + " - " + right.script + ")"); + return new DurationExpression("(" + left.getScript() + " - " + right.getScript() + ")"); } @Override - public > L composeDistinctValuesFunction( - L list, Class listType) { - return Expression.instantiate("distinct-values(" + list.script + ")", listType); + public T composeDistinctValuesFunction( + T list, Class listType) { + return Expression.instantiate("distinct-values(" + list.getScript() + ")", listType); } @Override - public > L composeUnionFunction(L listOne, - L listTwo, Class listType) { + public T composeUnionFunction(T listOne, + T listTwo, Class listType) { return Expression - .instantiate("distinct-values((" + listOne.script + ", " + listTwo.script + "))", listType); + .instantiate("distinct-values((" + listOne.getScript() + ", " + listTwo.getScript() + "))", listType); } @Override - public > L composeIntersectFunction(L listOne, L listTwo, Class listType) { - return Expression.instantiate("distinct-values(for $L1 in " + listOne.script + " return if (some $L2 in " + listTwo.script + " satisfies $L1 = $L2) then $L1 else ())", listType); + public T composeIntersectFunction(T listOne, T listTwo, Class listType) { + return Expression.instantiate("distinct-values(for $L1 in " + listOne.getScript() + " return if (some $L2 in " + listTwo.getScript() + " satisfies $L1 = $L2) then $L1 else ())", listType); } @Override - public > L composeExceptFunction(L listOne, L listTwo, Class listType) { - return Expression.instantiate("distinct-values(for $L1 in " + listOne.script + " return if (every $L2 in " + listTwo.script + " satisfies $L1 != $L2) then $L1 else ())", listType); + public T composeExceptFunction(T listOne, T listTwo, Class listType) { + return Expression.instantiate("distinct-values(for $L1 in " + listOne.getScript() + " return if (every $L2 in " + listTwo.getScript() + " satisfies $L1 != $L2) then $L1 else ())", listType); } //#endregion Duration functions --------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java index a4a32736..d3be5133 100644 --- a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java @@ -2,17 +2,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; + import org.junit.jupiter.api.Test; -import eu.europa.ted.efx.model.Expression.PathExpression; + import eu.europa.ted.efx.xpath.XPathAttributeLocator; class XPathAttributeLocatorTest { private void testAttribute(final String attributePath, final String expectedPath, final String expectedAttribute) { final XPathAttributeLocator locator = - XPathAttributeLocator.findAttribute(new PathExpression(attributePath)); + XPathAttributeLocator.findAttribute(attributePath); - assertEquals(expectedPath, locator.getPath().script); + assertEquals(expectedPath, locator.getPath().getScript()); assertEquals(expectedAttribute, locator.getAttribute()); } @@ -30,8 +31,8 @@ void testXPathAttributeLocator_WithMultipleAttributes() { @Test void testXPathAttributeLocator_WithoutAttribute() { final XPathAttributeLocator locator = XPathAttributeLocator - .findAttribute(new PathExpression("/path/path[@otherAttribute = 'text']")); - assertEquals("/path/path[@otherAttribute = 'text']", locator.getPath().script); + .findAttribute("/path/path[@otherAttribute = 'text']"); + assertEquals("/path/path[@otherAttribute = 'text']", locator.getPath().getScript()); assertNull(locator.getAttribute()); } diff --git a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java index 19f92e9c..9e666a25 100644 --- a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java @@ -1,14 +1,14 @@ package eu.europa.ted.efx; import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; -import eu.europa.ted.efx.model.Expression.PathExpression; + import eu.europa.ted.efx.xpath.XPathContextualizer; class XPathContextualizerTest { private String contextualize(final String context, final String xpath) { - return XPathContextualizer.contextualize(new PathExpression(context), - new PathExpression(xpath)).script; + return XPathContextualizer.contextualize(context, xpath); } @Test diff --git a/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java b/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java index 6fb8b169..5b62a810 100644 --- a/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java @@ -16,7 +16,9 @@ import eu.europa.ted.eforms.sdk.entity.SdkField; import eu.europa.ted.eforms.sdk.entity.SdkNode; import eu.europa.ted.efx.interfaces.SymbolResolver; -import eu.europa.ted.efx.model.Expression.PathExpression; +import eu.europa.ted.efx.model.expressions.path.NodePathExpression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.types.FieldTypes; import eu.europa.ted.efx.xpath.XPathContextualizer; public abstract class AbstractSymbolResolverMock @@ -153,7 +155,7 @@ public PathExpression getAbsolutePathOfField(final String fieldId) { throw new ParseCancellationException( String.format("Unknown field identifier '%s'.", fieldId)); } - return new PathExpression(sdkField.getXpathAbsolute()); + return PathExpression.instantiate(sdkField.getXpathAbsolute(), FieldTypes.fromString(sdkField.getType())); } /** @@ -168,6 +170,6 @@ public PathExpression getAbsolutePathOfNode(final String nodeId) { throw new ParseCancellationException(String.format("Unknown node identifier '%s'.", nodeId)); } - return new PathExpression(sdkNode.getXpathAbsolute()); + return new NodePathExpression(sdkNode.getXpathAbsolute()); } } 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 d7255ac8..4a096305 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -4,46 +4,48 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; + import eu.europa.ted.efx.interfaces.MarkupGenerator; -import eu.europa.ted.efx.model.Expression; -import eu.europa.ted.efx.model.Expression.NumericExpression; -import eu.europa.ted.efx.model.Expression.PathExpression; -import eu.europa.ted.efx.model.Expression.StringExpression; -import eu.europa.ted.efx.model.Markup; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.templates.Markup; public class MarkupGeneratorMock implements MarkupGenerator { @Override public Markup renderVariableExpression(Expression valueReference) { - return new Markup(String.format("eval(%s)", valueReference.script)); + return new Markup(String.format("eval(%s)", valueReference.getScript())); } @Override public Markup renderLabelFromKey(StringExpression key) { - return this.renderLabelFromKey(key, Expression.empty(NumericExpression.class)); + return this.renderLabelFromKey(key, NumericExpression.empty()); } @Override public Markup renderLabelFromKey(StringExpression key, NumericExpression quantity) { if (quantity.isEmpty()) { - return new Markup(String.format("label(%s)", key.script)); + return new Markup(String.format("label(%s)", key.getScript())); } - return new Markup(String.format("label(%s, %s)", key.script, quantity.script)); + return new Markup(String.format("label(%s, %s)", key.getScript(), quantity.getScript())); } @Override public Markup renderLabelFromExpression(Expression expression) { - return this.renderLabelFromExpression(expression, Expression.empty(NumericExpression.class)); + return this.renderLabelFromExpression(expression, NumericExpression.empty()); } @Override public Markup renderLabelFromExpression(Expression expression, NumericExpression quantity) { if (quantity.isEmpty()) { - return new Markup(String.format("label(%s)", expression.script)); + return new Markup(String.format("label(%s)", expression.getScript())); } - return new Markup(String.format("label(%s, %s)", expression.script, quantity.script)); + return new Markup(String.format("label(%s, %s)", expression.getScript(), quantity.getScript())); } @Override @@ -80,7 +82,7 @@ public Markup renderFragmentInvocation(String name, PathExpression context) { @Override public Markup renderFragmentInvocation(String name, PathExpression context, Set> variables) { - return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name, + return new Markup(String.format("for-each(%s).call(%s(%s))", context.getScript(), name, variables.stream() .map(v -> String.format("%s:%s", v.getLeft(), v.getRight())) .collect(Collectors.joining(", ")))); diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java index eeddb116..58205ade 100644 --- a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java +++ b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java @@ -1,16 +1,20 @@ package eu.europa.ted.efx.mock.sdk1; import static java.util.Map.entry; + import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; + import eu.europa.ted.efx.mock.AbstractSymbolResolverMock; +import eu.europa.ted.efx.model.expressions.path.PathExpression; import eu.europa.ted.efx.sdk1.entity.SdkCodelistV1; import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; import eu.europa.ted.efx.sdk1.entity.SdkNodeV1; +import eu.europa.ted.efx.xpath.XPathAttributeLocator; public class SymbolResolverMockV1 extends AbstractSymbolResolverMock { @@ -50,4 +54,19 @@ protected Class getSdkFieldClass() { protected String getFieldsJsonFilename() { return "fields-sdk1.json"; } + + @Override + public Boolean isAttributeField(final String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).hasAttribute(); + } + + @Override + public String getAttributeOfField(String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttribute(); + } + + @Override + public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getPath(); + } } diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java index ae3e6840..41b18f95 100644 --- a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java +++ b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java @@ -1,6 +1,7 @@ package eu.europa.ted.efx.mock.sdk2; import static java.util.Map.entry; + import java.io.IOException; import java.util.Arrays; import java.util.HashMap; @@ -8,10 +9,13 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; + import eu.europa.ted.efx.mock.AbstractSymbolResolverMock; +import eu.europa.ted.efx.model.expressions.path.PathExpression; import eu.europa.ted.efx.sdk2.entity.SdkCodelistV2; import eu.europa.ted.efx.sdk2.entity.SdkFieldV2; import eu.europa.ted.efx.sdk2.entity.SdkNodeV2; +import eu.europa.ted.efx.xpath.XPathAttributeLocator; public class SymbolResolverMockV2 extends AbstractSymbolResolverMock { @@ -76,4 +80,19 @@ protected Class getSdkFieldClass() { protected String getFieldsJsonFilename() { return "fields-sdk2.json"; } + + @Override + public Boolean isAttributeField(final String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).hasAttribute(); + } + + @Override + public String getAttributeOfField(String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttribute(); + } + + @Override + public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getPath(); + } } diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java index 16862e4d..b165937c 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -1034,7 +1034,7 @@ void testFieldAttributeValueReference() { @Test void testFieldAttributeValueReference_SameElementContext() { - testExpressionTranslationWithContext("@Attribute = 'text'", "BT-00-Text", + testExpressionTranslationWithContext("./@Attribute = 'text'", "BT-00-Text", "BT-00-Attribute == 'text'"); } 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 67a83e13..a98697b5 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java @@ -210,14 +210,14 @@ void testShorthandIndirectLabelReferenceForIndicator() { @Test void testShorthandIndirectLabelReferenceForCode() { assertEquals( - "let block01() -> { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", + "let block01() -> { label(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-Code}")); } @Test void testShorthandIndirectLabelReferenceForInternalCode() { assertEquals( - "let block01() -> { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", + "let block01() -> { label(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}")); } @@ -297,7 +297,7 @@ void testShorthandLabelReferenceFromContext_WithNodeContext() { @Test void testShorthandIndirectLabelReferenceFromContextField() { assertEquals( - "let block01() -> { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "let block01() -> { label(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} #value")); } @@ -311,14 +311,14 @@ void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() { @Test void testShorthandFieldValueReferenceFromContextField() { - assertEquals("let block01() -> { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01())", + assertEquals("let block01() -> { eval(./normalize-space(text())) }\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} $value")); } @Test void testShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( - "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", + "let block01() -> { text('blah ')label(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())", translateTemplate("{BT-00-Code} blah #value blah $value blah")); } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index f8b20805..c0ce95a3 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1064,7 +1064,7 @@ void testFieldAttributeValueReference() { @Test void testFieldAttributeValueReference_SameElementContext() { - testExpressionTranslationWithContext("@Attribute = 'text'", "BT-00-Text", + testExpressionTranslationWithContext("./@Attribute = 'text'", "BT-00-Text", "BT-00-Attribute == 'text'"); } 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 7274879b..f78cc073 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -299,14 +299,14 @@ void testShorthandIndirectLabelReferenceForIndicator() { @Test void testShorthandIndirectLabelReferenceForCode() { assertEquals( - "let block01() -> { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", + "let block01() -> { label(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-Code}")); } @Test void testShorthandIndirectLabelReferenceForInternalCode() { assertEquals( - "let block01() -> { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", + "let block01() -> { label(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())", translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}")); } @@ -386,7 +386,7 @@ void testShorthandLabelReferenceFromContext_WithNodeContext() { @Test void testShorthandIndirectLabelReferenceFromContextField() { assertEquals( - "let block01() -> { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", + "let block01() -> { label(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} #value")); } @@ -400,14 +400,14 @@ void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() { @Test void testShorthandFieldValueReferenceFromContextField() { - assertEquals("let block01() -> { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01())", + assertEquals("let block01() -> { eval(./normalize-space(text())) }\nfor-each(/*/PathNode/CodeField).call(block01())", translateTemplate("{BT-00-Code} $value")); } @Test void testShorthandFieldValueReferenceFromContextField_WithText() { assertEquals( - "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' blah ')eval(.)text(' blah') }\nfor-each(/*/PathNode/CodeField).call(block01())", + "let block01() -> { text('blah ')label(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())", translateTemplate("{BT-00-Code} blah #value blah $value blah")); } From 01117d49691ff61126e8041d30ef5e2107df55ba Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Tue, 25 Jul 2023 01:35:36 +0200 Subject: [PATCH 52/57] Adaptations suggested in PR comments. --- .../ted/eforms/sdk/SdkSymbolResolver.java | 10 +++++----- .../ted/efx/interfaces/SymbolResolver.java | 4 ++-- .../eu/europa/ted/efx/model/CallStack.java | 12 +++++------ ...CallStackObject.java => ParsedEntity.java} | 2 +- .../ted/efx/model/expressions/Expression.java | 4 ++-- .../ted/efx/model/templates/Markup.java | 4 ++-- .../ted/efx/model/variables/Identifier.java | 4 ++-- .../ted/efx/model/variables/VariableList.java | 4 ++-- .../efx/sdk1/EfxExpressionTranslatorV1.java | 4 ++-- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 2 +- .../efx/sdk2/EfxExpressionTranslatorV2.java | 4 ++-- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 2 +- .../ted/efx/xpath/XPathAttributeLocator.java | 20 ++++++++++++++++--- .../ted/efx/XPathAttributeLocatorTest.java | 8 ++++---- .../efx/mock/sdk1/SymbolResolverMockV1.java | 8 ++++---- .../efx/mock/sdk2/SymbolResolverMockV2.java | 8 ++++---- 16 files changed, 57 insertions(+), 43 deletions(-) rename src/main/java/eu/europa/ted/efx/model/{CallStackObject.java => ParsedEntity.java} (88%) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java index 2f49cb93..294e7b82 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -177,7 +177,7 @@ public String getRootCodelistOfField(final String fieldId) { } @Override - public Boolean isAttributeField(final String fieldId) { + public boolean isAttributeField(final String fieldId) { if (!additionalFieldInfoMap.containsKey(fieldId)) { this.cacheAdditionalFieldInfo(fieldId); } @@ -185,7 +185,7 @@ public Boolean isAttributeField(final String fieldId) { } @Override - public String getAttributeOfField(final String fieldId) { + public String getAttributeNameFromAttributeField(final String fieldId) { if (!additionalFieldInfoMap.containsKey(fieldId)) { this.cacheAdditionalFieldInfo(fieldId); } @@ -208,7 +208,7 @@ public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(final String fie * TODO: Move this additional info to SdkField class, and move the XPathAttributeLocator to the eforms-core-library. */ class AdditionalFieldInfo { - public Boolean isAttribute; + public boolean isAttribute; public String attributeName; public PathExpression pathWithoutAttribute; } @@ -226,8 +226,8 @@ private void cacheAdditionalFieldInfo(final String fieldId) { var parsedPath = XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)); var info = new AdditionalFieldInfo(); info.isAttribute = parsedPath.hasAttribute(); - info.attributeName = parsedPath.getAttribute(); - info.pathWithoutAttribute = parsedPath.getPath(); + info.attributeName = parsedPath.getAttributeName(); + info.pathWithoutAttribute = parsedPath.getElementPath(); additionalFieldInfoMap.put(fieldId, info); } } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java b/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java index 36391408..5e31afc1 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java @@ -119,7 +119,7 @@ public PathExpression getRelativePathOfNode(final String nodeId, * @param fieldId The identifier of the field to look for. * @return True if the field points to an @attribute, false otherwise. */ - public Boolean isAttributeField(final String fieldId); + public boolean isAttributeField(final String fieldId); /** * Gets the attribute name of the given field (if the field points to @@ -129,7 +129,7 @@ public PathExpression getRelativePathOfNode(final String nodeId, * @return The attribute name of the given field, without the @ prefix, or an * empty string if the field does not point to an @attribute. */ - public String getAttributeOfField(final String fieldId); + public String getAttributeNameFromAttributeField(final String fieldId); /** * Gets the absolute path of the given field, without the attribute part. diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java index c5359a84..ccbe28bc 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -32,7 +32,7 @@ public class CallStack { * available within the * scope of the sub-expression. */ - class StackFrame extends Stack { + class StackFrame extends Stack { /** * Keeps a list of all identifiers declared in the current scope as well as @@ -60,8 +60,8 @@ void declareIdentifier(Identifier identifier) { * @param expectedType The type that the returned object is expected to have. * @return The object removed from the top of the stack. */ - synchronized T pop(Class expectedType) { - Class actualType = this.peek().getClass(); + synchronized T pop(Class expectedType) { + Class actualType = this.peek().getClass(); if (expectedType.isAssignableFrom(actualType)) { return expectedType.cast(this.pop()); } @@ -233,11 +233,11 @@ public void pushIdentifierReference(String identifierName) { * * @param item The object to push on the stack. */ - public void push(CallStackObject item) { + public void push(ParsedEntity item) { this.frames.peek().push(item); } - public synchronized T pop(Class expectedType) { + public synchronized T pop(Class expectedType) { return this.frames.peek().pop(expectedType); } @@ -247,7 +247,7 @@ public synchronized T pop(Class expectedType) { * * @return The object at the top of the current stack frame. */ - public synchronized CallStackObject peek() { + public synchronized ParsedEntity peek() { return this.frames.peek().peek(); } diff --git a/src/main/java/eu/europa/ted/efx/model/CallStackObject.java b/src/main/java/eu/europa/ted/efx/model/ParsedEntity.java similarity index 88% rename from src/main/java/eu/europa/ted/efx/model/CallStackObject.java rename to src/main/java/eu/europa/ted/efx/model/ParsedEntity.java index c4fd8ba1..0e3b1a79 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStackObject.java +++ b/src/main/java/eu/europa/ted/efx/model/ParsedEntity.java @@ -6,6 +6,6 @@ * As the EfxExpressionTranslator translates EFX to a target language, the objects in the call-stack * are typically code snippets in the target language. */ -public interface CallStackObject { +public interface ParsedEntity { } diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java b/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java index ed8e30d9..c0404122 100644 --- a/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java +++ b/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java @@ -4,9 +4,9 @@ import org.antlr.v4.runtime.misc.ParseCancellationException; -import eu.europa.ted.efx.model.CallStackObject; +import eu.europa.ted.efx.model.ParsedEntity; -public interface Expression extends CallStackObject { +public interface Expression extends ParsedEntity { public String getScript(); diff --git a/src/main/java/eu/europa/ted/efx/model/templates/Markup.java b/src/main/java/eu/europa/ted/efx/model/templates/Markup.java index 972212dc..23767ab6 100644 --- a/src/main/java/eu/europa/ted/efx/model/templates/Markup.java +++ b/src/main/java/eu/europa/ted/efx/model/templates/Markup.java @@ -1,11 +1,11 @@ package eu.europa.ted.efx.model.templates; -import eu.europa.ted.efx.model.CallStackObject; +import eu.europa.ted.efx.model.ParsedEntity; /** * Represents markup in the target template language. */ -public class Markup implements CallStackObject { +public class Markup implements ParsedEntity { /** * Stores the markup script in the target language. diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java b/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java index 71538660..22acf0ad 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java @@ -1,11 +1,11 @@ package eu.europa.ted.efx.model.variables; -import eu.europa.ted.efx.model.CallStackObject; +import eu.europa.ted.efx.model.ParsedEntity; import eu.europa.ted.efx.model.expressions.Expression; import eu.europa.ted.efx.model.expressions.TypedExpression; import eu.europa.ted.efx.model.types.EfxDataType; -public class Identifier implements CallStackObject { +public class Identifier implements ParsedEntity { final public String name; final public Class dataType; final public Expression declarationExpression; diff --git a/src/main/java/eu/europa/ted/efx/model/variables/VariableList.java b/src/main/java/eu/europa/ted/efx/model/variables/VariableList.java index b656a342..005a5088 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/VariableList.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/VariableList.java @@ -2,9 +2,9 @@ import java.util.LinkedList; -import eu.europa.ted.efx.model.CallStackObject; +import eu.europa.ted.efx.model.ParsedEntity; -public class VariableList extends LinkedList implements CallStackObject { +public class VariableList extends LinkedList implements ParsedEntity { public VariableList() { } 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 4a1e0369..21e5e147 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -1027,7 +1027,7 @@ public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference( this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), - this.symbols.getAttributeOfField(fieldId), + this.symbols.getAttributeNameFromAttributeField(fieldId), PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); @@ -1042,7 +1042,7 @@ public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx this.stack.push(this.script.composeFieldAttributeReference( this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), - this.symbols.getAttributeOfField(fieldId), + this.symbols.getAttributeNameFromAttributeField(fieldId), PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); 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 a303a7f5..460a4eb7 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -295,7 +295,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { final PathExpression valueReference = this.symbols.isAttributeField(fieldId) ? this.script.composeFieldAttributeReference( this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), currentContext.absolutePath()), - this.symbols.getAttributeOfField(fieldId), StringPathExpression.class) + this.symbols.getAttributeNameFromAttributeField(fieldId), StringPathExpression.class) : this.script.composeFieldValueReference( this.symbols.getRelativePathOfField(fieldId, currentContext.absolutePath())); Variable loopVariable = new Variable("item", diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index e6aa3c8c..e4a06a87 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1050,7 +1050,7 @@ public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference( this.symbols.getRelativePath( this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), - this.symbols.getAttributeOfField(fieldId), + this.symbols.getAttributeNameFromAttributeField(fieldId), PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); @@ -1065,7 +1065,7 @@ public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx this.stack.push(this.script.composeFieldAttributeReference( this.symbols.getRelativePath( this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), - this.symbols.getAttributeOfField(fieldId), + this.symbols.getAttributeNameFromAttributeField(fieldId), PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); 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 7f4b13f1..ed39da2d 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -339,7 +339,7 @@ private void shorthandIndirectLabelReference(final String fieldId, final Numeric ? this.script.composeFieldAttributeReference( this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), currentContext.absolutePath()), - this.symbols.getAttributeOfField(fieldId), StringPathExpression.class) + this.symbols.getAttributeNameFromAttributeField(fieldId), StringPathExpression.class) : this.script.composeFieldValueReference( this.symbols.getRelativePathOfField(fieldId, currentContext.absolutePath())); Variable loopVariable = new Variable("item", diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java index 7482ef89..34d24ef4 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java @@ -5,6 +5,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.apache.commons.lang3.StringUtils; import eu.europa.ted.efx.model.expressions.Expression; import eu.europa.ted.efx.model.expressions.path.NodePathExpression; @@ -31,12 +32,24 @@ public class XPathAttributeLocator extends XPath20BaseListener { private String path; private String attribute; - public NodePathExpression getPath() { + /** + * Gets the XPath to the XML element that contains the attribute. + * The returned XPath therefore does not contain the attribute itself. + * + * @return A {@link NodePathExpression} pointing to the XML element that contains the attribute. + */ + public NodePathExpression getElementPath() { return Expression.instantiate(path, NodePathExpression.class); } - public String getAttribute() { - return attribute; + /** + * Gets the name of the attribute (without the @ prefix). + * If the parsed XPath did not point to an attribute, then this method returns null. + * + * @return The name of the attribute (or null if the parsed XPath did not point to an attribute). + */ + public String getAttributeName() { + return StringUtils.isBlank(attribute) ? null : attribute; } public Boolean hasAttribute() { @@ -60,6 +73,7 @@ public void exitAbbrevforwardstep(AbbrevforwardstepContext ctx) { this.attribute = ctx.nodetest().getText(); } } + public static XPathAttributeLocator findAttribute(final PathExpression xpath) { return findAttribute(xpath.getScript()); } diff --git a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java index d3be5133..89316907 100644 --- a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java +++ b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java @@ -13,8 +13,8 @@ private void testAttribute(final String attributePath, final String expectedPath final XPathAttributeLocator locator = XPathAttributeLocator.findAttribute(attributePath); - assertEquals(expectedPath, locator.getPath().getScript()); - assertEquals(expectedAttribute, locator.getAttribute()); + assertEquals(expectedPath, locator.getElementPath().getScript()); + assertEquals(expectedAttribute, locator.getAttributeName()); } @Test @@ -32,8 +32,8 @@ void testXPathAttributeLocator_WithMultipleAttributes() { void testXPathAttributeLocator_WithoutAttribute() { final XPathAttributeLocator locator = XPathAttributeLocator .findAttribute("/path/path[@otherAttribute = 'text']"); - assertEquals("/path/path[@otherAttribute = 'text']", locator.getPath().getScript()); - assertNull(locator.getAttribute()); + assertEquals("/path/path[@otherAttribute = 'text']", locator.getElementPath().getScript()); + assertNull(locator.getAttributeName()); } @Test diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java index 58205ade..8d67e44c 100644 --- a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java +++ b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java @@ -56,17 +56,17 @@ protected String getFieldsJsonFilename() { } @Override - public Boolean isAttributeField(final String fieldId) { + public boolean isAttributeField(final String fieldId) { return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).hasAttribute(); } @Override - public String getAttributeOfField(String fieldId) { - return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttribute(); + public String getAttributeNameFromAttributeField(String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttributeName(); } @Override public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) { - return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getPath(); + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getElementPath(); } } diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java index 41b18f95..e2353b13 100644 --- a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java +++ b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java @@ -82,17 +82,17 @@ protected String getFieldsJsonFilename() { } @Override - public Boolean isAttributeField(final String fieldId) { + public boolean isAttributeField(final String fieldId) { return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).hasAttribute(); } @Override - public String getAttributeOfField(String fieldId) { - return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttribute(); + public String getAttributeNameFromAttributeField(String fieldId) { + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttributeName(); } @Override public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) { - return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getPath(); + return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getElementPath(); } } From d11951e521bddbb232bd28e51013e76835d210bc Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Wed, 26 Jul 2023 16:15:08 +0200 Subject: [PATCH 53/57] Removed deprecated methods --- .../ted/eforms/sdk/SdkSymbolResolver.java | 5 ++++- .../ted/efx/interfaces/MarkupGenerator.java | 20 ----------------- .../ted/efx/interfaces/ScriptGenerator.java | 12 ---------- .../ted/efx/xpath/XPathScriptGenerator.java | 22 ------------------- .../ted/efx/mock/MarkupGeneratorMock.java | 11 ---------- 5 files changed, 4 insertions(+), 66 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java index 294e7b82..0f797ab7 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -229,5 +229,8 @@ private void cacheAdditionalFieldInfo(final String fieldId) { info.attributeName = parsedPath.getAttributeName(); info.pathWithoutAttribute = parsedPath.getElementPath(); additionalFieldInfoMap.put(fieldId, info); - } + } + + // #endregion Temporary helpers ------------------------------------------------ + } 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 af5dafd8..ce6f8153 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -106,17 +106,6 @@ public interface MarkupGenerator { Markup renderLineBreak(); - /** - * @deprecated Use {@link #composeFragmentDefinition(String, String, Markup, Set)} instead. - * - * @param name the name of the fragment. - * @param number the outline number of the fragment. - * @param content the content of the fragment. - * @return the code that encapsulates the fragment in the target template. - */ - @Deprecated(since = "2.0.0", forRemoval = true) - Markup composeFragmentDefinition(final String name, String number, Markup content); - /** * Given a fragment name (identifier) and some pre-rendered content, this method returns the code * that encapsulates it in the target template @@ -130,15 +119,6 @@ public interface MarkupGenerator { Markup composeFragmentDefinition(final String name, String number, Markup content, Set parameters); - /** - * @deprecated Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead. - * - * @param name the name of the fragment. - * @param context the context of the fragment. - * @return the code that invokes (uses) the fragment. - */ - @Deprecated(since = "2.0.0", forRemoval = true) - Markup renderFragmentInvocation(final String name, final PathExpression context); /** * Given a fragment name (identifier), and an evaluation context, this method returns the code diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index b74fc3be..bc7b1bc1 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -198,27 +198,15 @@ public BooleanExpression composePatternMatchCondition(final StringExpression exp public T composeParenthesizedExpression(T expression, Class type); - @Deprecated(since = "1.0.0", forRemoval = true) - public BooleanExpression composeAllSatisfy(SequenceExpression list, - String variableName, BooleanExpression booleanExpression); - public BooleanExpression composeAllSatisfy( IteratorListExpression iterators, BooleanExpression booleanExpression); - @Deprecated(since = "1.0.0", forRemoval = true) - public BooleanExpression composeAnySatisfies(SequenceExpression list, - String variableName, BooleanExpression booleanExpression); - public BooleanExpression composeAnySatisfies( IteratorListExpression iterators, BooleanExpression booleanExpression); public T composeConditionalExpression(BooleanExpression condition, T whenTrue, T whenFalse, Class type); - @Deprecated(since = "1.0.0", forRemoval = true) - public T2 composeForExpression( - String variableName, T1 sourceList, ScalarExpression expression, Class targetListType); - public T composeForExpression( IteratorListExpression iterators, ScalarExpression expression, Class targetListType); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index ee6eda78..f04c4260 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -197,13 +197,6 @@ public BooleanExpression composePatternMatchCondition(StringExpression expressio String.format("fn:matches(normalize-space(%s), %s)", expression.getScript(), pattern)); } - @Override - public BooleanExpression composeAllSatisfy(SequenceExpression list, - String variableName, BooleanExpression booleanExpression) { - return new BooleanExpression( - "every " + variableName + " in " + list.getScript() + " satisfies " + booleanExpression.getScript()); - } - @Override public BooleanExpression composeAllSatisfy( IteratorListExpression iterators, BooleanExpression booleanExpression) { @@ -211,13 +204,6 @@ public BooleanExpression composeAllSatisfy( "every " + iterators.getScript() + " satisfies " + booleanExpression.getScript()); } - @Override - public BooleanExpression composeAnySatisfies(SequenceExpression list, - String variableName, BooleanExpression booleanExpression) { - return new BooleanExpression( - "some " + variableName + " in " + list.getScript() + " satisfies " + booleanExpression.getScript()); - } - @Override public BooleanExpression composeAnySatisfies( IteratorListExpression iterators, BooleanExpression booleanExpression) { @@ -233,14 +219,6 @@ public T composeConditionalExpression(BooleanExpress type); } - @Override - public T2 composeForExpression( - String variableName, T1 sourceList, ScalarExpression expression, Class targetListType) { - return Expression.instantiate( - "for " + variableName + " in " + sourceList.getScript() + " return " + expression.getScript(), - targetListType); - } - @Override public T composeForExpression( IteratorListExpression iterators, ScalarExpression expression, Class targetListType) { 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 4a096305..826d6bf3 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -1,6 +1,5 @@ package eu.europa.ted.efx.mock; -import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -58,11 +57,6 @@ public Markup renderLineBreak() { return new Markup(""); } - @Override - public Markup composeFragmentDefinition(String name, String number, Markup content) { - return this.composeFragmentDefinition(name, number, content, new LinkedHashSet<>()); - } - @Override public Markup composeFragmentDefinition(String name, String number, Markup content, Set parameters) { @@ -74,11 +68,6 @@ public Markup composeFragmentDefinition(String name, String number, Markup conte parameters.stream().collect(Collectors.joining(", ")), number, content.script)); } - @Override - public Markup renderFragmentInvocation(String name, PathExpression context) { - return this.renderFragmentInvocation(name, context, new LinkedHashSet<>()); - } - @Override public Markup renderFragmentInvocation(String name, PathExpression context, Set> variables) { From 74317de8ddf876cab594bd65d42558dd65ebaa49 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Wed, 26 Jul 2023 16:18:03 +0200 Subject: [PATCH 54/57] Set version number to 2.0.0-alpha.1 in pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 420763b7..c682a6c9 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ eu.europa.ted.eforms efx-toolkit-java - 2.0.0-SNAPSHOT + 2.0.0-alpha.1 jar EFX Toolkit for Java From 61ace285572f03e96934a890801bdf30b7f8f652 Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Wed, 26 Jul 2023 22:01:24 +0200 Subject: [PATCH 55/57] Updated pom.xml SDK dependency to 2.0.0-alpha.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c682a6c9..b5c9a1d3 100644 --- a/pom.xml +++ b/pom.xml @@ -256,7 +256,7 @@ eu.europa.ted.eforms eforms-sdk - 2.0.0-SNAPSHOT + 2.0.0-alpha.1 jar eforms-sdk/efx-grammar/**/*.g4 ${sdk.antlr4.dir}/eu/europa/ted/efx/sdk2 From 19cb78b9bb844a1becdd36129c74b7fb11b2f2ce Mon Sep 17 00:00:00 2001 From: Ioannis Rousochatzakis Date: Fri, 28 Jul 2023 17:34:32 +0200 Subject: [PATCH 56/57] Updated the changelog. --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bee01814..5c2650ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,38 @@ -# EFX Toolkit 1.3.0 Release Notes +# EFX Toolkit 2.0.0-alpha.1 Release Notes _The EFX Toolkit for Java developers is a library that enables the transpilation of [EFX](https://docs.ted.europa.eu/eforms/latest/efx) expressions and templates to different target languages. It also includes an implementation of an EFX-to-XPath transpiler._ --- ## In this release: -- Updated the XPath 2.0 parser, XPathContextualizer and XPathScriptGenerator to correctly translate sequences. -- Improved numeric formatting. The EfxTranslator API now includes overloaded methods that permit control of numeric formatting. The existing API has been preserved. -- Improved handling of multilingual text fields by adding automatic selection of the visualisation language. +This release: +- Improves translation of EFX-1. +- Adds support for translating EFX-2 expressions and templates. +- Removes support of the obsolete EFX versions included in pre-release versions of the SDK (SDK 0.x.x). +- Introduces some breaking changes in the interfaces that need to be implemented by new translators (SymbolResolver, ScriptGenerator, MarkupGenerator). + +## EFX-1 Support + +Although this is a pre-release version of the EFX Toolkit, it provides production-level support for EFX-1 transpilation. +EFX-1 is the current version of EFX released with SDK 1. Transpilation of EFX-1 to XPath is on par with the EFX Toolkit 1.3.0. + +## EFX-2 Support + +The new version of EFX is still under development and will be released with SDK 2.0.0. For more information of EFX-2 see the release notes of the eForms SDK 2.0.0-alpha.1. + +## Breaking changes + +For users of the Toolkit that have implemented custom transpilers, this release contains a few breaking changes. +More specifically: +- Some additional methods have been added to the SymbolResolver, ScriptGenerator and MarkupGenerator API. As a guide for your implementations please look a the implementations included in the EFX Toolkit for use by the EFX-to-XPath transpilation. +- Some deprecated methods were removed. +- An extensive refactoring in the type management system has rearranged the package structure. As a result some import statements i your code will need to be updated. + +Users of the Toolkit that only use the included EFX-to-XPath transpiler will not be affected by the above changes. + +## Future development + +Further alpha and beta releases of SDK 2 and EFX Toolkit 2 will be issued. While in "alpha" development stage, further braking changes may be introduced. SDK 2 and EFX 2 are expected to continue to be under development util late 2023. --- @@ -20,5 +45,6 @@ Documentation for the EFX Toolkit is available at: https://docs.ted.europa.eu/ef This version of the EFX Toolkit has a compile-time dependency on the following versions of eForms SDK versions and uses the EFX grammar that each version provides: - eForms SDK 1.x.x +- eForms SDK 2.0.0-alpha.1 It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.0.5. From ebac474910d7810dff5b99b6b161f84fea969f82 Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Fri, 28 Jul 2023 17:40:29 +0200 Subject: [PATCH 57/57] CHANGELOG: Fix a typo and some Markdown warnings --- CHANGELOG.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c2650ec..32890d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,18 +3,20 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation of [EFX](https://docs.ted.europa.eu/eforms/latest/efx) expressions and templates to different target languages. It also includes an implementation of an EFX-to-XPath transpiler._ --- -## In this release: + +## In this release This release: + - Improves translation of EFX-1. -- Adds support for translating EFX-2 expressions and templates. +- Adds support for translating EFX-2 expressions and templates. - Removes support of the obsolete EFX versions included in pre-release versions of the SDK (SDK 0.x.x). - Introduces some breaking changes in the interfaces that need to be implemented by new translators (SymbolResolver, ScriptGenerator, MarkupGenerator). ## EFX-1 Support -Although this is a pre-release version of the EFX Toolkit, it provides production-level support for EFX-1 transpilation. -EFX-1 is the current version of EFX released with SDK 1. Transpilation of EFX-1 to XPath is on par with the EFX Toolkit 1.3.0. +Although this is a pre-release version of the EFX Toolkit, it provides production-level support for EFX-1 transpilation. +EFX-1 is the current version of EFX released with SDK 1. Transpilation of EFX-1 to XPath is on par with the EFX Toolkit 1.3.0. ## EFX-2 Support @@ -22,11 +24,12 @@ The new version of EFX is still under development and will be released with SDK ## Breaking changes -For users of the Toolkit that have implemented custom transpilers, this release contains a few breaking changes. +For users of the Toolkit that have implemented custom transpilers, this release contains a few breaking changes. More specifically: + - Some additional methods have been added to the SymbolResolver, ScriptGenerator and MarkupGenerator API. As a guide for your implementations please look a the implementations included in the EFX Toolkit for use by the EFX-to-XPath transpilation. - Some deprecated methods were removed. -- An extensive refactoring in the type management system has rearranged the package structure. As a result some import statements i your code will need to be updated. +- An extensive refactoring in the type management system has rearranged the package structure. As a result some import statements in your code will need to be updated. Users of the Toolkit that only use the included EFX-to-XPath transpiler will not be affected by the above changes. @@ -39,11 +42,12 @@ Further alpha and beta releases of SDK 2 and EFX Toolkit 2 will be issued. While You can download the latest EFX Toolkit from Maven Central. [![Maven Central](https://img.shields.io/maven-central/v/eu.europa.ted.eforms/efx-toolkit-java?label=Download%20&style=flat-square)](https://central.sonatype.com/artifact/eu.europa.ted.eforms/efx-toolkit-java) -Documentation for the EFX Toolkit is available at: https://docs.ted.europa.eu/eforms/latest/efx-toolkit +Documentation for the EFX Toolkit is available at: --- This version of the EFX Toolkit has a compile-time dependency on the following versions of eForms SDK versions and uses the EFX grammar that each version provides: + - eForms SDK 1.x.x - eForms SDK 2.0.0-alpha.1