From 850a38bb8e61775c5ec1ddeee3bbc70633fab94e Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Tue, 13 Jan 2026 01:49:41 +0100 Subject: [PATCH 1/6] Fixed a casting issue in Parametrised --- .../europa/ted/efx/model/variables/Parametrised.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Parametrised.java b/src/main/java/eu/europa/ted/efx/model/variables/Parametrised.java index 60ad206..b74e442 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/Parametrised.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/Parametrised.java @@ -2,6 +2,12 @@ import eu.europa.ted.efx.model.types.EfxDataType; +/** + * An identifier that accepts parameters. + * + * Base class for parameterized identifiers like functions and templates. Extends {@link Identifier} + * to add parsed parameter information. + */ public class Parametrised extends Identifier { public final ParsedParameters parameters; @@ -19,8 +25,8 @@ public boolean equals(Object o) { return false; if (!super.equals(o)) return false; - Parametrised function = (Function) o; - return parameters != null ? parameters.equals(function.parameters) : function.parameters == null; + Parametrised other = (Parametrised) o; + return parameters != null ? parameters.equals(other.parameters) : other.parameters == null; } @Override From beecf75fa88c8272425bfcf2c6733c4df1649807 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Mon, 2 Feb 2026 01:11:07 +0100 Subject: [PATCH 2/6] TEDEFO-4807 Adapt toolkit to scalar/sequence grammar separation (TEDEFO-4808) Refactored expression model with separate scalar/sequence hierarchies. Introduced EfxTypeLattice for type variant management and TypeChecker interface for SDK-specific type validation. Updated translators to handle new expression model. Improved error reporting with ConsistencyCheckException for internal consistency checks. Enhanced symbol resolver for attribute fields. Added test coverage for sequence literals and context variables. --- .../ted/eforms/sdk/SdkSymbolResolver.java | 472 +++++++-- .../exceptions/ConsistencyCheckException.java | 103 ++ .../exceptions/InvalidArgumentException.java | 40 +- .../InvalidIdentifierException.java | 38 +- .../InvalidIndentationException.java | 65 +- .../efx/exceptions/InvalidUsageException.java | 44 +- .../exceptions/SymbolResolutionException.java | 57 +- .../efx/exceptions/TypeMismatchException.java | 51 +- .../ted/efx/interfaces/MarkupGenerator.java | 132 ++- .../ted/efx/interfaces/ScriptGenerator.java | 24 +- .../ted/efx/interfaces/SymbolResolver.java | 108 +- .../ted/efx/interfaces/TypeChecker.java | 42 + .../eu/europa/ted/efx/model/CallStack.java | 39 +- .../java/eu/europa/ted/efx/model/Context.java | 15 +- .../eu/europa/ted/efx/model/ContextStack.java | 34 +- .../ted/efx/model/expressions/Expression.java | 69 +- .../model/expressions/LiteralExpression.java | 46 + .../efx/model/expressions/PathExpression.java | 63 ++ .../model/expressions/TypedExpression.java | 114 +-- .../path/BooleanPathExpression.java | 12 - .../expressions/path/DatePathExpression.java | 12 - .../path/DurationPathExpression.java | 12 - .../MultilingualStringPathExpression.java | 12 - .../expressions/path/NodePathExpression.java | 16 - .../path/NumericPathExpression.java | 12 - .../expressions/path/PathExpression.java | 128 --- .../path/StringPathExpression.java | 16 - .../expressions/path/TimePathExpression.java | 12 - .../expressions/scalar/BooleanExpression.java | 27 +- .../expressions/scalar/BooleanLiteral.java | 28 + .../model/expressions/scalar/BooleanPath.java | 36 + .../expressions/scalar/DateExpression.java | 27 +- .../model/expressions/scalar/DateLiteral.java | 28 + .../model/expressions/scalar/DatePath.java | 36 + .../scalar/DurationExpression.java | 27 +- .../expressions/scalar/DurationLiteral.java | 28 + .../expressions/scalar/DurationPath.java | 36 + .../scalar/MultilingualStringExpression.java | 26 +- .../scalar/MultilingualStringPath.java | 30 + .../model/expressions/scalar/NodePath.java | 38 + .../expressions/scalar/NumericExpression.java | 27 +- .../expressions/scalar/NumericLiteral.java | 28 + .../model/expressions/scalar/NumericPath.java | 36 + .../expressions/scalar/ScalarExpression.java | 43 +- .../expressions/scalar/ScalarLiteral.java | 62 ++ .../model/expressions/scalar/ScalarPath.java | 140 +++ .../expressions/scalar/StringExpression.java | 31 +- .../expressions/scalar/StringLiteral.java | 28 + .../model/expressions/scalar/StringPath.java | 36 + .../expressions/scalar/TimeExpression.java | 27 +- .../model/expressions/scalar/TimeLiteral.java | 28 + .../model/expressions/scalar/TimePath.java | 36 + .../sequence/BooleanSequenceExpression.java | 31 +- .../sequence/BooleanSequenceLiteral.java | 28 + .../sequence/BooleanSequencePath.java | 36 + .../sequence/DateSequenceExpression.java | 31 +- .../sequence/DateSequenceLiteral.java | 28 + .../sequence/DateSequencePath.java | 36 + .../sequence/DurationSequenceExpression.java | 31 +- .../sequence/DurationSequenceLiteral.java | 28 + .../sequence/DurationSequencePath.java | 36 + .../MultilingualStringSequenceExpression.java | 22 +- .../MultilingualStringSequencePath.java | 30 + .../sequence/NodeSequencePath.java | 38 + .../sequence/NumericSequenceExpression.java | 31 +- .../sequence/NumericSequenceLiteral.java | 28 + .../sequence/NumericSequencePath.java | 36 + .../sequence/SequenceExpression.java | 42 +- .../expressions/sequence/SequenceLiteral.java | 63 ++ .../expressions/sequence/SequencePath.java | 130 +++ .../sequence/StringSequenceExpression.java | 31 +- .../sequence/StringSequenceLiteral.java | 28 + .../sequence/StringSequencePath.java | 36 + .../sequence/TimeSequenceExpression.java | 31 +- .../sequence/TimeSequenceLiteral.java | 28 + .../sequence/TimeSequencePath.java | 36 + .../efx/model/rules/NoticeSubtypeRange.java | 22 +- .../ted/efx/model/types/EfxDataType.java | 86 +- .../model/types/EfxDataTypeAssociation.java | 6 + .../efx/model/types/EfxExpressionType.java | 7 - .../types/EfxExpressionTypeAssociation.java | 14 - .../ted/efx/model/types/EfxTypeLattice.java | 182 ++++ .../ted/efx/model/types/FieldTypes.java | 7 + .../ted/efx/model/variables/Dictionary.java | 21 +- .../ted/efx/model/variables/Function.java | 25 +- .../efx/model/variables/ParsedParameters.java | 6 + .../efx/model/variables/StrictArguments.java | 16 +- .../ted/efx/model/variables/Template.java | 2 +- .../ted/efx/model/variables/Variable.java | 31 +- .../efx/sdk1/EfxExpressionTranslatorV1.java | 66 +- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 33 +- .../eu/europa/ted/efx/sdk1/TypeCheckerV1.java | 94 ++ .../ted/efx/sdk1/entity/SdkCodelistV1.java | 21 - .../ted/efx/sdk1/entity/SdkFieldV1.java | 47 - .../europa/ted/efx/sdk1/entity/SdkNodeV1.java | 22 - .../efx/sdk1/entity/SdkNoticeSubtypeV1.java | 21 - .../sdk1/xpath/XPathScriptGeneratorV1.java | 18 +- .../efx/sdk2/EfxExpressionTranslatorV2.java | 922 ++++++++++++++---- .../ted/efx/sdk2/EfxRulesTranslatorV2.java | 171 +++- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 405 +++++++- .../eu/europa/ted/efx/sdk2/TypeCheckerV2.java | 72 ++ .../ted/efx/sdk2/entity/SdkCodelistV2.java | 21 - .../ted/efx/sdk2/entity/SdkFieldV2.java | 41 - .../europa/ted/efx/sdk2/entity/SdkNodeV2.java | 29 - .../efx/sdk2/entity/SdkNoticeSubtypeV2.java | 21 - .../ted/efx/xpath/XPathContextualizer.java | 22 +- .../ted/efx/xpath/XPathScriptGenerator.java | 96 +- .../efx/mock/AbstractSymbolResolverMock.java | 174 ---- .../ted/efx/mock/MarkupGeneratorMock.java | 52 +- .../efx/mock/sdk1/SymbolResolverMockV1.java | 115 +-- .../efx/mock/sdk2/SymbolResolverMockV2.java | 147 +-- .../efx/model/types/EfxTypeLatticeTest.java | 101 ++ .../sdk2/EfxExpressionTranslatorV2Test.java | 378 ++++++- .../efx/sdk2/EfxRulesTranslatorV2Test.java | 85 ++ .../efx/sdk2/EfxTemplateTranslatorV2Test.java | 461 ++++++++- .../ted/efx/sdk2/SdkSymbolResolverTest.java | 650 ++++++++++++ .../dynamic/complete-validation.sch | 30 + .../dynamic/validation-stage-1a-1.sch | 7 + .../dynamic/validation-stage-1a-2.sch | 7 + .../input.efx | 6 + .../schematrons.json | 31 + .../static/complete-validation.sch | 30 + .../static/validation-stage-1a-1.sch | 7 + .../static/validation-stage-1a-2.sch | 7 + .../dynamic/complete-validation.sch | 31 + .../dynamic/validation-stage-1a-1.sch | 6 + .../dynamic/validation-stage-1a-2.sch | 6 + .../input.efx | 8 + .../schematrons.json | 31 + .../static/complete-validation.sch | 31 + .../static/validation-stage-1a-1.sch | 6 + .../static/validation-stage-1a-2.sch | 6 + .../dynamic/complete-validation.sch | 31 + .../dynamic/validation-stage-1a-1.sch | 6 + .../dynamic/validation-stage-1a-2.sch | 6 + .../input.efx | 8 + .../schematrons.json | 31 + .../static/complete-validation.sch | 31 + .../static/validation-stage-1a-1.sch | 6 + .../static/validation-stage-1a-2.sch | 6 + .../dynamic/complete-validation.sch | 31 + .../dynamic/validation-stage-1a-1.sch | 6 + .../dynamic/validation-stage-1a-2.sch | 6 + .../input.efx | 8 + .../schematrons.json | 31 + .../static/complete-validation.sch | 31 + .../static/validation-stage-1a-1.sch | 6 + .../static/validation-stage-1a-2.sch | 6 + .../dynamic/complete-validation.sch | 31 + .../dynamic/validation-stage-1a-1.sch | 6 + .../dynamic/validation-stage-1a-2.sch | 6 + .../input.efx | 8 + .../schematrons.json | 31 + .../static/complete-validation.sch | 31 + .../static/validation-stage-1a-1.sch | 6 + .../static/validation-stage-1a-2.sch | 6 + .../dynamic/complete-validation.sch | 31 + .../dynamic/validation-stage-1a-1.sch | 6 + .../dynamic/validation-stage-1a-2.sch | 6 + .../input.efx | 8 + .../schematrons.json | 31 + .../static/complete-validation.sch | 31 + .../static/validation-stage-1a-1.sch | 6 + .../static/validation-stage-1a-2.sch | 6 + .../dynamic/complete-validation.sch | 31 + .../dynamic/validation-stage-1a-1.sch | 6 + .../dynamic/validation-stage-1a-2.sch | 6 + .../input.efx | 8 + .../schematrons.json | 31 + .../static/complete-validation.sch | 31 + .../static/validation-stage-1a-1.sch | 6 + .../static/validation-stage-1a-2.sch | 6 + .../dynamic/complete-validation.sch | 34 + .../dynamic/validation-stage-1a-1.sch | 7 + .../dynamic/validation-stage-1a-2.sch | 7 + .../input.efx | 10 + .../schematrons.json | 31 + .../static/complete-validation.sch | 34 + .../static/validation-stage-1a-1.sch | 7 + .../static/validation-stage-1a-2.sch | 7 + src/test/resources/json/README.md | 67 ++ src/test/resources/json/fields-sdk1.json | 184 ---- src/test/resources/json/fields-sdk2.json | 230 ----- src/test/resources/json/sdk1-fields.json | 202 ++++ src/test/resources/json/sdk2-fields.json | 854 ++++++++++++++++ 185 files changed, 8821 insertions(+), 2010 deletions(-) create mode 100644 src/main/java/eu/europa/ted/efx/exceptions/ConsistencyCheckException.java create mode 100644 src/main/java/eu/europa/ted/efx/interfaces/TypeChecker.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/LiteralExpression.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/PathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/BooleanPathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/DatePathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/DurationPathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/MultilingualStringPathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/NumericPathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/PathExpression.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/path/StringPathExpression.java delete 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/BooleanLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanPath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/DatePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationPath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringPath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/NodePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericPath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarPath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringPath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/NodeSequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequencePath.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceLiteral.java create mode 100644 src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequencePath.java delete mode 100644 src/main/java/eu/europa/ted/efx/model/types/EfxExpressionType.java delete 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/EfxTypeLattice.java create mode 100644 src/main/java/eu/europa/ted/efx/sdk1/TypeCheckerV1.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk1/entity/SdkCodelistV1.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNodeV1.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNoticeSubtypeV1.java create mode 100644 src/main/java/eu/europa/ted/efx/sdk2/TypeCheckerV2.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java delete mode 100644 src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNoticeSubtypeV2.java delete mode 100644 src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java create mode 100644 src/test/java/eu/europa/ted/efx/model/types/EfxTypeLatticeTest.java create mode 100644 src/test/java/eu/europa/ted/efx/sdk2/SdkSymbolResolverTest.java create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/schematrons.json create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/complete-validation.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch create mode 100644 src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch create mode 100644 src/test/resources/json/README.md delete mode 100644 src/test/resources/json/fields-sdk1.json delete mode 100644 src/test/resources/json/fields-sdk2.json create mode 100644 src/test/resources/json/sdk1-fields.json create mode 100644 src/test/resources/json/sdk2-fields.json 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 2c403db..f986a4b 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,25 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.eforms.sdk; import java.nio.file.Path; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -20,24 +36,25 @@ import eu.europa.ted.eforms.sdk.repository.SdkNodeRepository; import eu.europa.ted.eforms.sdk.repository.SdkNoticeTypeRepository; import eu.europa.ted.eforms.sdk.resource.SdkResourceLoader; -import eu.europa.ted.eforms.xpath.XPathInfo; import eu.europa.ted.eforms.xpath.XPathProcessor; import eu.europa.ted.efx.interfaces.SymbolResolver; -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.model.expressions.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.NodePath; +import eu.europa.ted.efx.model.expressions.scalar.ScalarPath; +import eu.europa.ted.efx.model.expressions.sequence.NodeSequencePath; +import eu.europa.ted.efx.model.expressions.sequence.SequencePath; import eu.europa.ted.efx.model.types.FieldTypes; -import eu.europa.ted.efx.sdk2.entity.SdkFieldV2; -import eu.europa.ted.efx.sdk2.entity.SdkNodeV2; +import eu.europa.ted.eforms.sdk.entity.v2.SdkFieldV2; +import eu.europa.ted.eforms.sdk.entity.v2.SdkNodeV2; import eu.europa.ted.efx.xpath.XPathContextualizer; @SdkComponent(versions = { "1", "2" }, componentType = SdkComponentType.SYMBOL_RESOLVER) public class SdkSymbolResolver implements SymbolResolver { - protected Map fieldById; + protected SdkFieldRepository fieldById; protected Map fieldByAlias; - protected Map nodeById; + protected SdkNodeRepository nodeById; protected Map nodeByAlias; @@ -45,15 +62,8 @@ public class SdkSymbolResolver implements SymbolResolver { protected Map noticeTypesById; - /** - * 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. - */ + private SdkNode cachedRootNode; + @Override public final List expandCodelist(final String codelistId) { final SdkCodelist codelist = codelistById.get(codelistId); @@ -64,7 +74,14 @@ public final List expandCodelist(final String codelistId) { } /** - * Private, use getInstance method instead. + * Protected constructor for subclasses that load data differently (e.g., test mocks). + * Subclasses must populate the field/node/codelist maps themselves. + */ + protected SdkSymbolResolver() { + } + + /** + * Creates a symbol resolver by loading SDK metadata from the given path. * * @param sdkVersion The version of the SDK. * @param sdkRootPath The path to the root of the SDK. @@ -84,53 +101,70 @@ protected void loadMapData(final String sdkVersion, final Path sdkRootPath) Path noticeTypesPath = SdkResourceLoader.getResourceAsPath(sdkVersion, SdkConstants.SdkResource.NOTICE_TYPES_JSON, sdkRootPath); - this.fieldById = new SdkFieldRepository(sdkVersion, jsonPath); - this.fieldByAlias = indexFieldsByAlias(); + // Load nodes first (fields depend on nodes for parent wiring) this.nodeById = new SdkNodeRepository(sdkVersion, jsonPath); this.nodeByAlias = indexNodesByAlias(); + + // Load fields with parent node wiring + this.fieldById = new SdkFieldRepository(sdkVersion, jsonPath, this.nodeById); + this.fieldByAlias = indexFieldsByAlias(); + this.codelistById = new SdkCodelistRepository(sdkVersion, codelistsPath); this.noticeTypesById = new SdkNoticeTypeRepository(sdkVersion, noticeTypesPath); } - /** - * 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); + final SdkField sdkField = this.resolveField(fieldId); if (sdkField != null) { return sdkField.getParentNodeId(); } - throw SymbolResolutionException.unknownField(fieldId); + throw SymbolResolutionException.unknownSymbol(fieldId); } /** * @param fieldId The id of a field. - * @return The xPath of the given field. + * @return The xPath of the given field as a {@link SequencePath} if the field + * is repeatable from root context, {@link ScalarPath} otherwise. */ @Override public PathExpression getAbsolutePathOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); + final SdkField sdkField = this.resolveField(fieldId); if (sdkField == null) { - throw SymbolResolutionException.unknownField(fieldId); + throw SymbolResolutionException.unknownSymbol(fieldId); + } + return this.getAbsolutePathOfField(sdkField); + } + + private PathExpression getAbsolutePathOfField(final SdkField sdkField) { + FieldTypes fieldType = FieldTypes.fromString(sdkField.getType()); + if (this.isFieldRepeatableFromContext(sdkField, this.getRootNode())) { + return SequencePath.instantiate(sdkField.getXpathAbsolute(), fieldType); + } else { + return ScalarPath.instantiate(sdkField.getXpathAbsolute(), fieldType); } - return PathExpression.instantiate(sdkField.getXpathAbsolute(), FieldTypes.fromString(sdkField.getType())); } /** - * @param nodeId The id of a node or a field. - * @return The xPath of the given node or field. + * @param nodeId The id of a node. + * @return The xPath of the given node as a {@link NodeSequencePath} if + * the node is repeatable from root context, {@link NodePath} otherwise. */ @Override public PathExpression getAbsolutePathOfNode(final String nodeId) { - final SdkNode sdkNode = nodeById.get(nodeId); + final SdkNode sdkNode = this.resolveNode(nodeId); if (sdkNode == null) { - throw SymbolResolutionException.unknownNode(nodeId); + throw SymbolResolutionException.unknownSymbol(nodeId); + } + return this.getAbsolutePathOfNode(sdkNode); + } + + private PathExpression getAbsolutePathOfNode(final SdkNode sdkNode) { + if (this.isNodeRepeatableFromContext(sdkNode, null)) { + return new NodeSequencePath(sdkNode.getXpathAbsolute()); + } else { + return new NodePath(sdkNode.getXpathAbsolute()); } - return new NodePathExpression(sdkNode.getXpathAbsolute()); } /** @@ -140,11 +174,57 @@ public PathExpression getAbsolutePathOfNode(final String nodeId) { * xPath. * @param contextPath xPath indicating the context. * @return The xPath of the given field relative to the given context. + * @deprecated Use {@link #getRelativePathOfField(String, String)} instead. */ + @Deprecated(forRemoval = true) @Override public PathExpression getRelativePathOfField(String fieldId, PathExpression contextPath) { - final PathExpression xpath = getAbsolutePathOfField(fieldId); - return XPathContextualizer.contextualize(contextPath, xpath); + return XPathContextualizer.contextualize(contextPath, this.getAbsolutePathOfField(fieldId)); + } + + @Override + public PathExpression getRelativePathOfField(String fieldId, String contextId) { + SdkField sdkField = this.resolveField(fieldId); + if (sdkField == null) { + throw SymbolResolutionException.unknownSymbol(fieldId); + } + + // null contextId means root context - return absolute path with correct type + if (contextId == null) { + return this.getAbsolutePathOfField(sdkField); + } + + var context = this.resolveSymbol(contextId); + if (context.isEmpty()) { + throw SymbolResolutionException.unknownSymbol(contextId); + } + + if (context.isField()) { + return this.getRelativePathOfField(sdkField, context.field); + } + + return this.getRelativePathOfField(sdkField, context.node); + } + + private PathExpression getRelativePathOfField(SdkField sdkField, SdkNode context) { + + String relativeScript = XPathProcessor.contextualize(context.getXpathAbsolute(), sdkField.getXpathAbsolute()); + + if (isFieldRepeatableFromContext(sdkField, context)) { + return SequencePath.instantiate(relativeScript, FieldTypes.fromString(sdkField.getType())); + } else { + return ScalarPath.instantiate(relativeScript, FieldTypes.fromString(sdkField.getType())); + } + } + + private PathExpression getRelativePathOfField(SdkField sdkField, SdkField context) { + String relativeScript = XPathProcessor.contextualize(context.getXpathAbsolute(), sdkField.getXpathAbsolute()); + + if (this.isFieldRepeatableFromContext(sdkField, context)) { + return SequencePath.instantiate(relativeScript, FieldTypes.fromString(sdkField.getType())); + } else { + return ScalarPath.instantiate(relativeScript, FieldTypes.fromString(sdkField.getType())); + } } /** @@ -154,13 +234,58 @@ public PathExpression getRelativePathOfField(String fieldId, PathExpression cont * xPath. * @param contextPath XPath indicating the context. * @return The XPath of the given node relative to the given context. + * @deprecated Use {@link #getRelativePathOfNode(String, String)} instead. */ + @Deprecated(forRemoval = true) @Override public PathExpression getRelativePathOfNode(String nodeId, PathExpression contextPath) { - final PathExpression xpath = getAbsolutePathOfNode(nodeId); - return XPathContextualizer.contextualize(contextPath, xpath); + return XPathContextualizer.contextualize(contextPath, this.getAbsolutePathOfNode(nodeId)); + } + + @Override + public PathExpression getRelativePathOfNode(String nodeId, String contextId) { + SdkNode sdkNode = this.resolveNode(nodeId); + if (sdkNode == null) { + throw SymbolResolutionException.unknownSymbol(nodeId); + } + + // null contextId means root context - return absolute path with correct type + if (contextId == null) { + return this.getAbsolutePathOfNode(sdkNode); + } + + var context = this.resolveSymbol(contextId); + if (context.isEmpty()) { + throw SymbolResolutionException.unknownSymbol(contextId); + } + if (context.isField()) { + return this.getRelativePathOfNode(sdkNode, context.field); + } + + return this.getRelativePathOfNode(sdkNode, context.node); + } + + private PathExpression getRelativePathOfNode(SdkNode sdkNode, SdkNode context) { + String relativeScript = XPathProcessor.contextualize(context.getXpathAbsolute(), sdkNode.getXpathAbsolute()); + + if (this.isNodeRepeatableFromContext(sdkNode, context)) { + return new NodeSequencePath(relativeScript); + } else { + return new NodePath(relativeScript); + } + } + + private PathExpression getRelativePathOfNode(SdkNode sdkNode, SdkField context) { + String relativeScript = XPathProcessor.contextualize(context.getXpathAbsolute(), sdkNode.getXpathAbsolute()); + + if (this.isNodeRepeatableFromContext(sdkNode, context.getParentNode())) { + return new NodeSequencePath(relativeScript); + } else { + return new NodePath(relativeScript); + } } + @Deprecated(forRemoval = true) @Override public PathExpression getRelativePath(PathExpression absolutePath, PathExpression contextPath) { return XPathContextualizer.contextualize(contextPath, absolutePath); @@ -168,18 +293,18 @@ public PathExpression getRelativePath(PathExpression absolutePath, PathExpressio @Override public String getTypeOfField(String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); + final SdkField sdkField = this.resolveField(fieldId); if (sdkField == null) { - throw SymbolResolutionException.unknownField(fieldId); + throw SymbolResolutionException.unknownSymbol(fieldId); } return sdkField.getType(); } @Override public String getRootCodelistOfField(final String fieldId) { - final SdkField sdkField = fieldById.get(fieldId); + final SdkField sdkField = this.resolveField(fieldId); if (sdkField == null) { - throw SymbolResolutionException.unknownField(fieldId); + throw SymbolResolutionException.unknownSymbol(fieldId); } final String codelistId = sdkField.getCodelistId(); if (codelistId == null) { @@ -196,26 +321,37 @@ public String getRootCodelistOfField(final String fieldId) { @Override public boolean isAttributeField(final String fieldId) { - if (!additionalFieldInfoMap.containsKey(fieldId)) { - this.cacheAdditionalFieldInfo(fieldId); + final SdkField sdkField = this.resolveField(fieldId); + if (sdkField == null) { + throw SymbolResolutionException.unknownSymbol(fieldId); } - return additionalFieldInfoMap.get(fieldId).isAttribute(); + return sdkField.getXpathInfo().isAttribute(); } @Override public String getAttributeNameFromAttributeField(final String fieldId) { - if (!additionalFieldInfoMap.containsKey(fieldId)) { - this.cacheAdditionalFieldInfo(fieldId); + final SdkField sdkField = this.resolveField(fieldId); + if (sdkField == null) { + throw SymbolResolutionException.unknownSymbol(fieldId); } - return additionalFieldInfoMap.get(fieldId).getAttributeName(); + return sdkField.getXpathInfo().getAttributeName(); } @Override public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(final String fieldId) { - if (!additionalFieldInfoMap.containsKey(fieldId)) { - this.cacheAdditionalFieldInfo(fieldId); + final SdkField sdkField = this.resolveField(fieldId); + if (sdkField == null) { + throw SymbolResolutionException.unknownSymbol(fieldId); + } + + String pathToElement = sdkField.getXpathInfo().getPathToLastElement(); + SdkNode parentNode = sdkField.getParentNode(); + + if (parentNode != null && this.isNodeRepeatableFromContext(parentNode, null)) { + return new NodeSequencePath(pathToElement); + } else { + return new NodePath(pathToElement); } - return Expression.instantiate(additionalFieldInfoMap.get(fieldId).getPathToLastElement(), NodePathExpression.class); } @Override @@ -239,7 +375,7 @@ public List getAllNoticeSubtypeIds() { return noticeTypesById.keySet().stream().map(String::toUpperCase).sorted().toList(); } - private HashMap indexFieldsByAlias() { + protected HashMap indexFieldsByAlias() { return this.fieldById.values().stream() .filter(SdkFieldV2.class::isInstance) .map(SdkFieldV2.class::cast) @@ -252,7 +388,7 @@ private HashMap indexFieldsByAlias() { )); } - private HashMap indexNodesByAlias() { + protected HashMap indexNodesByAlias() { return this.nodeById.values().stream() .filter(SdkNodeV2.class::isInstance) .map(SdkNodeV2.class::cast) @@ -265,22 +401,222 @@ private HashMap indexNodesByAlias() { )); } - // #region Temporary helpers ------------------------------------------------ + @Override + public boolean isFieldRepeatableFromContext(final String fieldId, final String contextNodeId) { + final SdkField sdkField = this.resolveField(fieldId); + if (sdkField == null) { + throw SymbolResolutionException.unknownSymbol(fieldId); + } + var contextNode = contextNodeId != null ? this.resolveNode(contextNodeId) : this.getRootNode(); + if (contextNode == null) { + throw SymbolResolutionException.unknownSymbol(contextNodeId); + } + return this.isFieldRepeatableFromContext(sdkField, contextNode); + } + + private boolean isFieldRepeatableFromContext(final SdkField sdkField, final SdkField context) { + // If the field itself is repeatable, it returns multiple values + if (sdkField.isRepeatable()) { + return true; + } - /** - * 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<>(); + // Use cached ancestry from node + Set contextAncestry = context != null + ? context.getParentNode().getAncestry() + : Collections.emptySet(); + + // Walk up from the field's parent node toward root, looking for a repeatable + // node + String currentNodeId = sdkField.getParentNodeId(); + while (currentNodeId != null) { + // Context boundary reached - the context node (even if repeatable) doesn't + // count because we're positioned inside one instance of it. No repeatable node exists + // between the field and context, so the field doesn't repeat from this context. + if (contextAncestry.contains(currentNodeId)) { + return false; + } + + SdkNode node = nodeById.get(currentNodeId); + if (node == null) { + break; + } + + // If this node is repeatable, the field returns multiple values from context + if (node.isRepeatable()) { + return true; + } + + currentNodeId = node.getParentId(); + } + + return false; + } + + private boolean isFieldRepeatableFromContext(final SdkField sdkField, final SdkNode context) { + // If the field itself is repeatable, it returns multiple values + if (sdkField.isRepeatable()) { + return true; + } + + // Use cached ancestry from node + Set contextAncestry = context != null + ? context.getAncestry() + : Collections.emptySet(); + + // Walk up from the field's parent node toward root, looking for a repeatable + // node + String currentNodeId = sdkField.getParentNodeId(); + while (currentNodeId != null) { + // Context boundary reached - the context node (even if repeatable) doesn't + // count + // because we're positioned inside one instance of it. No repeatable node exists + // between the field and context, so the field doesn't repeat from this context. + if (contextAncestry.contains(currentNodeId)) { + return false; + } + + SdkNode node = nodeById.get(currentNodeId); + if (node == null) { + break; + } + + // If this node is repeatable, the field returns multiple values from context + if (node.isRepeatable()) { + return true; + } + + currentNodeId = node.getParentId(); + } + + return false; + } + + @Override + public boolean isNodeRepeatableFromContext(final String nodeId, final String contextNodeId) { + final SdkNode sdkNode = this.resolveNode(nodeId); + if (sdkNode == null) { + throw SymbolResolutionException.unknownSymbol(nodeId); + } + + final SdkNode contextNode = contextNodeId != null + ? this.resolveNode(contextNodeId) + : null; + if (contextNodeId != null && contextNode == null) { + throw SymbolResolutionException.unknownSymbol(contextNodeId); + } + + return this.isNodeRepeatableFromContext(sdkNode, contextNode); + } + + private boolean isNodeRepeatableFromContext(final SdkNode sdkNode, final SdkNode contextNode) { + // Use cached ancestry from node + Set contextAncestry = contextNode != null + ? contextNode.getAncestry() + : Collections.emptySet(); + + // Walk up from the node toward root, looking for a repeatable node + String currentNodeId = sdkNode.getId(); + while (currentNodeId != null) { + // Context boundary reached - the context node (even if repeatable) doesn't count + // because we're positioned inside one instance of it. No repeatable node exists + // between the target node and context, so it doesn't repeat from this context. + if (contextAncestry.contains(currentNodeId)) { + return false; + } + + SdkNode node = this.nodeById.get(currentNodeId); + if (node == null) { + break; + } + + // If this node is repeatable, return true + if (node.isRepeatable()) { + return true; + } + + currentNodeId = node.getParentId(); + } + + return false; + } + + @Override + public String getRootNodeId() { + return this.getRootNode().getId(); + } + + @Override + public PathExpression getRootPath() { + return new NodePath(this.getRootNode().getXpathAbsolute()); + } + + private SdkNode getRootNode() { + if (this.cachedRootNode == null) { + this.cachedRootNode = this.nodeById.values().stream() + .filter(node -> node.getParentId() == null) + .findFirst() + .orElseThrow(SymbolResolutionException::rootNodeNotFound); + } + return this.cachedRootNode; + } + // #region Identifier Resolution ------------------------------------------------ + + static class SdkEntity { + final SdkNode node; + final SdkField field; + + static SdkEntity EMPTY = new SdkEntity(null, null); + + static SdkEntity ofNode(SdkNode node) { + return new SdkEntity(node, null); + } + + static SdkEntity ofField(SdkField field) { + return new SdkEntity(null, field); + } - private void cacheAdditionalFieldInfo(final String fieldId) { - if (additionalFieldInfoMap.containsKey(fieldId)) { - return; + private SdkEntity(SdkNode node, SdkField field) { + this.node = node; + this.field = field; } - XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript()); - additionalFieldInfoMap.put(fieldId, xpathInfo); + + boolean isNode() { + return this.node != null; + } + + boolean isField() { + return this.field != null; + } + + boolean isEmpty() { + return this.node == null && this.field == null; + } + } + + private SdkEntity resolveSymbol(String symbol) { + if (symbol == null) { + return SdkEntity.EMPTY; + } + + SdkNode node = this.resolveNode(symbol); + if (node != null) { + return SdkEntity.ofNode(node); + } + + SdkField field = this.resolveField(symbol); + return SdkEntity.ofField(field); + } + + private SdkField resolveField(String fieldId) { + return Optional.ofNullable(this.fieldById.get(fieldId)) + .orElseGet(() -> this.fieldByAlias.get(fieldId)); + } + + private SdkNode resolveNode(String nodeId) { + return Optional.ofNullable(this.nodeById.get(nodeId)) + .orElseGet(() -> this.nodeByAlias.get(nodeId)); } - // #endregion Temporary helpers ------------------------------------------------ + // #endregion Identifier Resolution ------------------------------------------------ } diff --git a/src/main/java/eu/europa/ted/efx/exceptions/ConsistencyCheckException.java b/src/main/java/eu/europa/ted/efx/exceptions/ConsistencyCheckException.java new file mode 100644 index 0000000..e156614 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/exceptions/ConsistencyCheckException.java @@ -0,0 +1,103 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.exceptions; + +/** + * Exception thrown when the toolkit encounters an internal consistency check failure. + * This indicates a bug in the toolkit implementation, not a user error. + * These are "should never happen" errors that suggest developer mistakes in the toolkit code. + */ +public class ConsistencyCheckException extends IllegalStateException { + + public enum ErrorCode { + TYPE_NOT_REGISTERED, + UNSUPPORTED_TYPE_IN_CONDITIONAL, + MISSING_TYPE_MAPPING, + MISSING_TYPE_ANNOTATION, + UNKNOWN_EXPRESSION_TYPE, + INVALID_VARIABLE_CONTEXT + } + + private static final String TYPE_NOT_REGISTERED = + "EfxDataType %s is not registered in TYPE_VARIANTS. " + + "This indicates a bug in the type system. " + + "Add the missing type to TYPE_VARIANTS with its scalar and sequence variants, " + + "ensuring subtypes appear before supertypes. " + + "Run EfxTypeLatticeTest to verify the registration."; + + private static final String UNSUPPORTED_TYPE_IN_CONDITIONAL = + "Type %s is not supported in conditional expressions. " + + "This indicates the translator is missing a handler for this type. " + + "Add an else-if branch for this type in exitConditionalExpression()."; + + private static final String MISSING_TYPE_MAPPING = + "Type %s is not mapped in %s. " + + "This indicates the translator is missing a type mapping. " + + "Add the missing type to the map."; + + private static final String MISSING_TYPE_ANNOTATION = + "TypedExpression class %s is missing @EfxDataTypeAssociation annotation. " + + "This indicates a bug in the type system. " + + "Add the annotation to specify which EfxDataType this expression represents."; + + private static final String UNKNOWN_EXPRESSION_TYPE = + "Expression type %s is not handled by the type conversion logic. " + + "This indicates a bug in the type system. " + + "The target type must be PathExpression, SequenceExpression, or ScalarExpression."; + + private static final String INVALID_VARIABLE_CONTEXT = + "Variable context is neither a field nor a node context. " + + "This indicates a bug in the translator. " + + "Ensure all variable contexts are properly classified as FieldContext or NodeContext."; + + private final ErrorCode errorCode; + + private ConsistencyCheckException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public static ConsistencyCheckException typeNotRegistered(Class type) { + return new ConsistencyCheckException(ErrorCode.TYPE_NOT_REGISTERED, + String.format(TYPE_NOT_REGISTERED, type.getName())); + } + + public static ConsistencyCheckException unsupportedTypeInConditional(Class type) { + return new ConsistencyCheckException(ErrorCode.UNSUPPORTED_TYPE_IN_CONDITIONAL, + String.format(UNSUPPORTED_TYPE_IN_CONDITIONAL, type.getName())); + } + + public static ConsistencyCheckException missingTypeMapping(Class type, String mapName) { + return new ConsistencyCheckException(ErrorCode.MISSING_TYPE_MAPPING, + String.format(MISSING_TYPE_MAPPING, type.getName(), mapName)); + } + + public static ConsistencyCheckException missingTypeAnnotation(Class type) { + return new ConsistencyCheckException(ErrorCode.MISSING_TYPE_ANNOTATION, + String.format(MISSING_TYPE_ANNOTATION, type.getName())); + } + + public static ConsistencyCheckException unknownExpressionType(Class type) { + return new ConsistencyCheckException(ErrorCode.UNKNOWN_EXPRESSION_TYPE, + String.format(UNKNOWN_EXPRESSION_TYPE, type.getName())); + } + + public static ConsistencyCheckException invalidVariableContext() { + return new ConsistencyCheckException(ErrorCode.INVALID_VARIABLE_CONTEXT, INVALID_VARIABLE_CONTEXT); + } +} diff --git a/src/main/java/eu/europa/ted/efx/exceptions/InvalidArgumentException.java b/src/main/java/eu/europa/ted/efx/exceptions/InvalidArgumentException.java index 638cf5d..7841162 100644 --- a/src/main/java/eu/europa/ted/efx/exceptions/InvalidArgumentException.java +++ b/src/main/java/eu/europa/ted/efx/exceptions/InvalidArgumentException.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.exceptions; import org.antlr.v4.runtime.misc.ParseCancellationException; @@ -12,17 +25,32 @@ */ @SuppressWarnings("squid:MaximumInheritanceDepth") // Necessary to integrate with ANTLR4 parser cancellation public class InvalidArgumentException extends ParseCancellationException { + + public enum ErrorCode { + ARGUMENT_NUMBER_MISMATCH, + ARGUMENT_TYPE_MISMATCH, + UNSUPPORTED_SEQUENCE_TYPE, + MISSING_ARGUMENT + } + private static final String ARGUMENT_NUMBER_MISMATCH = "Argument number mismatch in call to %s '%s'. Expected %d but got %d."; private static final String ARGUMENT_TYPE_MISMATCH = "Argument type mismatch for argument %d in call to %s '%s'. Expected %s but got %s."; private static final String UNSUPPORTED_SEQUENCE_TYPE = "Unsupported sequence type '%s' in call to %s."; private static final String MISSING_ARGUMENT = "No argument passed for parameter '%s'."; - private InvalidArgumentException(String message) { + private final ErrorCode errorCode; + + private InvalidArgumentException(ErrorCode errorCode, String message) { super(message); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; } public static InvalidArgumentException argumentNumberMismatch(Parametrised identifier, int expectedNumber, int actualNumber) { - return new InvalidArgumentException( + return new InvalidArgumentException(ErrorCode.ARGUMENT_NUMBER_MISMATCH, String.format(ARGUMENT_NUMBER_MISMATCH, identifier.getClass().getSimpleName().toLowerCase(), identifier.name, expectedNumber, actualNumber)); } @@ -30,20 +58,20 @@ public static InvalidArgumentException argumentNumberMismatch(Parametrised ident public static InvalidArgumentException argumentNumberMismatch(Parametrised identifier, int expectedNumber) { return argumentNumberMismatch(identifier, expectedNumber, expectedNumber + 1); } - + public static InvalidArgumentException argumentTypeMismatch(int position, Parametrised identifier, Class expectedType, Class actualType) { - return new InvalidArgumentException( + return new InvalidArgumentException(ErrorCode.ARGUMENT_TYPE_MISMATCH, String.format(ARGUMENT_TYPE_MISMATCH, position + 1, identifier.getClass().getSimpleName().toLowerCase(), identifier.name, TypedExpression.getEfxDataType(expectedType).getSimpleName(), TypedExpression.getEfxDataType(actualType).getSimpleName())); } public static InvalidArgumentException unsupportedSequenceType(String type, String functionName) { - return new InvalidArgumentException(String.format(UNSUPPORTED_SEQUENCE_TYPE, type, functionName)); + return new InvalidArgumentException(ErrorCode.UNSUPPORTED_SEQUENCE_TYPE, String.format(UNSUPPORTED_SEQUENCE_TYPE, type, functionName)); } public static InvalidArgumentException missingArgument(String parameterName) { - return new InvalidArgumentException(String.format(MISSING_ARGUMENT, parameterName)); + return new InvalidArgumentException(ErrorCode.MISSING_ARGUMENT, String.format(MISSING_ARGUMENT, parameterName)); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/exceptions/InvalidIdentifierException.java b/src/main/java/eu/europa/ted/efx/exceptions/InvalidIdentifierException.java index 60128c5..2d87373 100644 --- a/src/main/java/eu/europa/ted/efx/exceptions/InvalidIdentifierException.java +++ b/src/main/java/eu/europa/ted/efx/exceptions/InvalidIdentifierException.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.exceptions; import org.antlr.v4.runtime.misc.ParseCancellationException; @@ -10,18 +23,37 @@ */ @SuppressWarnings("squid:MaximumInheritanceDepth") // Necessary to integrate with ANTLR4 parser cancellation public class InvalidIdentifierException extends ParseCancellationException { + + public enum ErrorCode { + UNDECLARED_IDENTIFIER, + IDENTIFIER_ALREADY_DECLARED, + NOT_A_CONTEXT_VARIABLE + } + private static final String UNDECLARED_IDENTIFIER = "Identifier '%s' is not declared."; private static final String IDENTIFIER_ALREADY_DECLARED = "Identifier '%s' is already declared in this scope."; + private static final String NOT_A_CONTEXT_VARIABLE = "Variable '%s' is not a context variable."; + + private final ErrorCode errorCode; - private InvalidIdentifierException(String message) { + private InvalidIdentifierException(ErrorCode errorCode, String message) { super(message); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; } public static InvalidIdentifierException undeclaredIdentifier(String identifierName) { - return new InvalidIdentifierException(String.format(UNDECLARED_IDENTIFIER, identifierName)); + return new InvalidIdentifierException(ErrorCode.UNDECLARED_IDENTIFIER, String.format(UNDECLARED_IDENTIFIER, identifierName)); } public static InvalidIdentifierException alreadyDeclared(String identifierName) { - return new InvalidIdentifierException(String.format(IDENTIFIER_ALREADY_DECLARED, identifierName)); + return new InvalidIdentifierException(ErrorCode.IDENTIFIER_ALREADY_DECLARED, String.format(IDENTIFIER_ALREADY_DECLARED, identifierName)); + } + + public static InvalidIdentifierException notAContextVariable(String variableName) { + return new InvalidIdentifierException(ErrorCode.NOT_A_CONTEXT_VARIABLE, String.format(NOT_A_CONTEXT_VARIABLE, variableName)); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/exceptions/InvalidIndentationException.java b/src/main/java/eu/europa/ted/efx/exceptions/InvalidIndentationException.java index ce78846..1f87a22 100644 --- a/src/main/java/eu/europa/ted/efx/exceptions/InvalidIndentationException.java +++ b/src/main/java/eu/europa/ted/efx/exceptions/InvalidIndentationException.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.exceptions; import org.antlr.v4.runtime.misc.ParseCancellationException; @@ -9,41 +22,57 @@ */ @SuppressWarnings("squid:MaximumInheritanceDepth") // Necessary to integrate with ANTLR4 parser cancellation public class InvalidIndentationException extends ParseCancellationException { - 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 NO_NESTING_ON_INVOCATIONS = - "Nesting content under a template invocation is not allowed."; - private static final String NO_INDENT_ON_TEMPLATE_DECLARATIONS = "Indentation is not allowed on template declaration lines."; - - private InvalidIndentationException(String message) { + + public enum ErrorCode { + INCONSISTENT_INDENTATION_SPACES, + INDENTATION_LEVEL_SKIPPED, + START_INDENT_AT_ZERO, + MIXED_INDENTATION, + NO_NESTING_ON_INVOCATIONS, + NO_INDENT_ON_TEMPLATE_DECLARATIONS + } + + 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 NO_NESTING_ON_INVOCATIONS = "Nesting content under a template invocation is not allowed."; + private static final String NO_INDENT_ON_TEMPLATE_DECLARATIONS = "Indentation is not allowed on template declaration lines."; + + private final ErrorCode errorCode; + + private InvalidIndentationException(ErrorCode errorCode, String message) { super(message); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; } public static InvalidIndentationException inconsistentSpaces(int spaces) { - return new InvalidIndentationException(String.format(INCONSISTENT_INDENTATION_SPACES, spaces)); + return new InvalidIndentationException(ErrorCode.INCONSISTENT_INDENTATION_SPACES, + String.format(INCONSISTENT_INDENTATION_SPACES, spaces)); } public static InvalidIndentationException indentationLevelSkipped() { - return new InvalidIndentationException(INDENTATION_LEVEL_SKIPPED); + return new InvalidIndentationException(ErrorCode.INDENTATION_LEVEL_SKIPPED, INDENTATION_LEVEL_SKIPPED); } public static InvalidIndentationException startIndentAtZero() { - return new InvalidIndentationException(START_INDENT_AT_ZERO); + return new InvalidIndentationException(ErrorCode.START_INDENT_AT_ZERO, START_INDENT_AT_ZERO); } public static InvalidIndentationException mixedIndentation() { - return new InvalidIndentationException(MIXED_INDENTATION); + return new InvalidIndentationException(ErrorCode.MIXED_INDENTATION, MIXED_INDENTATION); } public static InvalidIndentationException noNestingOnInvocations() { - return new InvalidIndentationException(NO_NESTING_ON_INVOCATIONS); + return new InvalidIndentationException(ErrorCode.NO_NESTING_ON_INVOCATIONS, NO_NESTING_ON_INVOCATIONS); } + public static InvalidIndentationException noIndentOnTemplateDeclarations() { - return new InvalidIndentationException(NO_INDENT_ON_TEMPLATE_DECLARATIONS); + return new InvalidIndentationException(ErrorCode.NO_INDENT_ON_TEMPLATE_DECLARATIONS, + NO_INDENT_ON_TEMPLATE_DECLARATIONS); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java b/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java index ecadaf2..98018d3 100644 --- a/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java +++ b/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.exceptions; import org.antlr.v4.runtime.misc.ParseCancellationException; @@ -7,18 +20,43 @@ */ @SuppressWarnings("squid:MaximumInheritanceDepth") // Necessary to integrate with ANTLR4 parser cancellation public class InvalidUsageException extends ParseCancellationException { + + public enum ErrorCode { + SHORTHAND_REQUIRES_CODE_OR_INDICATOR, + SHORTHAND_REQUIRES_FIELD_CONTEXT, + INVALID_NOTICE_SUBTYPE_RANGE_ORDER, + INVALID_NOTICE_SUBTYPE_TOKEN + } + private static final String SHORTHAND_REQUIRES_CODE_OR_INDICATOR = "Indirect label reference shorthand #{%1$s}, requires a field of type 'code' or 'indicator'. Field %1$s is of type %2$s."; private static final String SHORTHAND_REQUIRES_FIELD_CONTEXT = "The %s shorthand syntax can only be used when a field is declared as context."; + private static final String INVALID_NOTICE_SUBTYPE_RANGE_ORDER = "Notice subtype range '%s-%s' is not in ascending order."; + private static final String INVALID_NOTICE_SUBTYPE_TOKEN = "Invalid notice subtype token '%s'. Expected format: 'X' or 'X-Y'."; + + private final ErrorCode errorCode; - private InvalidUsageException(String message) { + private InvalidUsageException(ErrorCode errorCode, String message) { super(message); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; } public static InvalidUsageException shorthandRequiresCodeOrIndicator(String fieldName, String fieldType) { - return new InvalidUsageException(String.format(SHORTHAND_REQUIRES_CODE_OR_INDICATOR, fieldName, fieldType)); + return new InvalidUsageException(ErrorCode.SHORTHAND_REQUIRES_CODE_OR_INDICATOR, String.format(SHORTHAND_REQUIRES_CODE_OR_INDICATOR, fieldName, fieldType)); } public static InvalidUsageException shorthandRequiresFieldContext(String shorthandType) { - return new InvalidUsageException(String.format(SHORTHAND_REQUIRES_FIELD_CONTEXT, shorthandType)); + return new InvalidUsageException(ErrorCode.SHORTHAND_REQUIRES_FIELD_CONTEXT, String.format(SHORTHAND_REQUIRES_FIELD_CONTEXT, shorthandType)); + } + + public static InvalidUsageException invalidNoticeSubtypeRangeOrder(String start, String end) { + return new InvalidUsageException(ErrorCode.INVALID_NOTICE_SUBTYPE_RANGE_ORDER, String.format(INVALID_NOTICE_SUBTYPE_RANGE_ORDER, start, end)); + } + + public static InvalidUsageException invalidNoticeSubtypeToken(String token) { + return new InvalidUsageException(ErrorCode.INVALID_NOTICE_SUBTYPE_TOKEN, String.format(INVALID_NOTICE_SUBTYPE_TOKEN, token)); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/exceptions/SymbolResolutionException.java b/src/main/java/eu/europa/ted/efx/exceptions/SymbolResolutionException.java index 8996566..a28ecaf 100644 --- a/src/main/java/eu/europa/ted/efx/exceptions/SymbolResolutionException.java +++ b/src/main/java/eu/europa/ted/efx/exceptions/SymbolResolutionException.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.exceptions; import org.antlr.v4.runtime.misc.ParseCancellationException; @@ -10,33 +23,49 @@ */ @SuppressWarnings("squid:MaximumInheritanceDepth") // Necessary to integrate with ANTLR4 parser cancellation public class SymbolResolutionException extends ParseCancellationException { - private static final String UNKNOWN_FIELD = "Unknown field '%s'."; - private static final String UNKNOWN_NODE = "Unknown node '%s'."; + + public enum ErrorCode { + UNKNOWN_SYMBOL, + UNKNOWN_CODELIST, + NO_CODELIST_FOR_FIELD, + ROOT_NODE_NOT_FOUND, + UNKNOWN_NOTICE_SUBTYPE + } + + private static final String UNKNOWN_SYMBOL = "Unknown symbol '%s'."; private static final String UNKNOWN_CODELIST = "Unknown codelist '%s'."; - private static final String UNKNOWN_ALIAS = "Unknown field or node alias '%s'."; private static final String NO_CODELIST_FOR_FIELD = "Field '%s' is not associated with a codelist."; + private static final String ROOT_NODE_NOT_FOUND = "Could not find the root node. Check that node metadata is loaded correctly."; + private static final String UNKNOWN_NOTICE_SUBTYPE = "Unknown notice subtype '%s' in range '%s'."; - private SymbolResolutionException(String message) { - super(message); - } + private final ErrorCode errorCode; - public static SymbolResolutionException unknownField(String fieldId) { - return new SymbolResolutionException(String.format(UNKNOWN_FIELD, fieldId)); + private SymbolResolutionException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; } - public static SymbolResolutionException unknownNode(String nodeId) { - return new SymbolResolutionException(String.format(UNKNOWN_NODE, nodeId)); + public ErrorCode getErrorCode() { + return errorCode; } public static SymbolResolutionException unknownCodelist(String codelistId) { - return new SymbolResolutionException(String.format(UNKNOWN_CODELIST, codelistId)); + return new SymbolResolutionException(ErrorCode.UNKNOWN_CODELIST, String.format(UNKNOWN_CODELIST, codelistId)); } - public static SymbolResolutionException unknownAlias(String alias) { - return new SymbolResolutionException(String.format(UNKNOWN_ALIAS, alias)); + public static SymbolResolutionException unknownSymbol(String symbol) { + return new SymbolResolutionException(ErrorCode.UNKNOWN_SYMBOL, String.format(UNKNOWN_SYMBOL, symbol)); } public static SymbolResolutionException noCodelistForField(String fieldId) { - return new SymbolResolutionException(String.format(NO_CODELIST_FOR_FIELD, fieldId)); + return new SymbolResolutionException(ErrorCode.NO_CODELIST_FOR_FIELD, String.format(NO_CODELIST_FOR_FIELD, fieldId)); + } + + public static SymbolResolutionException rootNodeNotFound() { + return new SymbolResolutionException(ErrorCode.ROOT_NODE_NOT_FOUND, ROOT_NODE_NOT_FOUND); + } + + public static SymbolResolutionException unknownNoticeSubtype(String noticeSubtype, String rangeString) { + return new SymbolResolutionException(ErrorCode.UNKNOWN_NOTICE_SUBTYPE, String.format(UNKNOWN_NOTICE_SUBTYPE, noticeSubtype, rangeString)); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/exceptions/TypeMismatchException.java b/src/main/java/eu/europa/ted/efx/exceptions/TypeMismatchException.java index d83d95f..1eeb6e1 100644 --- a/src/main/java/eu/europa/ted/efx/exceptions/TypeMismatchException.java +++ b/src/main/java/eu/europa/ted/efx/exceptions/TypeMismatchException.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.exceptions; import org.antlr.v4.runtime.misc.ParseCancellationException; @@ -13,11 +26,28 @@ */ @SuppressWarnings("squid:MaximumInheritanceDepth") // Necessary to integrate with ANTLR4 parser cancellation public class TypeMismatchException extends ParseCancellationException { - private static final String TYPE_MISMATCH_CANNOT_CONVERT = "Type mismatch. Expected %s instead of %s."; - private static final String TYPE_MISMATCH_CANNOT_COMPARE = "Type mismatch. Cannot compare values of different types: %s and %s"; - private TypeMismatchException(String message) { + public enum ErrorCode { + CANNOT_CONVERT, + CANNOT_COMPARE, + EXPECTED_SEQUENCE, + EXPECTED_FIELD_CONTEXT + } + + private static final String CANNOT_CONVERT = "Type mismatch. Expected %s instead of %s."; + private static final String CANNOT_COMPARE = "Type mismatch. Cannot compare values of different types: %s and %s"; + private static final String EXPECTED_SEQUENCE = "Type mismatch. Field '%s' may return multiple values from context '%s', but is used as a scalar. Use a sequence expression or change the context."; + private static final String EXPECTED_FIELD_CONTEXT = "Type mismatch. Context variable '$%s' refers to node '%s', but is used as a value. Only field context variables can be used in value expressions."; + + private final ErrorCode errorCode; + + private TypeMismatchException(ErrorCode errorCode, String message) { super(message); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; } public static TypeMismatchException cannotConvert(Class expectedType, @@ -27,17 +57,26 @@ public static TypeMismatchException cannotConvert(Class var actual = actualType.asSubclass(TypedExpression.class); var expected = expectedType.asSubclass(TypedExpression.class); - return new TypeMismatchException(String.format(TYPE_MISMATCH_CANNOT_CONVERT, + return new TypeMismatchException(ErrorCode.CANNOT_CONVERT, String.format(CANNOT_CONVERT, TypedExpression.getEfxDataType(expected).getSimpleName(), TypedExpression.getEfxDataType(actual).getSimpleName())); } - return new TypeMismatchException(String.format(TYPE_MISMATCH_CANNOT_CONVERT, + return new TypeMismatchException(ErrorCode.CANNOT_CONVERT, String.format(CANNOT_CONVERT, expectedType.getSimpleName(), actualType.getSimpleName())); } public static TypeMismatchException cannotCompare(Expression left, Expression right) { - return new TypeMismatchException(String.format(TYPE_MISMATCH_CANNOT_COMPARE, + return new TypeMismatchException(ErrorCode.CANNOT_COMPARE, String.format(CANNOT_COMPARE, left.getClass().getSimpleName(), right.getClass().getSimpleName())); } + + public static TypeMismatchException fieldMayRepeat(String fieldId, String contextSymbol) { + return new TypeMismatchException(ErrorCode.EXPECTED_SEQUENCE, String.format(EXPECTED_SEQUENCE, fieldId, + contextSymbol != null ? contextSymbol : "root")); + } + + public static TypeMismatchException nodesHaveNoValue(String variableName, String nodeId) { + return new TypeMismatchException(ErrorCode.EXPECTED_FIELD_CONTEXT, String.format(EXPECTED_FIELD_CONTEXT, variableName, nodeId)); + } } 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 a3c3a3d..757b872 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java @@ -19,7 +19,8 @@ import org.apache.commons.lang3.tuple.Pair; import eu.europa.ted.efx.model.expressions.Expression; -import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.PathExpression; +import eu.europa.ted.efx.model.expressions.TypedExpression; 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.Conditional; @@ -77,13 +78,33 @@ default public Markup composeOutputFile(final List content, final List type, final String name, final Expression initialiser); + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default Markup renderVariableDeclaration(final Class type, final String name, final Expression initialiser) { + assert initialiser instanceof TypedExpression : "Initialiser must be a TypedExpression"; + return renderVariableDeclaration(name, (TypedExpression) initialiser); + } /** * Renders the markup necessary to declare and initialise a dictionary . @@ -99,19 +120,36 @@ default public Markup composeOutputFile(final List content, final List> parameters, final TypedExpression expression); + /** * Renders the Markup necessary to make the function available at runtime. * - * @param type A sub-class of EfxDataType that represents the return type - * of the function. + * @deprecated Use {@link #renderFunctionDeclaration(String, Map, TypedExpression)} instead. + * This method is being deprecated as of version 2.0.0-alpha.6 and + * will be removed before version 2.0.0 is released. + * + * @param type The return type of the function. * @param name The name of the function. - * @param parameters The function parameters as a map of parameter names to - * their corresponding EfxDataType. - * @param expression The function body (the expression that must be evaluated - * when the function is invoked). + * @param parameters The parameters of the function as a map from parameter name to its EFX data type. + * @param expression The function body expression. * @return A Markup object that declares the function. */ - Markup renderFunctionDeclaration(final Class type, final String name, final Map> parameters, final Expression expression); + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) + default Markup renderFunctionDeclaration(final Class type, final String name, final Map> parameters, final Expression expression) { + assert expression instanceof TypedExpression : "Expression must be a TypedExpression"; + return renderFunctionDeclaration(name, parameters, (TypedExpression) expression); + } + /** * Given an expression (which will eventually, at runtime, evaluate to the value @@ -144,7 +182,7 @@ default public Markup renderVariableExpression(final Expression variableExpressi * @return the template code that renders the label in the target template * language. */ - Markup renderLabelFromKey(final StringExpression key, TranslatorContext translatorContext); + Markup renderLabelFromKey(final StringExpression key, final TranslatorContext translatorContext); @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default public Markup renderLabelFromKey(final StringExpression key) { @@ -164,7 +202,7 @@ default public Markup renderLabelFromKey(final StringExpression key) { * @return the template code that renders the label in the target template * language. */ - Markup renderLabelFromKey(final StringExpression key, final NumericExpression quantity, TranslatorContext translatorContext); + Markup renderLabelFromKey(final StringExpression key, final NumericExpression quantity, final TranslatorContext translatorContext); @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default public Markup renderLabelFromKey(final StringExpression key, final NumericExpression quantity) { @@ -183,7 +221,7 @@ default public Markup renderLabelFromKey(final StringExpression key, final Numer * @return the template code that renders the label in the target template * language. */ - Markup renderLabelFromExpression(final Expression expression, TranslatorContext translatorContext); + Markup renderLabelFromExpression(final Expression expression, final TranslatorContext translatorContext); @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default public Markup renderLabelFromExpression(final Expression expression) { @@ -204,7 +242,7 @@ default public Markup renderLabelFromExpression(final Expression expression) { * @return the template code that renders the label in the target template * language. */ - Markup renderLabelFromExpression(final Expression expression, final NumericExpression quantity, TranslatorContext translatorContext); + Markup renderLabelFromExpression(final Expression expression, final NumericExpression quantity, final TranslatorContext translatorContext); @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default public Markup renderLabelFromExpression(final Expression expression, final NumericExpression quantity) { @@ -222,7 +260,7 @@ default public Markup renderLabelFromExpression(final Expression expression, fin * @param translatorContext additional context information provided by the template translator. * @return the template code that adds this text in the target template. */ - Markup renderFreeText(final String freeText, TranslatorContext translatorContext); + Markup renderFreeText(final String freeText, final TranslatorContext translatorContext); @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default public Markup renderFreeText(final String freeText) { @@ -238,7 +276,7 @@ default public Markup renderFreeText(final String freeText) { * @param translatorContext additional context information provided by the template translator. * @return the template code that renders the hyperlink in the target template language. */ - Markup renderHyperlink(final Markup label, final StringExpression url, TranslatorContext translatorContext); + Markup renderHyperlink(final Markup label, final StringExpression url, final TranslatorContext translatorContext); /** @@ -250,7 +288,7 @@ default public Markup renderFreeText(final String freeText) { * * @return the markup that represents a line break in the target template. */ - Markup renderLineBreak(TranslatorContext translatorContext); + Markup renderLineBreak(final TranslatorContext translatorContext); @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default public Markup renderLineBreak() { @@ -259,17 +297,16 @@ default public Markup renderLineBreak() { /** * Given a fragment name (identifier) and some pre-rendered content, this method - * returns the code - * that encapsulates it in the target template - * * @deprecated This method is deprecated and will be removed in future versions. - * Use {@link #composeFragmentDefinition(String, String, Set, Markup, Markup, Set)} instead. - * We are keeping the method temporarily to prevent build errors. - * This method is being deprecated as of version 2.0.0-alpha.6 and - * will be removed before version 2.0.0 is released - * The default implementation provided here only throws - * UnsupportedOperationException to prevent accidental use of this - * method. The EfxTemplateTranslator will not call this method. - * + * returns the code that encapsulates it in the target template. + * + * @deprecated This method is deprecated and will be removed in future versions. + * Use {@link #composeFragmentDefinition(String, String, Set, Markup, Markup, Set, + * TranslatorContext)} instead. We are keeping the method temporarily to prevent + * build errors. This method is being deprecated as of version 2.0.0-alpha.6 and + * will be removed before version 2.0.0 is released. The default implementation + * provided here only throws UnsupportedOperationException to prevent accidental + * use of this method. The EfxTemplateTranslator will not call this method. + * * @param name the name of the fragment. * @param number the outline number of the fragment. * @param content the content of the fragment. @@ -285,20 +322,19 @@ default Markup composeFragmentDefinition(final String name, String number, Marku /** * Given a fragment name (identifier) and some pre-rendered content, this method - * returns the code - * that encapsulates it in the target template - * - * @param name the name of the fragment. - * @param number the outline number of the fragment. - * @param conditionals the conditionals of the fragment. - * @param content the content of the fragment. - * @param children the children of the fragment. - * @param parameters the parameters of the fragment. - * @param translatorContext additional context information provided by the template translator. - * @return the code that encapsulates the fragment in the target template. + * returns the code that encapsulates it in the target template. + * + * @param name the name of the fragment. + * @param number the outline number of the fragment. + * @param conditionals the conditionals of the fragment. + * @param content the content of the fragment. + * @param children the children of the fragment. + * @param parameters the parameters of the fragment. + * @param translatorContext additional context information provided by the template translator. + * @return the code that encapsulates the fragment in the target template. */ - Markup composeFragmentDefinition(final String name, String number, Set conditionals, Markup content, Markup children, - Set parameters, TranslatorContext translatorContext); + Markup composeFragmentDefinition(final String name, final String number, final Set conditionals, final Markup content, final Markup children, + final Set parameters, final TranslatorContext translatorContext); @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) default public Markup composeFragmentDefinition(final String name, String number, Set conditionals, @@ -325,7 +361,7 @@ default public Markup composeFragmentDefinition(final String name, String number * @param translatorContext additional context information provided by the template translator. * @return the code that invokes (uses) the fragment. */ - Markup renderFragmentInvocation(final String name, final Set arguments, TranslatorContext translatorContext); + Markup renderFragmentInvocation(final String name, final Set arguments, final TranslatorContext translatorContext); /** * Given a fragment name (identifier), and an evaluation context, this method @@ -361,7 +397,7 @@ default public Markup renderFragmentInvocation(final String name, final Set T composeList(List T composeConditionalExpression(BooleanExpress public T composeForExpression( IteratorListExpression iterators, ScalarExpression expression, Class targetListType); - public IteratorExpression composeIteratorExpression(Expression variableDeclarationExpression, SequenceExpression sourceList); + public IteratorExpression composeIteratorExpression(Expression variableDeclarationExpression, SequenceExpression sourceList); public IteratorListExpression composeIteratorList(List iterators); @@ -250,9 +250,21 @@ public PathExpression composeFieldInExternalReference(final PathExpression exter */ public PathExpression joinPaths(PathExpression first, PathExpression second); + /** + * Makes the given absolute path relative to the given context path. + * + * This is target-language-specific because different target languages (XPath, JavaScript, etc.) + * may have different path contextualization semantics. + * + * @param absolutePath The absolute path to contextualize. + * @param contextPath The context path to make the result relative to. + * @return The path relative to the given context. + */ + public PathExpression contextualizePath(PathExpression absolutePath, PathExpression contextPath); + /** * Gets a piece of text and returns it inside quotes as expected by the target language. - * + * * @param value The text to be quoted. * @return The quoted text. */ @@ -285,7 +297,7 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, /** * 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. */ @@ -294,7 +306,7 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, /** * 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. */ 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 f56132f..9cbe03a 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/SymbolResolver.java @@ -15,7 +15,7 @@ import java.util.List; -import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.PathExpression; /** * A SymbolResolver is a mechanism used by EFX translators to resolve symbols. @@ -48,27 +48,63 @@ public interface SymbolResolver { * 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. * @return The path to the given field relative to the given contextPath. + * @deprecated Use {@link #getRelativePathOfField(String, String)} instead. */ + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) public PathExpression getRelativePathOfField(final String fieldId, final PathExpression contextPath); + /** + * Gets the path that can be used to locate the given field in the data source, relative to the + * given context (node or field). + * + * @param fieldId The identifier of the field to look for. + * @param contextId The identifier of the context node or field. If a field ID is provided, + * its parent node is used as the context. + * @return The path to the given field relative to the given context. + */ + public PathExpression getRelativePathOfField(final String fieldId, final String contextId); + /** * Gets the path that can be used to locate the given node in the data source, relative to another * 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. + * @deprecated Use {@link #getRelativePathOfNode(String, String)} instead. */ + @Deprecated(since = "2.0.0-alpha.6", forRemoval = true) public PathExpression getRelativePathOfNode(final String nodeId, final PathExpression contextPath); + /** + * Gets the path that can be used to locate the given node in the data source, relative to the + * given context (node or field). + * + * @param nodeId The identifier of the node to look for. + * @param contextId The identifier of the context node or field. If a field ID is provided, + * its parent node is used as the context. + * @return The path to the given node relative to the given context. + */ + public PathExpression getRelativePathOfNode(final String nodeId, final String contextId); + + /** + * Converts an absolute path to a relative path based on the given context. + * + * @param absolutePath The absolute path to convert. + * @param contextPath The context path to make the result relative to. + * @return The path relative to the given context. + * @deprecated Use {@link ScriptGenerator#contextualizePath(PathExpression, PathExpression)} instead. + * Path contextualization is target-language-specific and belongs on ScriptGenerator. + */ + @Deprecated(forRemoval = true) public PathExpression getRelativePath(PathExpression absolutePath, PathExpression contextPath); /** @@ -159,8 +195,70 @@ public PathExpression getRelativePathOfNode(final String nodeId, * @return List of notice type IDs (e.g., ["1", "2", ..., "40", "CEI", "E1", ..., "X02"]) */ public List getAllNoticeSubtypeIds(); - + + /** + * Resolves a field alias to its canonical field identifier. + * + * Returns null if the alias is not recognized as a field alias. This allows callers to implement + * fallback logic (e.g., trying {@link #getNodeIdFromAlias} next). The translator throws + * {@code SymbolResolutionException.unknownAlias()} only when both field and node lookups fail. + * + * @param alias The alias to resolve. + * @return The canonical field ID, or null if the alias is not a known field alias. + */ public String getFieldIdFromAlias(final String alias); + /** + * Resolves a node alias to its canonical node identifier. + * + * Returns null if the alias is not recognized as a node alias. This allows callers to implement + * fallback logic. The translator throws {@code SymbolResolutionException.unknownAlias()} only + * when both field and node lookups fail. + * + * @param alias The alias to resolve. + * @return The canonical node ID, or null if the alias is not a known node alias. + */ public String getNodeIdFromAlias(final String alias); + + /** + * Determines if a field reference would return multiple values when evaluated from a given + * context. + * + * A field is considered repeatable from a context if: + * 1. The field itself is marked as repeatable, OR + * 2. Any node between the field's parent and the context (exclusive) is repeatable + * + * @param fieldId The identifier of the field to check. + * @param contextNodeId The identifier of the context node, or null for root context. + * @return true if the field would return multiple values from the given context. + */ + public boolean isFieldRepeatableFromContext(final String fieldId, final String contextNodeId); + + /** + * Determines if a node reference would return multiple values when evaluated from a given + * context. + * + * A node is considered repeatable from a context if: + * 1. The node itself is marked as repeatable, OR + * 2. Any ancestor node between the node and the context (exclusive) is repeatable + * + * @param nodeId The identifier of the node to check. + * @param contextNodeId The identifier of the context node, or null for root context. + * @return true if the node would return multiple values from the given context. + */ + public boolean isNodeRepeatableFromContext(final String nodeId, final String contextNodeId); + + /** + * Gets the identifier of the root node. + * + * @return The root node ID (e.g., "ND-Root"). + */ + public String getRootNodeId(); + + /** + * Gets the absolute path to the root node. + * + * @return The absolute path of the root node as a PathExpression. + */ + public PathExpression getRootPath(); } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TypeChecker.java b/src/main/java/eu/europa/ted/efx/interfaces/TypeChecker.java new file mode 100644 index 0000000..86d1210 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/interfaces/TypeChecker.java @@ -0,0 +1,42 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.interfaces; + +import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.sdk1.TypeCheckerV1; +import eu.europa.ted.efx.sdk2.TypeCheckerV2; + +/** + * Determines whether one EFX expression type can be converted to another. + * + * Used by the {@link eu.europa.ted.efx.model.CallStack} to validate type compatibility during + * EFX expression evaluation. Different SDK versions have different type checking rules: + * {@link eu.europa.ted.efx.sdk1.TypeCheckerV1} (lenient) and + * {@link eu.europa.ted.efx.sdk2.TypeCheckerV2} (strict, requiring concrete types). + */ +public interface TypeChecker { + + TypeChecker V1 = TypeCheckerV1.INSTANCE; + TypeChecker V2 = TypeCheckerV2.INSTANCE; + + /** + * Checks if an expression of type {@code from} can be converted to type {@code to}. + * + * @param from the source expression type + * @param to the target expression type + * @return {@code true} if the conversion is allowed, {@code false} otherwise + */ + boolean canConvert(Class from, + Class to); +} 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 77907b5..592c2e4 100644 --- a/src/main/java/eu/europa/ted/efx/model/CallStack.java +++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model; import java.util.HashMap; @@ -13,6 +26,7 @@ import eu.europa.ted.efx.exceptions.InvalidIdentifierException; import eu.europa.ted.efx.exceptions.TypeMismatchException; +import eu.europa.ted.efx.interfaces.TypeChecker; import eu.europa.ted.efx.model.expressions.TypedExpression; import eu.europa.ted.efx.model.types.EfxDataType; import eu.europa.ted.efx.model.variables.ParsedParameter; @@ -33,6 +47,11 @@ public class CallStack { 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."; + /** + * The type checker used for type conversion checks. + */ + private final TypeChecker typeChecker; + /** * Stack frames are means of controlling the scope of variables and parameters. * Certain @@ -63,7 +82,7 @@ void declareIdentifier(Identifier identifier) { * 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. */ @@ -76,7 +95,7 @@ synchronized T pop(Class expectedType) { 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)) { + if (typeChecker.canConvert(actual, expected)) { return expectedType.cast(TypedExpression.from((TypedExpression) this.pop(), expected)); } throw TypeMismatchException.cannotConvert(expected, actual); @@ -94,7 +113,7 @@ synchronized T peek(Class expectedType) { 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)) { + if (typeChecker.canConvert(actual, expected)) { return expectedType.cast(TypedExpression.from((TypedExpression) this.peek(), expected)); } } @@ -123,13 +142,23 @@ public void clear() { Map globalIdentifierRegistry = new LinkedHashMap<>(); /** - * Default and only constructor. Adds a global scope to the stack. + * Creates a CallStack with a specific type checker. + * + * @param typeChecker The type checker to use for type conversion checks. */ - public CallStack() { + public CallStack(TypeChecker typeChecker) { + this.typeChecker = typeChecker; this.frames = new Stack<>(); this.frames.push(new StackFrame()); // The global scope } + /** + * Default constructor. Uses TypeCheckerV2 for strict type checking. + */ + public CallStack() { + this(TypeChecker.V2); + } + /** * Creates a new stack frame and pushes it on top of the call stack. * 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 0403f2f..2d199cf 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,19 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model; -import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.PathExpression; import eu.europa.ted.efx.model.variables.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 4d8a781..4fc73aa 100644 --- a/src/main/java/eu/europa/ted/efx/model/ContextStack.java +++ b/src/main/java/eu/europa/ted/efx/model/ContextStack.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model; import java.util.HashMap; @@ -7,7 +20,7 @@ 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.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.PathExpression; /** * Used to keep track of the current evaluation context. Extends Stack<Context> to provide @@ -44,7 +57,7 @@ public FieldContext pushFieldContext(final String fieldId) { this.push(context); return context; } - PathExpression relativePath = symbols.getRelativePathOfField(fieldId, this.absolutePath()); + PathExpression relativePath = symbols.getRelativePathOfField(fieldId, this.symbol()); FieldContext context = new FieldContext(fieldId, absolutePath, relativePath); this.push(context); return context; @@ -64,7 +77,7 @@ public NodeContext pushNodeContext(final String nodeId) { this.push(context); return context; } - PathExpression relativePath = symbols.getRelativePathOfNode(nodeId, this.absolutePath()); + PathExpression relativePath = symbols.getRelativePathOfNode(nodeId, this.symbol()); NodeContext context = new NodeContext(nodeId, absolutePath, relativePath); this.push(context); return context; @@ -137,7 +150,7 @@ 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() { @@ -147,4 +160,17 @@ public PathExpression relativePath() { return this.peek().relativePath(); } + + /** + * Returns the parent context (second from top of the stack) without removing it. + * This is useful for the ".." context shortcut which refers to the grandparent context. + * + * @return the parent context, or null if there are fewer than 2 contexts on the stack. + */ + public Context peekParentContext() { + if (this.size() < 2) { + return null; + } + return this.get(this.size() - 2); + } } 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 75d6dd5..54c6d62 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 @@ -1,3 +1,16 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.expressions; import java.lang.reflect.Constructor; @@ -7,34 +20,36 @@ import eu.europa.ted.efx.model.ParsedEntity; +/** + * Root interface for all expression AST nodes in the EFX type system. + * + * An {@link Expression} wraps a target-language script fragment (e.g., XPath) produced during + * EFX-to-target-language translation. The script is accessed via {@link #getScript()}. + * + * Expressions form a type hierarchy enabling compile-time type safety during translation. + * Use {@link TypedExpression} subinterfaces for type-specific operations. + * + * @see TypedExpression for expressions with associated EFX data types + * @see LiteralExpression for constant values known at translation time + * @see PathExpression for references to document elements + */ public interface Expression extends ParsedEntity { 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 (Boolean.TRUE.equals(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); - } + Constructor constructor = type.getConstructor(String.class); + return constructor.newInstance(script); } catch (Exception e) { throw new ParseCancellationException(e); } } + static T from(Expression source, Class returnType) { + return Expression.instantiate(source.getScript(), returnType); + } + static T empty(Class type) { return instantiate("", type); } @@ -45,25 +60,14 @@ static T empty(Class type) { 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() { @@ -81,12 +85,13 @@ public boolean equals(Object obj) { } Expression other = (Expression) obj; - return Objects.equals(script, other.getScript()) && isLiteral == other.isLiteral(); + return Objects.equals(script, other.getScript()) + && (this instanceof LiteralExpression) == (other instanceof LiteralExpression); } - + @Override public int hashCode() { - return Objects.hash(script, isLiteral); + return Objects.hash(script, this instanceof LiteralExpression); } } -} \ No newline at end of file +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/LiteralExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/LiteralExpression.java new file mode 100644 index 0000000..8f7e633 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/LiteralExpression.java @@ -0,0 +1,46 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions; + +import eu.europa.ted.efx.model.expressions.scalar.ScalarLiteral; +import eu.europa.ted.efx.model.expressions.sequence.SequenceLiteral; + +/** + * A {@link TypedExpression} representing a constant value known at translation time. + * + * Unlike a {@link PathExpression} which generates a script that resolves to a value at runtime, + * a literal expression generates a script that embeds the constant value itself. + * + * {@link ScalarLiteral} represents single-value literals, while {@link SequenceLiteral} + * represents sequences of literals. + * + * @see PathExpression for expressions that reference document data + */ +public interface LiteralExpression extends TypedExpression { + + /** + * 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); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/PathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/PathExpression.java new file mode 100644 index 0000000..16e87c4 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/PathExpression.java @@ -0,0 +1,63 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions; + +import eu.europa.ted.efx.model.expressions.scalar.ScalarPath; +import eu.europa.ted.efx.model.expressions.sequence.SequencePath; + +/** + * A {@link TypedExpression} representing a reference to an eForms Field or Node in a document. + * + * The generated script resolves to a value at runtime (similar to an XPath expression). + * + * {@link ScalarPath} represents references that resolve to a single value, while + * {@link SequencePath} represents references that may resolve to multiple values. + * Path expressions can convert between scalar and sequence representations via + * {@link #asScalar()} and {@link #asSequence()}. + */ +public interface PathExpression extends TypedExpression { + + /** + * Returns a scalar version of this path expression. + * If already scalar, returns this. If a sequence, creates the corresponding + * scalar path expression with the same script. + * + * @return A {@link ScalarPath} with the same script and primitive type. + */ + ScalarPath asScalar(); + + /** + * Returns a sequence version of this path expression. + * If already a sequence, returns this. If scalar, creates the corresponding + * sequence path expression with the same script. + * + * @return A {@link SequencePath} with the same script and primitive type. + */ + SequencePath asSequence(); + + /** + * 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); + } +} \ 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 index 1b21095..292fc00 100644 --- a/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java +++ b/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java @@ -1,96 +1,91 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.expressions; -import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.exceptions.ConsistencyCheckException; 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; - +import eu.europa.ted.efx.model.types.EfxTypeLattice; + +/** + * An {@link Expression} with an associated {@link EfxDataType}. + * + * Each concrete implementation is annotated with {@link EfxDataTypeAssociation} to declare its + * type in the EFX type system. This enables compile-time type checking and type-safe conversions + * during EFX translation. + * + * @see EfxDataType for the complete type hierarchy + * @see EfxDataTypeAssociation for the annotation linking expressions to types + */ 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"); + throw ConsistencyCheckException.missingTypeAnnotation(clazz); } return annotation.dataType(); } public static T from(TypedExpression source, Class targetType) { + // When target is PathExpression, delegate to PathExpression.from 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); + // When converting PathExpression to SequenceExpression, use the appropriate concrete sequence type + if (source instanceof PathExpression && SequenceExpression.class.isAssignableFrom(targetType)) { + Class primitiveType = EfxTypeLattice.toPrimitive(source.getDataType()); + Class concreteType = SequenceExpression.fromEfxDataType.get(primitiveType); + if (concreteType != null) { + return targetType.cast(Expression.from(source, concreteType)); + } } - } - 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(); + // When converting PathExpression to ScalarExpression, use the appropriate concrete scalar type + if (source instanceof PathExpression && ScalarExpression.class.isAssignableFrom(targetType)) { + Class primitiveType = EfxTypeLattice.toPrimitive(source.getDataType()); + Class concreteType = ScalarExpression.fromEfxDataType.get(primitiveType); + if (concreteType != null) { + return targetType.cast(Expression.from(source, concreteType)); + } + } - return toExpressionType.isAssignableFrom(fromExpressionType) && toDataType.isAssignableFrom(fromDataType); + 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 ConsistencyCheckException.unknownExpressionType(targetType); + } } public abstract class Impl extends Expression.Impl implements TypedExpression { - private Class expressionType; private Class dataType; - protected Impl(final String script, Class expressionType, - Class dataType) { - this(script, false, expressionType, dataType); - } - - protected Impl(final String script, final Boolean isLiteral, - Class expressionType, Class dataType) { - super(script, isLiteral); - this.expressionType = expressionType; + protected Impl(final String script, Class dataType) { + super(script); this.dataType = dataType; } - @Override - public Class getExpressionType() { - return this.expressionType; - } - @Override public Class getDataType() { return this.dataType; @@ -100,10 +95,5 @@ public Class getDataType() { 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/path/BooleanPathExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/path/BooleanPathExpression.java deleted file mode 100644 index edf55b5..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/BooleanPathExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index e7a8c76..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/DatePathExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index ea87912..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/DurationPathExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 84c0f5f..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/MultilingualStringPathExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index b81591f..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/NodePathExpression.java +++ /dev/null @@ -1,16 +0,0 @@ -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); - } - - public static NodePathExpression empty() { - return new NodePathExpression(""); - } -} \ 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 deleted file mode 100644 index c404c93..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/NumericPathExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index ae3f06e..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/PathExpression.java +++ /dev/null @@ -1,128 +0,0 @@ -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. - * - * @param the EFX data type for the expression. - */ - @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 deleted file mode 100644 index 4573b01..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/StringPathExpression.java +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 30acecf..0000000 --- a/src/main/java/eu/europa/ted/efx/model/expressions/path/TimePathExpression.java +++ /dev/null @@ -1,12 +0,0 @@ -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 index 61ab75b..3a0cf33 100644 --- 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 @@ -1,20 +1,35 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A boolean-typed scalar AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a boolean value. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Boolean.class) -public class BooleanExpression extends ScalarExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.BooleanScalar.class) +public class BooleanExpression extends ScalarExpression.Impl { public BooleanExpression(final String script) { - super(script, EfxDataType.Boolean.class); + super(script, EfxDataType.BooleanScalar.class); } - public BooleanExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Boolean.class); + protected BooleanExpression(final String script, Class type) { + super(script, type); } public static BooleanExpression empty() { diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanLiteral.java new file mode 100644 index 0000000..ae1c6dd --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A boolean-typed literal AST node representing a constant boolean value known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.BooleanScalar.class) +public class BooleanLiteral extends BooleanExpression implements ScalarLiteral { + + public BooleanLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanPath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanPath.java new file mode 100644 index 0000000..d35d1c9 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/BooleanPath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A boolean-typed path AST node referencing an eForms Field that resolves to a boolean value. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.BooleanScalar.class) +public class BooleanPath extends ScalarPath.Impl { + + public BooleanPath(final String script) { + super(script, EfxDataType.BooleanScalar.class); + } + + protected BooleanPath(final String script, Class type) { + super(script, type); + } + + public static BooleanPath empty() { + return new BooleanPath(""); + } +} \ 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 index 8975b7f..7202e58 100644 --- 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 @@ -1,20 +1,35 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A date-typed scalar AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a date value. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Date.class) -public class DateExpression extends ScalarExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.DateScalar.class) +public class DateExpression extends ScalarExpression.Impl { public DateExpression(final String script) { - super(script, EfxDataType.Date.class); + super(script, EfxDataType.DateScalar.class); } - public DateExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Date.class); + protected DateExpression(final String script, Class type) { + super(script, type); } public static DateExpression empty() { diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateLiteral.java new file mode 100644 index 0000000..32ff14a --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DateLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A date-typed literal AST node representing a constant date value known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DateScalar.class) +public class DateLiteral extends DateExpression implements ScalarLiteral { + + public DateLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DatePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DatePath.java new file mode 100644 index 0000000..0bd789f --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DatePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A date-typed path AST node referencing an eForms Field that resolves to a date value. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DateScalar.class) +public class DatePath extends ScalarPath.Impl { + + public DatePath(final String script) { + super(script, EfxDataType.DateScalar.class); + } + + protected DatePath(final String script, Class type) { + super(script, type); + } + + public static DatePath empty() { + return new DatePath(""); + } +} \ 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 index d3c6b04..ae14bcf 100644 --- 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 @@ -1,20 +1,35 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A duration-typed scalar AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a duration value. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Duration.class) -public class DurationExpression extends ScalarExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.DurationScalar.class) +public class DurationExpression extends ScalarExpression.Impl { public DurationExpression(final String script) { - super(script, EfxDataType.Duration.class); + super(script, EfxDataType.DurationScalar.class); } - public DurationExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Duration.class); + protected DurationExpression(final String script, Class type) { + super(script, type); } public static DurationExpression empty() { diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationLiteral.java new file mode 100644 index 0000000..ed598cd --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A duration-typed literal AST node representing a constant duration value known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DurationScalar.class) +public class DurationLiteral extends DurationExpression implements ScalarLiteral { + + public DurationLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationPath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationPath.java new file mode 100644 index 0000000..2e2953f --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/DurationPath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A duration-typed path AST node referencing an eForms Field that resolves to a duration value. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DurationScalar.class) +public class DurationPath extends ScalarPath.Impl { + + public DurationPath(final String script) { + super(script, EfxDataType.DurationScalar.class); + } + + protected DurationPath(final String script, Class type) { + super(script, type); + } + + public static DurationPath empty() { + return new DurationPath(""); + } +} \ 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 index ba9c18b..ec7595a 100644 --- 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 @@ -1,16 +1,30 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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) +/** + * A multilingual-string-typed scalar AST node, extending {@link StringExpression}. + * + * Represents strings that may have multiple language versions in eForms documents. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.MultilingualStringScalar.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); + super(script, EfxDataType.MultilingualStringScalar.class); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringPath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringPath.java new file mode 100644 index 0000000..7f80507 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/MultilingualStringPath.java @@ -0,0 +1,30 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A multilingual-string-typed path AST node, extending {@link StringPath}. + * + * References an eForms Field that may have multiple language versions. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.MultilingualStringScalar.class) +public class MultilingualStringPath extends StringPath { + + public MultilingualStringPath(final String script) { + super(script, EfxDataType.MultilingualStringScalar.class); + } +} \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NodePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NodePath.java new file mode 100644 index 0000000..da489c1 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NodePath.java @@ -0,0 +1,38 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A path AST node referencing an eForms Node in a structured document. + * + * Unlike eForms Fields, eForms Nodes have no value and are used to set evaluation context. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.NodeScalar.class) +public class NodePath extends ScalarPath.Impl { + + public NodePath(final String script) { + super(script, EfxDataType.NodeScalar.class); + } + + protected NodePath(final String script, Class type) { + super(script, type); + } + + public static NodePath empty() { + return new NodePath(""); + } +} \ 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 index 2be9011..bde9ef0 100644 --- 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 @@ -1,20 +1,35 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A numeric-typed scalar AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a numeric value. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Number.class) -public class NumericExpression extends ScalarExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.NumberScalar.class) +public class NumericExpression extends ScalarExpression.Impl { public NumericExpression(final String script) { - super(script, EfxDataType.Number.class); + super(script, EfxDataType.NumberScalar.class); } - public NumericExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Number.class); + protected NumericExpression(final String script, Class type) { + super(script, type); } public static NumericExpression empty() { diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericLiteral.java new file mode 100644 index 0000000..e96cd43 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A numeric-typed literal AST node representing a constant numeric value known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.NumberScalar.class) +public class NumericLiteral extends NumericExpression implements ScalarLiteral { + + public NumericLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericPath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericPath.java new file mode 100644 index 0000000..4275d79 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/NumericPath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A numeric-typed path AST node referencing an eForms Field that resolves to a numeric value. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.NumberScalar.class) +public class NumericPath extends ScalarPath.Impl { + + public NumericPath(final String script) { + super(script, EfxDataType.NumberScalar.class); + } + + protected NumericPath(final String script, Class type) { + super(script, type); + } + + public static NumericPath empty() { + return new NumericPath(""); + } +} \ 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 index 6cc4bdb..7d5d2d9 100644 --- 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 @@ -1,3 +1,16 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.expressions.scalar; import static java.util.Map.entry; @@ -7,11 +20,18 @@ 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.EfxTypeLattice; import eu.europa.ted.efx.model.types.FieldTypes; -public interface ScalarExpression extends TypedExpression, EfxExpressionType.Scalar { +/** + * A {@link TypedExpression} representing a single value (as opposed to a sequence). + * + * Concrete implementations are typed by primitive: {@link BooleanExpression}, {@link StringExpression}, + * {@link NumericExpression}, {@link DateExpression}, {@link TimeExpression}, {@link DurationExpression}. + * + * @see eu.europa.ted.efx.model.expressions.sequence.SequenceExpression for multi-value expressions + */ +public interface ScalarExpression extends TypedExpression { /** * Maps {@link FieldTypes} to their corresponding {@link ScalarExpression} @@ -37,10 +57,10 @@ public interface ScalarExpression extends TypedExpression, EfxExpressionType.Sca entry(FieldTypes.EMAIL, StringExpression.class)); /** - * Maps {@link EfxDataType} to their corresponding {@link ScalarExpression} + * Maps primitive {@link EfxDataType} to their corresponding {@link ScalarExpression} * class. */ - Map, Class> fromEfxDataType = Map + Map, Class> fromEfxDataType = Map .ofEntries( entry(EfxDataType.String.class, StringExpression.class), // entry(EfxDataType.MultilingualString.class, MultilingualStringExpression.class), // @@ -79,24 +99,19 @@ static T from(TypedExpression source, Class retu * given {@link EfxDataType}. */ static ScalarExpression instantiate(String script, Class efxDataType) { - return Expression.instantiate(script, fromEfxDataType.get(efxDataType)); + return Expression.instantiate(script, fromEfxDataType.get(EfxTypeLattice.toPrimitive(efxDataType))); } /** * A base class for {@link ScalarExpression} implementations. - * + * * @param the EFX data type for the expression. */ - @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); + protected Impl(String script, Class dataType) { + super(script, dataType); } } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarLiteral.java new file mode 100644 index 0000000..9275382 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarLiteral.java @@ -0,0 +1,62 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +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.LiteralExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxTypeLattice; + +/** + * A {@link ScalarExpression} that is also a {@link LiteralExpression}. + * + * Represents a single constant value known at translation time, embedded directly in the + * generated script. + * + * @see eu.europa.ted.efx.model.expressions.sequence.SequenceLiteral for sequences of literals + */ +public interface ScalarLiteral extends ScalarExpression, LiteralExpression { + + /** + * Maps primitive {@link EfxDataType} to concrete scalar literal expression classes. + */ + Map, Class> fromEfxDataType = Map.ofEntries( + entry(EfxDataType.String.class, StringLiteral.class), + entry(EfxDataType.MultilingualString.class, StringLiteral.class), + entry(EfxDataType.Boolean.class, BooleanLiteral.class), + entry(EfxDataType.Number.class, NumericLiteral.class), + entry(EfxDataType.Date.class, DateLiteral.class), + entry(EfxDataType.Time.class, TimeLiteral.class), + entry(EfxDataType.Duration.class, DurationLiteral.class) + ); + + /** + * Creates a {@link ScalarLiteral} for the given {@link EfxDataType}. + * + * @param The type of the returned object. + * @param script The target language script representing the literal value. + * @param efxDataType The {@link EfxDataType} of the literal value. + * + * @return A {@link ScalarLiteral} for the given data type. + */ + @SuppressWarnings("unchecked") + static T instantiate(String script, Class efxDataType) { + Class type = fromEfxDataType.get(EfxTypeLattice.toPrimitive(efxDataType)); + return (T) Expression.instantiate(script, type); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarPath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarPath.java new file mode 100644 index 0000000..d9d7506 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/ScalarPath.java @@ -0,0 +1,140 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +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.PathExpression; +import eu.europa.ted.efx.model.expressions.sequence.SequencePath; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxTypeLattice; +import eu.europa.ted.efx.model.types.FieldTypes; + +/** + * A {@link ScalarExpression} that is also a {@link PathExpression}. + * + * References a single value in a document (non-repeatable from the current context). + * Can convert to {@link SequencePath} via {@link #asSequence()}. + * + * @see SequencePath for paths that may resolve to multiple values + */ +public interface ScalarPath extends ScalarExpression, PathExpression { + + /** + * Maps {@link FieldTypes} to concrete scalar path expression classes. + */ + Map> fromFieldType = Map.ofEntries( + entry(FieldTypes.ID, StringPath.class), // + entry(FieldTypes.ID_REF, StringPath.class), // + entry(FieldTypes.TEXT, StringPath.class), // + entry(FieldTypes.TEXT_MULTILINGUAL, MultilingualStringPath.class), // + entry(FieldTypes.INDICATOR, BooleanPath.class), // + entry(FieldTypes.AMOUNT, NumericPath.class), // + entry(FieldTypes.NUMBER, NumericPath.class), // + entry(FieldTypes.MEASURE, DurationPath.class), // + entry(FieldTypes.CODE, StringPath.class), + entry(FieldTypes.INTERNAL_CODE, StringPath.class), // + entry(FieldTypes.INTEGER, NumericPath.class), // + entry(FieldTypes.DATE, DatePath.class), // + entry(FieldTypes.ZONED_DATE, DatePath.class), // + entry(FieldTypes.TIME, TimePath.class), // + entry(FieldTypes.ZONED_TIME, TimePath.class), // + entry(FieldTypes.URL, StringPath.class), // + entry(FieldTypes.PHONE, StringPath.class), // + entry(FieldTypes.EMAIL, StringPath.class)); + + /** + * Maps primitive {@link EfxDataType} to concrete scalar path expression classes. + */ + Map, Class> fromEfxDataType = Map.ofEntries( + entry(EfxDataType.String.class, StringPath.class), // + entry(EfxDataType.MultilingualString.class, MultilingualStringPath.class), // + entry(EfxDataType.Boolean.class, BooleanPath.class), // + entry(EfxDataType.Number.class, NumericPath.class), // + entry(EfxDataType.Date.class, DatePath.class), // + entry(EfxDataType.Time.class, TimePath.class), // + entry(EfxDataType.Duration.class, DurationPath.class), // + entry(EfxDataType.Node.class, NodePath.class) // + ); + + /** + * Creates a {@link ScalarPath} for the given field type. + * + * @param The type of the returned object. + * @param script The target language script that resolves to a value. + * @param fieldType The type of field that the returned expression points to. + * + * @return A {@link ScalarPath} for the given field type. + */ + @SuppressWarnings("unchecked") + static T instantiate(String script, FieldTypes fieldType) { + Class type = fromFieldType.get(fieldType); + return (T) Expression.instantiate(script, type); + } + + /** + * Creates an empty {@link ScalarPath} for the given field type. + * + * @param The type of the returned object. + * @param fieldType The type of field that the returned expression points to. + * @return An empty {@link ScalarPath} for the given field type. + */ + @SuppressWarnings("unchecked") + static T empty(FieldTypes fieldType) { + Class type = fromFieldType.get(fieldType); + return (T) Expression.instantiate("", type); + } + + /** + * Creates a {@link ScalarPath} for the given {@link EfxDataType}. + * + * @param The type of the returned object. + * @param script The target language script that resolves to a value. + * @param efxDataType The {@link EfxDataType} of the field that the returned expression points to. + * + * @return A {@link ScalarPath} for the given data type. + */ + @SuppressWarnings("unchecked") + static T instantiate(String script, Class efxDataType) { + Class type = fromEfxDataType.get(EfxTypeLattice.toPrimitive(efxDataType)); + return (T) Expression.instantiate(script, type); + } + + /** + * A base class for {@link ScalarPath} implementations. + * + * @param the EFX data type for the expression. + */ + public abstract class Impl extends ScalarExpression.Impl + implements ScalarPath { + + protected Impl(final String script, Class dataType) { + super(script, dataType); + } + + @Override + public ScalarPath asScalar() { + return this; + } + + @Override + public SequencePath asSequence() { + Class sequenceType = EfxTypeLattice.toSequence(getDataType()); + return SequencePath.instantiate(getScript(), sequenceType); + } + } +} 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 index 4690e72..0d56cfa 100644 --- 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 @@ -1,24 +1,35 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A string-typed scalar AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a string value. */ -@EfxDataTypeAssociation(dataType = EfxDataType.String.class) -public class StringExpression extends ScalarExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.StringScalar.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); + super(script, EfxDataType.StringScalar.class); } - public StringExpression(final String script, final Boolean isLiteral, Class type) { - super(script, isLiteral, type); + protected StringExpression(final String script, Class type) { + super(script, type); } public static StringExpression empty() { diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringLiteral.java new file mode 100644 index 0000000..d672755 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A string-typed literal AST node representing a constant string value known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.StringScalar.class) +public class StringLiteral extends StringExpression implements ScalarLiteral { + + public StringLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringPath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringPath.java new file mode 100644 index 0000000..49ac648 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/StringPath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A string-typed path AST node referencing an eForms Field that resolves to a string value. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.StringScalar.class) +public class StringPath extends ScalarPath.Impl { + + public StringPath(final String script) { + super(script, EfxDataType.StringScalar.class); + } + + protected StringPath(final String script, Class type) { + super(script, type); + } + + public static StringPath empty() { + return new StringPath(""); + } +} \ 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 index 6916d7b..3cbb2d3 100644 --- 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 @@ -1,20 +1,35 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A time-typed scalar AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a time value. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Time.class) -public class TimeExpression extends ScalarExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.TimeScalar.class) +public class TimeExpression extends ScalarExpression.Impl { public TimeExpression(final String script) { - super(script, EfxDataType.Time.class); + super(script, EfxDataType.TimeScalar.class); } - public TimeExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Time.class); + protected TimeExpression(final String script, Class type) { + super(script, type); } public static TimeExpression empty() { diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeLiteral.java new file mode 100644 index 0000000..5371be4 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimeLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A time-typed literal AST node representing a constant time value known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.TimeScalar.class) +public class TimeLiteral extends TimeExpression implements ScalarLiteral { + + public TimeLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimePath.java new file mode 100644 index 0000000..74599bc --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/scalar/TimePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.scalar; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A time-typed path AST node referencing an eForms Field that resolves to a time value. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.TimeScalar.class) +public class TimePath extends ScalarPath.Impl { + + public TimePath(final String script) { + super(script, EfxDataType.TimeScalar.class); + } + + protected TimePath(final String script, Class type) { + super(script, type); + } + + public static TimePath empty() { + return new TimePath(""); + } +} \ 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 index 66bce7b..c85e5db 100644 --- 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 @@ -1,19 +1,38 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A boolean-typed sequence AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a sequence of boolean values. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Boolean.class) -public class BooleanSequenceExpression extends SequenceExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.BooleanSequence.class) +public class BooleanSequenceExpression extends SequenceExpression.Impl { public BooleanSequenceExpression(final String script) { - super(script, EfxDataType.Boolean.class); + super(script, EfxDataType.BooleanSequence.class); + } + + protected BooleanSequenceExpression(final String script, Class type) { + super(script, type); } - public BooleanSequenceExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral,EfxDataType.Boolean.class); + public static BooleanSequenceExpression empty() { + return new BooleanSequenceExpression(""); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceLiteral.java new file mode 100644 index 0000000..ca510ae --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequenceLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A boolean-typed sequence literal AST node representing constant boolean values known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.BooleanSequence.class) +public class BooleanSequenceLiteral extends BooleanSequenceExpression implements SequenceLiteral { + + public BooleanSequenceLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequencePath.java new file mode 100644 index 0000000..5115b8d --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/BooleanSequencePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A boolean-typed sequence path AST node referencing an eForms Field that resolves to multiple boolean values. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.BooleanSequence.class) +public class BooleanSequencePath extends SequencePath.Impl { + + public BooleanSequencePath(final String script) { + super(script, EfxDataType.BooleanSequence.class); + } + + protected BooleanSequencePath(final String script, Class type) { + super(script, type); + } + + public static BooleanSequencePath empty() { + return new BooleanSequencePath(""); + } +} 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 index b9be995..7fa43f5 100644 --- 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 @@ -1,19 +1,38 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A date-typed sequence AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a sequence of date values. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Date.class) -public class DateSequenceExpression extends SequenceExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.DateSequence.class) +public class DateSequenceExpression extends SequenceExpression.Impl { public DateSequenceExpression(final String script) { - super(script, EfxDataType.Date.class); + super(script, EfxDataType.DateSequence.class); + } + + protected DateSequenceExpression(final String script, Class type) { + super(script, type); } - public DateSequenceExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Date.class); + public static DateSequenceExpression empty() { + return new DateSequenceExpression(""); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceLiteral.java new file mode 100644 index 0000000..6271764 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequenceLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A date-typed sequence literal AST node representing constant date values known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DateSequence.class) +public class DateSequenceLiteral extends DateSequenceExpression implements SequenceLiteral { + + public DateSequenceLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequencePath.java new file mode 100644 index 0000000..cbd4880 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DateSequencePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A date-typed sequence path AST node referencing an eForms Field that resolves to multiple date values. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DateSequence.class) +public class DateSequencePath extends SequencePath.Impl { + + public DateSequencePath(final String script) { + super(script, EfxDataType.DateSequence.class); + } + + protected DateSequencePath(final String script, Class type) { + super(script, type); + } + + public static DateSequencePath empty() { + return new DateSequencePath(""); + } +} 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 index 94f6318..7239fd2 100644 --- 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 @@ -1,19 +1,38 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A duration-typed sequence AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a sequence of duration values. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Duration.class) -public class DurationSequenceExpression extends SequenceExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.DurationSequence.class) +public class DurationSequenceExpression extends SequenceExpression.Impl { public DurationSequenceExpression(final String script) { - super(script, EfxDataType.Duration.class); + super(script, EfxDataType.DurationSequence.class); + } + + protected DurationSequenceExpression(final String script, Class type) { + super(script, type); } - public DurationSequenceExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Duration.class); + public static DurationSequenceExpression empty() { + return new DurationSequenceExpression(""); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceLiteral.java new file mode 100644 index 0000000..33adc0b --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequenceLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A duration-typed sequence literal AST node representing constant duration values known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DurationSequence.class) +public class DurationSequenceLiteral extends DurationSequenceExpression implements SequenceLiteral { + + public DurationSequenceLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequencePath.java new file mode 100644 index 0000000..e163d8f --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/DurationSequencePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A duration-typed sequence path AST node referencing an eForms Field that resolves to multiple duration values. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.DurationSequence.class) +public class DurationSequencePath extends SequencePath.Impl { + + public DurationSequencePath(final String script) { + super(script, EfxDataType.DurationSequence.class); + } + + protected DurationSequencePath(final String script, Class type) { + super(script, type); + } + + public static DurationSequencePath empty() { + return new DurationSequencePath(""); + } +} 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 index 1aed6a5..2519257 100644 --- 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 @@ -1,12 +1,30 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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) +/** + * A multilingual-string-typed sequence AST node, extending {@link StringSequenceExpression}. + * + * Represents sequences of strings that may have multiple language versions in eForms documents. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.MultilingualStringSequence.class) public class MultilingualStringSequenceExpression extends StringSequenceExpression { public MultilingualStringSequenceExpression(final String script) { - super(script, false, EfxDataType.MultilingualString.class); + super(script, EfxDataType.MultilingualStringSequence.class); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequencePath.java new file mode 100644 index 0000000..e760512 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/MultilingualStringSequencePath.java @@ -0,0 +1,30 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A multilingual-string-typed sequence path AST node, extending {@link StringSequencePath}. + * + * References an eForms Field that may have multiple language versions, resolving to multiple values. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.MultilingualStringSequence.class) +public class MultilingualStringSequencePath extends StringSequencePath { + + public MultilingualStringSequencePath(final String script) { + super(script, EfxDataType.MultilingualStringSequence.class); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NodeSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NodeSequencePath.java new file mode 100644 index 0000000..9a81b82 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NodeSequencePath.java @@ -0,0 +1,38 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A sequence path AST node referencing multiple eForms Nodes in a structured document. + * + * Unlike eForms Fields, eForms Nodes have no value and are used to set evaluation context. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.NodeSequence.class) +public class NodeSequencePath extends SequencePath.Impl { + + public NodeSequencePath(final String script) { + super(script, EfxDataType.NodeSequence.class); + } + + protected NodeSequencePath(final String script, Class type) { + super(script, type); + } + + public static NodeSequencePath empty() { + return new NodeSequencePath(""); + } +} 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 index 9844e8a..a9e935f 100644 --- 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 @@ -1,19 +1,38 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A numeric-typed sequence AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a sequence of numeric values. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Number.class) -public class NumericSequenceExpression extends SequenceExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.NumberSequence.class) +public class NumericSequenceExpression extends SequenceExpression.Impl { public NumericSequenceExpression(final String script) { - super(script, EfxDataType.Number.class); + super(script, EfxDataType.NumberSequence.class); + } + + protected NumericSequenceExpression(final String script, Class type) { + super(script, type); } - public NumericSequenceExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Number.class); + public static NumericSequenceExpression empty() { + return new NumericSequenceExpression(""); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceLiteral.java new file mode 100644 index 0000000..dffffb9 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequenceLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A numeric-typed sequence literal AST node representing constant numeric values known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.NumberSequence.class) +public class NumericSequenceLiteral extends NumericSequenceExpression implements SequenceLiteral { + + public NumericSequenceLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequencePath.java new file mode 100644 index 0000000..69707e1 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/NumericSequencePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A numeric-typed sequence path AST node referencing an eForms Field that resolves to multiple numeric values. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.NumberSequence.class) +public class NumericSequencePath extends SequencePath.Impl { + + public NumericSequencePath(final String script) { + super(script, EfxDataType.NumberSequence.class); + } + + protected NumericSequencePath(final String script, Class type) { + super(script, type); + } + + public static NumericSequencePath empty() { + return new NumericSequencePath(""); + } +} 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 index 7908c68..7adcc7e 100644 --- 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 @@ -1,3 +1,16 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.expressions.sequence; import static java.util.Map.entry; @@ -7,11 +20,19 @@ 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.EfxTypeLattice; import eu.europa.ted.efx.model.types.FieldTypes; -public interface SequenceExpression extends TypedExpression, EfxExpressionType.Sequence { +/** + * A {@link TypedExpression} representing multiple values (as opposed to a single value). + * + * Concrete implementations are typed by primitive: {@link BooleanSequenceExpression}, + * {@link StringSequenceExpression}, {@link NumericSequenceExpression}, {@link DateSequenceExpression}, + * {@link TimeSequenceExpression}, {@link DurationSequenceExpression}. + * + * @see eu.europa.ted.efx.model.expressions.scalar.ScalarExpression for single-value expressions + */ +public interface SequenceExpression extends TypedExpression { /** * Maps {@link FieldTypes} to the corresponding {@link SequenceExpression}. @@ -37,9 +58,9 @@ public interface SequenceExpression extends TypedExpression, EfxExpressionType.S entry(FieldTypes.EMAIL, StringSequenceExpression.class)); /** - * Maps {@link EfxDataType} to the corresponding {@link SequenceExpression}. + * Maps primitive {@link EfxDataType} to the corresponding {@link SequenceExpression}. */ - Map, Class> fromEfxDataType = Map + Map, Class> fromEfxDataType = Map .ofEntries( entry(EfxDataType.String.class, StringSequenceExpression.class), // entry(EfxDataType.MultilingualString.class, MultilingualStringSequenceExpression.class), // @@ -77,24 +98,19 @@ static T from(TypedExpression source, Class efxDataType) { - return Expression.instantiate(script, fromEfxDataType.get(efxDataType)); + return Expression.instantiate(script, fromEfxDataType.get(EfxTypeLattice.toPrimitive(efxDataType))); } /** * A base class for {@link SequenceExpression} implementations. - * + * * @param the data type of the sequence expression result */ - @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); + super(script, dataType); } } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceLiteral.java new file mode 100644 index 0000000..47c39f6 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequenceLiteral.java @@ -0,0 +1,63 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +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.LiteralExpression; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxTypeLattice; + +/** + * A {@link SequenceExpression} that is also a {@link LiteralExpression}. + * + * Represents a sequence of constant values known at translation time, embedded directly in the + * generated script. + * + * @see eu.europa.ted.efx.model.expressions.scalar.ScalarLiteral for single-value literals + */ +public interface SequenceLiteral extends SequenceExpression, LiteralExpression { + + /** + * Maps primitive {@link EfxDataType} to concrete sequence literal expression classes. + */ + Map, Class> fromEfxDataType = Map.ofEntries( + entry(EfxDataType.String.class, StringSequenceLiteral.class), + entry(EfxDataType.MultilingualString.class, StringSequenceLiteral.class), + entry(EfxDataType.Boolean.class, BooleanSequenceLiteral.class), + entry(EfxDataType.Number.class, NumericSequenceLiteral.class), + entry(EfxDataType.Date.class, DateSequenceLiteral.class), + entry(EfxDataType.Time.class, TimeSequenceLiteral.class), + entry(EfxDataType.Duration.class, DurationSequenceLiteral.class) + ); + + /** + * Creates a {@link SequenceLiteral} for the given {@link EfxDataType}. + * + * @param The type of the returned object. + * @param script The target language script representing the literal sequence. + * @param efxDataType The {@link EfxDataType} of the literal sequence values. + * + * @return A {@link SequenceLiteral} for the given data type. + */ + @SuppressWarnings("unchecked") + static T instantiate(String script, Class efxDataType) { + Class primitiveType = EfxTypeLattice.toPrimitive(efxDataType); + Class type = fromEfxDataType.get(primitiveType); + return (T) Expression.instantiate(script, type); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequencePath.java new file mode 100644 index 0000000..3c496be --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/SequencePath.java @@ -0,0 +1,130 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +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.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.ScalarPath; +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxTypeLattice; +import eu.europa.ted.efx.model.types.FieldTypes; + +/** + * A {@link SequenceExpression} that is also a {@link PathExpression}. + * + * References multiple values in a document (repeatable from the current context). + * Can convert to {@link ScalarPath} via {@link #asScalar()}. + * + * @see ScalarPath for paths that resolve to a single value + */ +public interface SequencePath extends SequenceExpression, PathExpression { + + /** + * Maps {@link FieldTypes} to concrete sequence path expression classes. + */ + Map> fromFieldType = Map.ofEntries( + entry(FieldTypes.ID, StringSequencePath.class), // + entry(FieldTypes.ID_REF, StringSequencePath.class), // + entry(FieldTypes.TEXT, StringSequencePath.class), // + entry(FieldTypes.TEXT_MULTILINGUAL, MultilingualStringSequencePath.class), // + entry(FieldTypes.INDICATOR, BooleanSequencePath.class), // + entry(FieldTypes.AMOUNT, NumericSequencePath.class), // + entry(FieldTypes.NUMBER, NumericSequencePath.class), // + entry(FieldTypes.MEASURE, DurationSequencePath.class), // + entry(FieldTypes.CODE, StringSequencePath.class), + entry(FieldTypes.INTERNAL_CODE, StringSequencePath.class), // + entry(FieldTypes.INTEGER, NumericSequencePath.class), // + entry(FieldTypes.DATE, DateSequencePath.class), // + entry(FieldTypes.ZONED_DATE, DateSequencePath.class), // + entry(FieldTypes.TIME, TimeSequencePath.class), // + entry(FieldTypes.ZONED_TIME, TimeSequencePath.class), // + entry(FieldTypes.URL, StringSequencePath.class), // + entry(FieldTypes.PHONE, StringSequencePath.class), // + entry(FieldTypes.EMAIL, StringSequencePath.class)); + + /** + * Maps primitive {@link EfxDataType} to concrete sequence path expression classes. + */ + Map, Class> fromEfxDataType = Map.ofEntries( + entry(EfxDataType.String.class, StringSequencePath.class), // + entry(EfxDataType.MultilingualString.class, MultilingualStringSequencePath.class), // + entry(EfxDataType.Boolean.class, BooleanSequencePath.class), // + entry(EfxDataType.Number.class, NumericSequencePath.class), // + entry(EfxDataType.Date.class, DateSequencePath.class), // + entry(EfxDataType.Time.class, TimeSequencePath.class), // + entry(EfxDataType.Duration.class, DurationSequencePath.class), // + entry(EfxDataType.Node.class, NodeSequencePath.class) // + ); + + /** + * Creates a {@link SequencePath} for the given field type. + * Use this when the field is repeatable from the current context. + * + * @param The type of the returned object. + * @param script The target language script that resolves to a sequence. + * @param fieldType The type of field that the returned expression points to. + * + * @return A {@link SequencePath} for the given field type. + */ + @SuppressWarnings("unchecked") + static T instantiate(String script, FieldTypes fieldType) { + Class type = fromFieldType.get(fieldType); + return (T) Expression.instantiate(script, type); + } + + /** + * Creates a {@link SequencePath} for the given {@link EfxDataType}. + * Use this when the field is repeatable from the current context. + * + * @param The type of the returned object. + * @param script The target language script that resolves to a sequence. + * @param efxDataType The {@link EfxDataType} of the field that the returned expression points to. + * + * @return A {@link SequencePath} for the given data type. + */ + @SuppressWarnings("unchecked") + static T instantiate(String script, Class efxDataType) { + Class primitiveType = EfxTypeLattice.toPrimitive(efxDataType); + Class type = fromEfxDataType.get(primitiveType); + return (T) Expression.instantiate(script, type); + } + + /** + * A base class for {@link SequencePath} implementations. + * + * @param the EFX data type for the expression. + */ + public abstract class Impl extends SequenceExpression.Impl + implements SequencePath { + + protected Impl(final String script, Class dataType) { + super(script, dataType); + } + + @Override + public ScalarPath asScalar() { + Class scalarType = EfxTypeLattice.toScalar(getDataType()); + return ScalarPath.instantiate(getScript(), scalarType); + } + + @Override + public SequencePath asSequence() { + return this; + } + } +} 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 index 176d9f9..fe9fc6b 100644 --- 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 @@ -1,23 +1,38 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A string-typed sequence AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a sequence of string values. */ -@EfxDataTypeAssociation(dataType = EfxDataType.String.class) -public class StringSequenceExpression extends SequenceExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.StringSequence.class) +public class StringSequenceExpression extends SequenceExpression.Impl { public StringSequenceExpression(final String script) { - this(script, false); + super(script, EfxDataType.StringSequence.class); } - public StringSequenceExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.String.class); + protected StringSequenceExpression(final String script, Class type) { + super(script, type); } - protected StringSequenceExpression(final String script, final Boolean isLiteral, Class type) { - super(script, isLiteral, type); + public static StringSequenceExpression empty() { + return new StringSequenceExpression(""); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceLiteral.java new file mode 100644 index 0000000..5778bd4 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequenceLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A string-typed sequence literal AST node representing constant string values known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.StringSequence.class) +public class StringSequenceLiteral extends StringSequenceExpression implements SequenceLiteral { + + public StringSequenceLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequencePath.java new file mode 100644 index 0000000..dffa4bc --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/StringSequencePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A string-typed sequence path AST node referencing an eForms Field that resolves to multiple string values. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.StringSequence.class) +public class StringSequencePath extends SequencePath.Impl { + + public StringSequencePath(final String script) { + super(script, EfxDataType.StringSequence.class); + } + + protected StringSequencePath(final String script, Class type) { + super(script, type); + } + + public static StringSequencePath empty() { + return new StringSequencePath(""); + } +} 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 index c8aebc5..8d7041c 100644 --- 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 @@ -1,18 +1,37 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ 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. + * A time-typed sequence AST node in the expression tree built during EFX translation. + * + * Wraps a target-language script fragment that evaluates to a sequence of time values. */ -@EfxDataTypeAssociation(dataType = EfxDataType.Time.class) -public class TimeSequenceExpression extends SequenceExpression.Impl { +@EfxDataTypeAssociation(dataType = EfxDataType.TimeSequence.class) +public class TimeSequenceExpression extends SequenceExpression.Impl { public TimeSequenceExpression(final String script) { - super(script, EfxDataType.Time.class); + super(script, EfxDataType.TimeSequence.class); + } + + protected TimeSequenceExpression(final String script, Class type) { + super(script, type); } - public TimeSequenceExpression(final String script, final Boolean isLiteral) { - super(script, isLiteral, EfxDataType.Time.class); + public static TimeSequenceExpression empty() { + return new TimeSequenceExpression(""); } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceLiteral.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceLiteral.java new file mode 100644 index 0000000..7a9f59b --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequenceLiteral.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A time-typed sequence literal AST node representing constant time values known at translation time. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.TimeSequence.class) +public class TimeSequenceLiteral extends TimeSequenceExpression implements SequenceLiteral { + + public TimeSequenceLiteral(final String script) { + super(script); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequencePath.java b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequencePath.java new file mode 100644 index 0000000..1422787 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/expressions/sequence/TimeSequencePath.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.expressions.sequence; + +import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxDataTypeAssociation; + +/** + * A time-typed sequence path AST node referencing an eForms Field that resolves to multiple time values. + */ +@EfxDataTypeAssociation(dataType = EfxDataType.TimeSequence.class) +public class TimeSequencePath extends SequencePath.Impl { + + public TimeSequencePath(final String script) { + super(script, EfxDataType.TimeSequence.class); + } + + protected TimeSequencePath(final String script, Class type) { + super(script, type); + } + + public static TimeSequencePath empty() { + return new TimeSequencePath(""); + } +} diff --git a/src/main/java/eu/europa/ted/efx/model/rules/NoticeSubtypeRange.java b/src/main/java/eu/europa/ted/efx/model/rules/NoticeSubtypeRange.java index 5fd9ccb..bbfd56c 100644 --- a/src/main/java/eu/europa/ted/efx/model/rules/NoticeSubtypeRange.java +++ b/src/main/java/eu/europa/ted/efx/model/rules/NoticeSubtypeRange.java @@ -16,6 +16,8 @@ import java.util.ArrayList; import java.util.List; +import eu.europa.ted.efx.exceptions.InvalidUsageException; +import eu.europa.ted.efx.exceptions.SymbolResolutionException; import eu.europa.ted.efx.model.ParsedEntity; public class NoticeSubtypeRange implements ParsedEntity, Iterable { @@ -47,9 +49,7 @@ public NoticeSubtypeRange(String rangeString, List validNoticeSubtypes) case 1: { int idx = validNoticeSubtypes.indexOf(parts[0]); if (idx < 0) { - throw new IllegalArgumentException( - String.format("Invalid notice type ID '%s' in compressed list '%s'", - parts[0], rangeString)); + throw SymbolResolutionException.unknownNoticeSubtype(parts[0], rangeString); } noticeSubtypes.add(validNoticeSubtypes.get(idx)); break; @@ -57,20 +57,14 @@ public NoticeSubtypeRange(String rangeString, List validNoticeSubtypes) case 2: { int startIdx = validNoticeSubtypes.indexOf(parts[0]); if (startIdx < 0) { - throw new IllegalArgumentException( - String.format("Invalid notice subtype '%s' in range '%s-%s'.", - parts[0], parts[0], parts[1])); + throw SymbolResolutionException.unknownNoticeSubtype(parts[0], parts[0] + "-" + parts[1]); } int endIdx = validNoticeSubtypes.indexOf(parts[1]); if (endIdx < 0) { - throw new IllegalArgumentException( - String.format("Invalid notice subtype '%s' in range '%s-%s'.", - parts[1], parts[0], parts[1])); + throw SymbolResolutionException.unknownNoticeSubtype(parts[1], parts[0] + "-" + parts[1]); } if (startIdx > endIdx) { - throw new IllegalArgumentException( - String.format("Notice subtype range '%s-%s' is not in ascending order.", parts[0], - parts[1])); + throw InvalidUsageException.invalidNoticeSubtypeRangeOrder(parts[0], parts[1]); } for (int i = startIdx; i <= endIdx; i++) { @@ -79,9 +73,7 @@ public NoticeSubtypeRange(String rangeString, List validNoticeSubtypes) break; } default: - throw new IllegalArgumentException( - String.format("Invalid notice subtype token '%s'.", - item)); + throw InvalidUsageException.invalidNoticeSubtypeToken(item); } } 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 index 32cc5a1..3a7f954 100644 --- a/src/main/java/eu/europa/ted/efx/model/types/EfxDataType.java +++ b/src/main/java/eu/europa/ted/efx/model/types/EfxDataType.java @@ -1,16 +1,82 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.types; +/** + * Marker interfaces defining the EFX type system as a 2D lattice. + * + * The type system has two dimensions: + * - Primitive type (String, Boolean, Number, Date, Time, Duration, Node) + * - Cardinality (Scalar for single values, Sequence for multiple values) + * + * Concrete types combine both dimensions (e.g., StringScalar, BooleanSequence). + * Expression classes use {@link EfxDataTypeAssociation} to declare their type. + * + * @see EfxTypeLattice for type conversion operations + */ public interface EfxDataType { - Class ANY = EfxDataType.class; - Class UNDEFINED = Undefined.class; + Class VOID = EfxDataType.Void.class; - interface Boolean extends EfxDataType {} - interface String extends EfxDataType {} + /** + * Cardinality markers for EFX types (scalar vs sequence). + * Nested within EfxDataType but NOT extending it, to prevent + * accidental misuse where primitive types are expected. + */ + interface Cardinality { + /** Marker for single-value types */ + interface Scalar extends Cardinality {} + /** Marker for multi-value types */ + interface Sequence extends Cardinality {} + } + + /** + * Type category markers that extend EfxDataType. + * Used for type-safe return types in EfxTypeLattice. + */ + interface Primitive extends EfxDataType {} + interface ConcreteScalar extends EfxDataType {} + interface ConcreteSequence extends EfxDataType {} + + // EFX primitive types (extend Primitive marker) + interface Boolean extends Primitive {} + interface String extends Primitive {} interface MultilingualString extends String {} - interface Number extends EfxDataType {} - interface Date extends EfxDataType {} - interface Duration extends EfxDataType {} - interface Time extends EfxDataType {} - interface Node extends EfxDataType {} - interface Undefined extends EfxDataType {} + interface Number extends Primitive {} + interface Date extends Primitive {} + interface Time extends Primitive {} + interface Duration extends Primitive {} + interface Node extends Primitive {} + /** Unit type for templates/procedures that produce output but don't return a value. */ + interface Void extends EfxDataType {} + + // Concrete scalar types (primitive + Cardinality.Scalar + ConcreteScalar) + interface BooleanScalar extends Boolean, Cardinality.Scalar, ConcreteScalar {} + interface StringScalar extends String, Cardinality.Scalar, ConcreteScalar {} + interface MultilingualStringScalar extends StringScalar, MultilingualString {} + interface NumberScalar extends Number, Cardinality.Scalar, ConcreteScalar {} + interface DateScalar extends Date, Cardinality.Scalar, ConcreteScalar {} + interface TimeScalar extends Time, Cardinality.Scalar, ConcreteScalar {} + interface DurationScalar extends Duration, Cardinality.Scalar, ConcreteScalar {} + interface NodeScalar extends Node, Cardinality.Scalar, ConcreteScalar {} + + // Concrete sequence types (primitive + Cardinality.Sequence + ConcreteSequence) + interface BooleanSequence extends Boolean, Cardinality.Sequence, ConcreteSequence {} + interface StringSequence extends String, Cardinality.Sequence, ConcreteSequence {} + interface MultilingualStringSequence extends StringSequence, MultilingualString {} + interface NumberSequence extends Number, Cardinality.Sequence, ConcreteSequence {} + interface DateSequence extends Date, Cardinality.Sequence, ConcreteSequence {} + interface TimeSequence extends Time, Cardinality.Sequence, ConcreteSequence {} + interface DurationSequence extends Duration, Cardinality.Sequence, ConcreteSequence {} + interface NodeSequence extends Node, Cardinality.Sequence, ConcreteSequence {} } 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 index 2fd4fd7..e4c7e20 100644 --- a/src/main/java/eu/europa/ted/efx/model/types/EfxDataTypeAssociation.java +++ b/src/main/java/eu/europa/ted/efx/model/types/EfxDataTypeAssociation.java @@ -6,6 +6,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Runtime annotation linking an expression class to its {@link EfxDataType}. + * + * Applied to concrete expression classes to declare their type in the EFX type system, + * enabling type introspection and type-safe operations during translation. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited 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 deleted file mode 100644 index 1f2a7f2..0000000 --- a/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionType.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index a6496c6..0000000 --- a/src/main/java/eu/europa/ted/efx/model/types/EfxExpressionTypeAssociation.java +++ /dev/null @@ -1,14 +0,0 @@ -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/EfxTypeLattice.java b/src/main/java/eu/europa/ted/efx/model/types/EfxTypeLattice.java new file mode 100644 index 0000000..d8e6a10 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/model/types/EfxTypeLattice.java @@ -0,0 +1,182 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.types; + +import java.util.Arrays; +import java.util.List; + +import eu.europa.ted.efx.exceptions.ConsistencyCheckException; + +/** + * Provides type conversion operations for the EFX type system. + * + * The EFX type system is a two-dimensional lattice with primitive types + * (String, Boolean, Number, Date, Time, Duration, Node) and cardinalities + * (Scalar for single values, Sequence for multiple values). + * + * @see #toPrimitive(Class) Get the primitive type (e.g., String from StringScalar) + * @see #toScalar(Class) Get the scalar variant (e.g., StringScalar from String) + * @see #toSequence(Class) Get the sequence variant (e.g., StringSequence from String) + */ +public final class EfxTypeLattice { + + private EfxTypeLattice() { + // Utility class - prevent instantiation + } + + /** + * Groups a primitive type with its scalar and sequence variants. + */ + private static final class TypeVariants { + final Class primitive; + final Class scalar; + final Class sequence; + + TypeVariants(Class primitive, + Class scalar, + Class sequence) { + this.primitive = primitive; + this.scalar = scalar; + this.sequence = sequence; + } + } + + /** + * Registered type variants. Order matters: specific types must come before + * their supertypes (e.g., MultilingualString before String) for correct + * subtype matching. + */ + private static final List TYPE_VARIANTS = Arrays.asList( + new TypeVariants(EfxDataType.MultilingualString.class, + EfxDataType.MultilingualStringScalar.class, + EfxDataType.MultilingualStringSequence.class), + new TypeVariants(EfxDataType.String.class, + EfxDataType.StringScalar.class, + EfxDataType.StringSequence.class), + new TypeVariants(EfxDataType.Number.class, + EfxDataType.NumberScalar.class, + EfxDataType.NumberSequence.class), + new TypeVariants(EfxDataType.Boolean.class, + EfxDataType.BooleanScalar.class, + EfxDataType.BooleanSequence.class), + new TypeVariants(EfxDataType.Date.class, + EfxDataType.DateScalar.class, + EfxDataType.DateSequence.class), + new TypeVariants(EfxDataType.Time.class, + EfxDataType.TimeScalar.class, + EfxDataType.TimeSequence.class), + new TypeVariants(EfxDataType.Duration.class, + EfxDataType.DurationScalar.class, + EfxDataType.DurationSequence.class), + new TypeVariants(EfxDataType.Node.class, + EfxDataType.NodeScalar.class, + EfxDataType.NodeSequence.class) + ); + + /** + * Returns the primitive type for the given EFX data type. + * + * @param type any EFX data type (primitive, scalar, or sequence) + * @return the corresponding primitive type + * @throws IllegalArgumentException if type is null + * @throws ConsistencyCheckException if type is not registered in TYPE_VARIANTS + */ + public static Class toPrimitive(Class type) { + if (type == null) { + throw new IllegalArgumentException("type cannot be null"); + } + for (TypeVariants variants : TYPE_VARIANTS) { + if (variants.primitive.isAssignableFrom(type)) { + return variants.primitive; + } + } + throw ConsistencyCheckException.typeNotRegistered(type); + } + + /** + * Returns the scalar variant for the given EFX data type. + * + * @param type any EFX data type (primitive, scalar, or sequence) + * @return the corresponding scalar type + * @throws IllegalArgumentException if type is null + * @throws ConsistencyCheckException if type is not registered in TYPE_VARIANTS + */ + public static Class toScalar(Class type) { + if (type == null) { + throw new IllegalArgumentException("type cannot be null"); + } + for (TypeVariants variants : TYPE_VARIANTS) { + if (variants.primitive.isAssignableFrom(type)) { + return variants.scalar; + } + } + throw ConsistencyCheckException.typeNotRegistered(type); + } + + /** + * Returns the sequence variant for the given EFX data type. + * + * @param type any EFX data type (primitive, scalar, or sequence) + * @return the corresponding sequence type + * @throws IllegalArgumentException if type is null + * @throws ConsistencyCheckException if type is not registered in TYPE_VARIANTS + */ + public static Class toSequence(Class type) { + if (type == null) { + throw new IllegalArgumentException("type cannot be null"); + } + for (TypeVariants variants : TYPE_VARIANTS) { + if (variants.primitive.isAssignableFrom(type)) { + return variants.sequence; + } + } + throw ConsistencyCheckException.typeNotRegistered(type); + } + + /** + * Returns true if the given type has scalar cardinality. + * + * @param type the EFX data type to check + * @return true if the type implements {@link EfxDataType.Cardinality.Scalar} + */ + public static boolean isScalar(Class type) { + return type != null && EfxDataType.Cardinality.Scalar.class.isAssignableFrom(type); + } + + /** + * Returns true if the given type has sequence cardinality. + * + * @param type the EFX data type to check + * @return true if the type implements {@link EfxDataType.Cardinality.Sequence} + */ + public static boolean isSequence(Class type) { + return type != null && EfxDataType.Cardinality.Sequence.class.isAssignableFrom(type); + } + + /** + * Returns true if the given type is registered in TYPE_VARIANTS. + * Used by tests to verify all concrete types are registered. + * + * @param type the type to check + * @return true if the type is registered + */ + public static boolean isRegistered(Class type) { + for (TypeVariants variants : TYPE_VARIANTS) { + if (variants.scalar.equals(type) || variants.sequence.equals(type)) { + return true; + } + } + return false; + } +} 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 index 52e47c8..c999f8c 100644 --- a/src/main/java/eu/europa/ted/efx/model/types/FieldTypes.java +++ b/src/main/java/eu/europa/ted/efx/model/types/FieldTypes.java @@ -3,6 +3,13 @@ import java.util.HashMap; import java.util.Map; +/** + * Enumeration of eForms SDK field types. + * + * Maps the field type strings from the SDK (e.g., "text", "indicator", "date") + * to enum constants. Expression classes use this to determine the appropriate + * EFX type for a given field. + */ public enum FieldTypes { ID("id"), // ID_REF("id-ref"), // diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Dictionary.java b/src/main/java/eu/europa/ted/efx/model/variables/Dictionary.java index bc026c0..95263fb 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/Dictionary.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/Dictionary.java @@ -1,9 +1,28 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.variables; +import eu.europa.ted.efx.model.expressions.PathExpression; import eu.europa.ted.efx.model.expressions.TypedExpression; -import eu.europa.ted.efx.model.expressions.path.PathExpression; import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +/** + * A dictionary variable declared in EFX source code. + * + * Maps string keys to values retrieved from a path expression. The dictionary stores the key + * expression, the path expression for values, and the expression type of the path. + */ public class Dictionary extends Identifier { public final StringExpression keyExpression; public final PathExpression pathExpression; diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Function.java b/src/main/java/eu/europa/ted/efx/model/variables/Function.java index d58d87b..c73a475 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/Function.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/Function.java @@ -1,15 +1,32 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.variables; import eu.europa.ted.efx.model.expressions.TypedExpression; -import eu.europa.ted.efx.model.types.EfxDataType; +/** + * A user-defined function declared in EFX source code. + * + * Extends {@link Parametrised} to add the function body expression, whose type determines + * the function's return type. + */ public class Function extends Parametrised { public final TypedExpression expression; - public Function(String name, Class returnType, ParsedParameters parameters, - TypedExpression expression) { - super(name, returnType, parameters); + public Function(String name, ParsedParameters parameters, TypedExpression expression) { + super(name, expression.getDataType(), parameters); this.expression = expression; } diff --git a/src/main/java/eu/europa/ted/efx/model/variables/ParsedParameters.java b/src/main/java/eu/europa/ted/efx/model/variables/ParsedParameters.java index f4cb323..cf785cb 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/ParsedParameters.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/ParsedParameters.java @@ -11,6 +11,12 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * An ordered collection of parsed parameters for a function or template. + * + * Preserves parameter order (important for positional arguments) and provides conversions + * to Set and Map representations. + */ public class ParsedParameters extends LinkedList implements ParsedEntity { public ParsedParameters() { diff --git a/src/main/java/eu/europa/ted/efx/model/variables/StrictArguments.java b/src/main/java/eu/europa/ted/efx/model/variables/StrictArguments.java index f68cb4b..092d534 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/StrictArguments.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/StrictArguments.java @@ -1,8 +1,22 @@ +/* + * Copyright 2025 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.variables; import java.util.Objects; import eu.europa.ted.efx.exceptions.InvalidArgumentException; +import eu.europa.ted.efx.interfaces.TypeChecker; import eu.europa.ted.efx.model.expressions.TypedExpression; public class StrictArguments extends ParsedArguments { @@ -22,7 +36,7 @@ public boolean addArgument(TypedExpression argument) { var actualType = argument.getClass(); var expectedType = this.identifier.parameters.get(position).getParameterType(); - if (!TypedExpression.canConvert(actualType, expectedType)) { + if (!TypeChecker.V2.canConvert(actualType, expectedType)) { throw InvalidArgumentException.argumentTypeMismatch(position, this.identifier, expectedType, actualType); } return this.add(new ParsedArgument(this.identifier.parameters.get(position), argument)); diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Template.java b/src/main/java/eu/europa/ted/efx/model/variables/Template.java index da95d45..ea662a4 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/Template.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/Template.java @@ -5,6 +5,6 @@ public class Template extends Parametrised { public Template(String name, ParsedParameters parameters) { - super(name, EfxDataType.UNDEFINED, parameters); + super(name, EfxDataType.VOID, parameters); } } \ 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 index 882841f..37128ee 100644 --- a/src/main/java/eu/europa/ted/efx/model/variables/Variable.java +++ b/src/main/java/eu/europa/ted/efx/model/variables/Variable.java @@ -1,16 +1,40 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.model.variables; import eu.europa.ted.efx.model.expressions.DeclarationExpression; import eu.europa.ted.efx.model.expressions.Expression; import eu.europa.ted.efx.model.expressions.TypedExpression; +import eu.europa.ted.efx.model.types.EfxTypeLattice; +/** + * A variable declared in EFX source code. + * + * Tracks three expressions: the initialization expression (determines the variable's type), + * the reference expression (target-language code for accessing the variable), and the + * declaration expression (target-language code for declaring the variable). + * + * For iterator variables, the initialization expression may be a sequence while the reference + * expression is scalar (since the iterator yields one element at a time). + */ public class Variable extends Identifier { public final Expression declarationExpression; public final TypedExpression initializationExpression; public final TypedExpression referenceExpression; public Variable(String variableName, TypedExpression initializationExpression, TypedExpression referenceExpression) { - this(variableName, DeclarationExpression.empty(), initializationExpression, referenceExpression); + this(variableName, DeclarationExpression.empty(), initializationExpression, referenceExpression); } public Variable(String variableName, Expression declarationExpression, TypedExpression initializationExpression, TypedExpression referenceExpression) { @@ -18,7 +42,10 @@ public Variable(String variableName, Expression declarationExpression, TypedExpr this.declarationExpression = declarationExpression; this.initializationExpression = initializationExpression; this.referenceExpression = referenceExpression; - assert referenceExpression.getDataType() == initializationExpression.getDataType(); + // Compare primitive types (without cardinality) since iterator variables have sequence initializers but scalar references + // Use isAssignableFrom to allow compatible types (e.g., MultilingualString is assignable to String) + assert EfxTypeLattice.toPrimitive(referenceExpression.getDataType()) + .isAssignableFrom(EfxTypeLattice.toPrimitive(initializationExpression.getDataType())); } @Override 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 ad2c0bc..1484dd7 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.sdk1; import java.util.ArrayList; @@ -21,27 +34,30 @@ import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.exceptions.InvalidArgumentException; import eu.europa.ted.efx.exceptions.TypeMismatchException; +import eu.europa.ted.efx.exceptions.ConsistencyCheckException; 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.interfaces.TypeChecker; import eu.europa.ted.efx.model.CallStack; 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.expressions.Expression; +import eu.europa.ted.efx.model.expressions.PathExpression; 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.NodePath; 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.ScalarPath; import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringPath; 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; @@ -51,6 +67,7 @@ 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.EfxTypeLattice; import eu.europa.ted.efx.model.types.FieldTypes; import eu.europa.ted.efx.model.variables.ParsedParameter; import eu.europa.ted.efx.model.variables.Variable; @@ -84,7 +101,7 @@ public class EfxExpressionTranslatorV1 extends EfxBaseListener * 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(); + protected CallStack stack = new CallStack(TypeChecker.V1); /** * The context stack is used to keep track of context switching in nested expressions. @@ -608,7 +625,7 @@ private void exitList(int listSize, public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) { var topOfStack = this.stack.peek(); assert topOfStack instanceof TypedExpression : "Expected a TypedExpression at the top of the stack."; - final Class typeWhenFalse = ((TypedExpression)topOfStack).getDataType(); + final Class typeWhenFalse = EfxTypeLattice.toPrimitive(((TypedExpression)topOfStack).getDataType()); if (typeWhenFalse == EfxDataType.Boolean.class) { this.exitConditionalBooleanExpression(); } else if (typeWhenFalse == EfxDataType.Number.class) { @@ -622,7 +639,7 @@ public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext } else if (typeWhenFalse == EfxDataType.Duration.class) { this.exitConditionalDurationExpression(); } else { - throw new IllegalStateException("Unknown type " + typeWhenFalse); + throw ConsistencyCheckException.unsupportedTypeInConditional(typeWhenFalse); } } @@ -742,7 +759,8 @@ public void exitDurationIteratorExpression(DurationIteratorExpressionContext ctx public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); - var variableType = path.getClass(); + // Iterator variable holds the current item (scalar), not the sequence + var variableType = path.asScalar().getClass(); var variableName = getVariableName(ctx.contextVariableDeclaration()); Variable variable = new Variable(variableName, this.script.composeVariableDeclaration(variableName, variableType), @@ -750,18 +768,18 @@ public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx) this.script.composeVariableReference(variableName, variableType)); this.stack.declareIdentifier(variable); - this.stack.push(this.script.composeIteratorExpression(variable.declarationExpression, path)); + this.stack.push(this.script.composeIteratorExpression(variable.declarationExpression, path.asSequence())); if (ctx.fieldContext() != null) { final String contextFieldId = getFieldId(ctx.fieldContext()); this.efxContext.declareContextVariable(variable.name, new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), - this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol()))); } else if (ctx.nodeContext() != null) { final String contextNodeId = getNodeId(ctx.nodeContext()); this.efxContext.declareContextVariable(variable.name, new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.symbol()))); } } @@ -908,13 +926,13 @@ public void exitDurationLiteral(DurationLiteralContext ctx) { @Override public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { this.stack.push( - this.symbols.getRelativePathOfNode(ctx.NodeId().getText(), this.efxContext.absolutePath())); + this.symbols.getRelativePathOfNode(ctx.NodeId().getText(), this.efxContext.symbol())); } @Override public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx) { this.stack.push( - symbols.getRelativePathOfField(ctx.FieldId().getText(), this.efxContext.absolutePath())); + symbols.getRelativePathOfField(ctx.FieldId().getText(), this.efxContext.symbol())); } @Override @@ -952,7 +970,7 @@ 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(NodePathExpression.class); + PathExpression nodeReference = this.stack.pop(NodePath.class); this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate)); } } @@ -1043,10 +1061,10 @@ public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { String fieldId = getFieldId(ctx.fieldReference()); if (this.symbols.isAttributeField(fieldId)) { this.stack.push(this.script.composeFieldAttributeReference( - this.symbols.getRelativePath( + this.script.contextualizePath( this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), this.symbols.getAttributeNameFromAttributeField(fieldId), - PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); + ScalarPath.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); } @@ -1058,10 +1076,10 @@ public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx String fieldId = getFieldId(ctx.fieldReference()); if (this.symbols.isAttributeField(fieldId)) { this.stack.push(this.script.composeFieldAttributeReference( - this.symbols.getRelativePath( + this.script.contextualizePath( this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), this.symbols.getAttributeNameFromAttributeField(fieldId), - PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); + ScalarPath.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); } @@ -1070,13 +1088,13 @@ 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(), StringPathExpression.class)); + ctx.attributeReference().Identifier().getText(), StringPath.class)); } @Override public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - ctx.attributeReference().Identifier().getText(), StringPathExpression.class)); + ctx.attributeReference().Identifier().getText(), StringPath.class)); } // #endregion Value References ---------------------------------------------- @@ -1094,7 +1112,7 @@ public void exitContextFieldSpecifier(ContextFieldSpecifierContext ctx) { final String contextFieldId = getFieldId(ctx.fieldContext()); this.efxContext .push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), - this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol()))); } @@ -1123,7 +1141,7 @@ public void exitContextNodeSpecifier(ContextNodeSpecifierContext ctx) { final String contextNodeId = getNodeId(ctx.node); this.efxContext .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.symbol()))); } /** @@ -1146,13 +1164,13 @@ public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { if (variableContext.isFieldContext()) { this.efxContext.push(new FieldContext(variableContext.symbol(), this.symbols.getAbsolutePathOfField(variableContext.symbol()), this.symbols - .getRelativePathOfField(variableContext.symbol(), this.efxContext.absolutePath()))); + .getRelativePathOfField(variableContext.symbol(), this.efxContext.symbol()))); } else if (variableContext.isNodeContext()) { this.efxContext.push(new NodeContext(variableContext.symbol(), this.symbols.getAbsolutePathOfNode(variableContext.symbol()), this.symbols - .getRelativePathOfNode(variableContext.symbol(), this.efxContext.absolutePath()))); + .getRelativePathOfNode(variableContext.symbol(), this.efxContext.symbol()))); } else { - throw new IllegalStateException("Variable context is neither a field nor a node context."); + throw ConsistencyCheckException.invalidVariableContext(); } } 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 1392d12..bad88c4 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.sdk1; import java.io.IOException; @@ -28,11 +41,11 @@ import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.PathExpression; import eu.europa.ted.efx.model.expressions.TypedExpression; -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.DateExpression; import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringPath; import eu.europa.ted.efx.model.expressions.sequence.DateSequenceExpression; import eu.europa.ted.efx.model.expressions.sequence.StringSequenceExpression; import eu.europa.ted.efx.model.expressions.sequence.TimeSequenceExpression; @@ -359,10 +372,10 @@ private void shorthandIndirectLabelReference(final String fieldId) { final String fieldType = this.symbols.getTypeOfField(fieldId); final PathExpression valueReference = this.symbols.isAttributeField(fieldId) ? this.script.composeFieldAttributeReference( - this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), currentContext.absolutePath()), - this.symbols.getAttributeNameFromAttributeField(fieldId), StringPathExpression.class) + this.script.contextualizePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), currentContext.absolutePath()), + this.symbols.getAttributeNameFromAttributeField(fieldId), StringPath.class) : this.script.composeFieldValueReference( - this.symbols.getRelativePathOfField(fieldId, currentContext.absolutePath())); + this.symbols.getRelativePathOfField(fieldId, currentContext.symbol())); Variable loopVariable = new Variable("item", this.script.composeVariableDeclaration("item", StringExpression.class), StringExpression.empty(), this.script.composeVariableReference("item", StringExpression.class)); @@ -373,7 +386,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.composeForExpression( this.script.composeIteratorList( List.of( - this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))), + this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference.asSequence()))), this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), this.script.getStringLiteralFromUnquotedString("|"), @@ -392,7 +405,7 @@ private void shorthandIndirectLabelReference(final String fieldId) { this.script.composeForExpression( this.script.composeIteratorList( List.of( - this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))), + this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference.asSequence()))), this.script.composeStringConcatenation(List.of( this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), this.script.getStringLiteralFromUnquotedString("|"), @@ -535,7 +548,7 @@ public void exitShorthandFieldValueReferenceFromContextField( throw InvalidUsageException.shorthandRequiresFieldContext("$value"); } this.stack.push(this.script.composeFieldValueReference( - this.symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.symbol()))); } // #endregion Expression Blocks ${...} -------------------------------------- @@ -643,13 +656,13 @@ private Context relativizeContext(Context childContext, Context parentContext) { if (childContext.isFieldContext()) { return new FieldContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContextAbsolutePath), childContext.variable()); + this.script.contextualizePath(childContext.absolutePath(), parentContextAbsolutePath), childContext.variable()); } assert childContext.isNodeContext() : "Child context should be either a FieldContext NodeContext."; return new NodeContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContextAbsolutePath)); + this.script.contextualizePath(childContext.absolutePath(), parentContextAbsolutePath)); } // #endregion Template lines ----------------------------------------------- diff --git a/src/main/java/eu/europa/ted/efx/sdk1/TypeCheckerV1.java b/src/main/java/eu/europa/ted/efx/sdk1/TypeCheckerV1.java new file mode 100644 index 0000000..159d8e5 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk1/TypeCheckerV1.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.sdk1; + +import eu.europa.ted.efx.interfaces.TypeChecker; +import eu.europa.ted.efx.model.expressions.PathExpression; +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.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxTypeLattice; + +/** + * V1-compatible type checker that handles abstract base types (Sequence, Scalar). + * Replicates V1 behavior where EfxExpressionType.Path extended both Scalar and Sequence, + * allowing PathExpressions to convert to either target type. + */ +public class TypeCheckerV1 implements TypeChecker { + + public static final TypeCheckerV1 INSTANCE = new TypeCheckerV1(); + + private TypeCheckerV1() {} + + @Override + public boolean canConvert(Class from, + Class to) { + // Direct class compatibility + if (to.isAssignableFrom(from)) { + return true; + } + + // Handle abstract base types FIRST (before annotation check, since these have no annotations) + // PathExpression can convert to ANY Scalar or Sequence target + if (PathExpression.class.isAssignableFrom(from)) { + if (to == SequenceExpression.class || to == ScalarExpression.class) { + return true; + } + } + + // Get source annotation (required for remaining checks) + var fromAnnotation = from.getAnnotation(EfxDataTypeAssociation.class); + if (fromAnnotation == null) { + return false; + } + var fromType = fromAnnotation.dataType(); + + // Handle abstract target types (ScalarExpression/SequenceExpression have no annotations) + if (to == SequenceExpression.class) { + return EfxTypeLattice.isSequence(fromType) || EfxTypeLattice.isScalar(fromType); + } + if (to == ScalarExpression.class) { + return EfxTypeLattice.isScalar(fromType); + } + + // For concrete target types, get the annotation + var toAnnotation = to.getAnnotation(EfxDataTypeAssociation.class); + if (toAnnotation == null) { + return false; + } + var toType = toAnnotation.dataType(); + + // PathExpression to concrete type - check primitive compatibility + if (PathExpression.class.isAssignableFrom(from)) { + var fromPrimitive = EfxTypeLattice.toPrimitive(fromType); + var toPrimitive = EfxTypeLattice.toPrimitive(toType); + return toPrimitive.isAssignableFrom(fromPrimitive); + } + + // Direct type compatibility + if (toType.isAssignableFrom(fromType)) { + return true; + } + + // Scalar can promote to Sequence + if (EfxTypeLattice.isScalar(fromType) && EfxTypeLattice.isSequence(toType)) { + var fromPrimitive = EfxTypeLattice.toPrimitive(fromType); + var toPrimitive = EfxTypeLattice.toPrimitive(toType); + return toPrimitive.isAssignableFrom(fromPrimitive); + } + + return false; + } +} 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 deleted file mode 100644 index c8ba0ec..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkCodelistV1.java +++ /dev/null @@ -1,21 +0,0 @@ -package eu.europa.ted.efx.sdk1.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 = {"1"}, componentType = SdkComponentType.CODELIST) -public class SdkCodelistV1 extends SdkCodelist { - - 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 deleted file mode 100644 index db807f7..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkFieldV1.java +++ /dev/null @@ -1,47 +0,0 @@ -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; -import eu.europa.ted.eforms.sdk.entity.SdkField; - -@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, - final String xpathRelative, final String codelistId) { - super(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId); - } - - 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/sdk1/entity/SdkNodeV1.java b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNodeV1.java deleted file mode 100644 index 502ea99..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNodeV1.java +++ /dev/null @@ -1,22 +0,0 @@ -package eu.europa.ted.efx.sdk1.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 = {"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(JsonNode node) { - super(node); - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNoticeSubtypeV1.java b/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNoticeSubtypeV1.java deleted file mode 100644 index b8f2c68..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk1/entity/SdkNoticeSubtypeV1.java +++ /dev/null @@ -1,21 +0,0 @@ -package eu.europa.ted.efx.sdk1.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.SdkNoticeSubtype; - -/** - * Represents a notice subtype from the SDK's notice-types.json file. - */ -@SdkComponent(versions = {"1"}, componentType = SdkComponentType.NOTICE_TYPE) -public class SdkNoticeSubtypeV1 extends SdkNoticeSubtype { - - public SdkNoticeSubtypeV1(String subTypeId, String documentType, String type) { - super(subTypeId, documentType, type); - } - - public SdkNoticeSubtypeV1(JsonNode json) { - super(json); - } -} 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 8dce460..5ce7c9f 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 @@ -1,3 +1,16 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.sdk1.xpath; import eu.europa.ted.eforms.sdk.component.SdkComponent; @@ -5,7 +18,8 @@ import eu.europa.ted.eforms.xpath.XPathInfo; import eu.europa.ted.eforms.xpath.XPathProcessor; import eu.europa.ted.efx.interfaces.TranslatorOptions; -import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.PathExpression; import eu.europa.ted.efx.model.types.EfxDataType; import eu.europa.ted.efx.xpath.XPathScriptGenerator; @@ -43,7 +57,7 @@ public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { public PathExpression composeFieldValueReference(PathExpression fieldReference) { XPathInfo xpathInfo = XPathProcessor.parse(fieldReference.getScript()); if (fieldReference.is(EfxDataType.MultilingualString.class) && !xpathInfo.hasPredicate("@languageID")) { - return PathExpression.instantiate("efx:preferred-language-text(" + fieldReference.getScript() + ")", fieldReference.getDataType()); + return Expression.instantiate("efx:preferred-language-text(" + fieldReference.getScript() + ")", fieldReference.getClass()); } 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 4aeeba6..e439e85 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.sdk2; import static java.util.Map.entry; @@ -24,7 +37,10 @@ import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.exceptions.InvalidArgumentException; +import eu.europa.ted.efx.exceptions.InvalidIdentifierException; import eu.europa.ted.efx.exceptions.SymbolResolutionException; +import eu.europa.ted.efx.exceptions.TypeMismatchException; +import eu.europa.ted.efx.exceptions.ConsistencyCheckException; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.SymbolResolver; @@ -34,19 +50,19 @@ import eu.europa.ted.efx.model.Context.NodeContext; import eu.europa.ted.efx.model.ContextStack; import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.PathExpression; 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.MultilingualStringPath; 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.ScalarPath; import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringPath; 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; @@ -56,6 +72,7 @@ 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.EfxTypeLattice; import eu.europa.ted.efx.model.types.FieldTypes; import eu.europa.ted.efx.model.variables.ParsedArguments; import eu.europa.ted.efx.model.variables.Function; @@ -310,7 +327,7 @@ public void enterSingleExpression(SingleExpressionContext ctx) { if (nodeId != null) { this.efxContext.pushNodeContext(nodeId); } else { - throw SymbolResolutionException.unknownAlias(alias.getText()); + throw SymbolResolutionException.unknownSymbol(alias.getText()); } } } @@ -715,58 +732,58 @@ private void exitConditionalDurationExpression() { @Override public void exitStringIteratorExpression(StringIteratorExpressionContext ctx) { - this.exitIteratorExpression(ctx.stringVariableDeclaration().variableName.getText(), StringExpression.class, StringSequenceExpression.class); + this.exitIteratorExpression(ctx.stringIteratorVariableDeclaration().variableName.getText(), StringExpression.class, StringSequenceExpression.class); } @Override public void exitBooleanIteratorExpression(BooleanIteratorExpressionContext ctx) { - this.exitIteratorExpression(ctx.booleanVariableDeclaration().variableName.getText(), BooleanExpression.class, BooleanSequenceExpression.class); + this.exitIteratorExpression(ctx.booleanIteratorVariableDeclaration().variableName.getText(), BooleanExpression.class, BooleanSequenceExpression.class); } @Override public void exitNumericIteratorExpression(NumericIteratorExpressionContext ctx) { - this.exitIteratorExpression(ctx.numericVariableDeclaration().variableName.getText(), NumericExpression.class, NumericSequenceExpression.class); + this.exitIteratorExpression(ctx.numericIteratorVariableDeclaration().variableName.getText(), NumericExpression.class, NumericSequenceExpression.class); } @Override public void exitDateIteratorExpression(DateIteratorExpressionContext ctx) { - this.exitIteratorExpression(ctx.dateVariableDeclaration().variableName.getText(), DateExpression.class, DateSequenceExpression.class); + this.exitIteratorExpression(ctx.dateIteratorVariableDeclaration().variableName.getText(), DateExpression.class, DateSequenceExpression.class); } @Override public void exitTimeIteratorExpression(TimeIteratorExpressionContext ctx) { - this.exitIteratorExpression(ctx.timeVariableDeclaration().variableName.getText(), TimeExpression.class, TimeSequenceExpression.class); + this.exitIteratorExpression(ctx.timeIteratorVariableDeclaration().variableName.getText(), TimeExpression.class, TimeSequenceExpression.class); } @Override public void exitDurationIteratorExpression(DurationIteratorExpressionContext ctx) { - this.exitIteratorExpression(ctx.durationVariableDeclaration().variableName.getText(), DurationExpression.class, DurationSequenceExpression.class); + this.exitIteratorExpression(ctx.durationIteratorVariableDeclaration().variableName.getText(), DurationExpression.class, DurationSequenceExpression.class); } @Override public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx) { PathExpression path = this.stack.pop(PathExpression.class); - var variableType = path.getClass(); - var variableName = ctx.contextVariableDeclaration().variableName.getText(); + var variableType = path.asScalar().getClass(); + var variableName = ctx.contextIteratorVariableDeclaration().variableName.getText(); 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)); + this.stack.push(this.script.composeIteratorExpression(variable.declarationExpression, path.asSequence())); if (ctx.fieldContext() != null) { final String contextFieldId = getFieldId(ctx.fieldContext()); this.efxContext.declareContextVariable(variable.name, new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), - this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol()))); } else if (ctx.nodeContext() != null) { final String contextNodeId = getNodeId(ctx.nodeContext()); this.efxContext.declareContextVariable(variable.name, new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.symbol()))); } } @@ -780,39 +797,37 @@ public void exitIteratorList(IteratorListContext ctx) { } @Override - public void exitParenthesizedStringsFromIteration(ParenthesizedStringsFromIterationContext ctx) { + public void exitParenthesizedStrings(ParenthesizedStringsContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(StringSequenceExpression.class), StringSequenceExpression.class)); } @Override - public void exitParenthesizedNumbersFromIteration(ParenthesizedNumbersFromIterationContext ctx) { + public void exitParenthesizedNumbers(ParenthesizedNumbersContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(NumericSequenceExpression.class), NumericSequenceExpression.class)); } @Override - public void exitParenthesizedBooleansFromIteration( - ParenthesizedBooleansFromIterationContext ctx) { + public void exitParenthesizedBooleans(ParenthesizedBooleansContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(BooleanSequenceExpression.class), BooleanSequenceExpression.class)); } @Override - public void exitParenthesizedDatesFromIteration(ParenthesizedDatesFromIterationContext ctx) { + public void exitParenthesizedDates(ParenthesizedDatesContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(DateSequenceExpression.class), DateSequenceExpression.class)); } @Override - public void exitParenthesizedTimesFromIteration(ParenthesizedTimesFromIterationContext ctx) { + public void exitParenthesizedTimes(ParenthesizedTimesContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(TimeSequenceExpression.class), TimeSequenceExpression.class)); } @Override - public void exitParenthesizedDurationsFromIteration( - ParenthesizedDurationsFromIterationContext ctx) { + public void exitParenthesizedDurations(ParenthesizedDurationsContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(DurationSequenceExpression.class), DurationSequenceExpression.class)); } @@ -942,6 +957,67 @@ public void exitDurationLiteral(DurationLiteralContext ctx) { this.stack.push(this.script.getDurationLiteralEquivalent(ctx.getText())); } + // Sequence literals for parameter values + @Override + public void exitStringSequenceLiteral(StringSequenceLiteralContext ctx) { + int count = ctx.stringLiteral().size(); + List items = new ArrayList<>(); + for (int i = 0; i < count; i++) { + items.add(0, this.stack.pop(StringExpression.class)); + } + this.stack.push(this.script.composeList(items, StringSequenceExpression.class)); + } + + @Override + public void exitNumericSequenceLiteral(NumericSequenceLiteralContext ctx) { + int count = ctx.numericLiteral().size(); + List items = new ArrayList<>(); + for (int i = 0; i < count; i++) { + items.add(0, this.stack.pop(NumericExpression.class)); + } + this.stack.push(this.script.composeList(items, NumericSequenceExpression.class)); + } + + @Override + public void exitBooleanSequenceLiteral(BooleanSequenceLiteralContext ctx) { + int count = ctx.booleanLiteral().size(); + List items = new ArrayList<>(); + for (int i = 0; i < count; i++) { + items.add(0, this.stack.pop(BooleanExpression.class)); + } + this.stack.push(this.script.composeList(items, BooleanSequenceExpression.class)); + } + + @Override + public void exitDateSequenceLiteral(DateSequenceLiteralContext ctx) { + int count = ctx.dateLiteral().size(); + List items = new ArrayList<>(); + for (int i = 0; i < count; i++) { + items.add(0, this.stack.pop(DateExpression.class)); + } + this.stack.push(this.script.composeList(items, DateSequenceExpression.class)); + } + + @Override + public void exitTimeSequenceLiteral(TimeSequenceLiteralContext ctx) { + int count = ctx.timeLiteral().size(); + List items = new ArrayList<>(); + for (int i = 0; i < count; i++) { + items.add(0, this.stack.pop(TimeExpression.class)); + } + this.stack.push(this.script.composeList(items, TimeSequenceExpression.class)); + } + + @Override + public void exitDurationSequenceLiteral(DurationSequenceLiteralContext ctx) { + int count = ctx.durationLiteral().size(); + List items = new ArrayList<>(); + for (int i = 0; i < count; i++) { + items.add(0, this.stack.pop(DurationExpression.class)); + } + this.stack.push(this.script.composeList(items, DurationSequenceExpression.class)); + } + // #endregion Literals ------------------------------------------------------ // #region References ------------------------------------------------------- @@ -949,13 +1025,13 @@ public void exitDurationLiteral(DurationLiteralContext ctx) { @Override public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { this.stack.push( - this.symbols.getRelativePathOfNode(ctx.NodeId().getText(), this.efxContext.absolutePath())); + this.symbols.getRelativePathOfNode(ctx.NodeId().getText(), this.efxContext.symbol())); } @Override public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx) { this.stack.push( - symbols.getRelativePathOfField(ctx.fieldId.getText(), this.efxContext.absolutePath())); + symbols.getRelativePathOfField(ctx.fieldId.getText(), this.efxContext.symbol())); } @Override @@ -993,7 +1069,7 @@ 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(NodePathExpression.class); + PathExpression nodeReference = this.stack.pop(PathExpression.class); this.stack.push(this.script.composeNodeReferenceWithPredicate(nodeReference, predicate)); } } @@ -1084,10 +1160,10 @@ public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { String fieldId = getFieldId(ctx.fieldReference()); if (this.symbols.isAttributeField(fieldId)) { this.stack.push(this.script.composeFieldAttributeReference( - this.symbols.getRelativePath( + this.script.contextualizePath( this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), this.symbols.getAttributeNameFromAttributeField(fieldId), - PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); + ScalarPath.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); } @@ -1099,10 +1175,10 @@ public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx String fieldId = getFieldId(ctx.fieldReference()); if (this.symbols.isAttributeField(fieldId)) { this.stack.push(this.script.composeFieldAttributeReference( - this.symbols.getRelativePath( + this.script.contextualizePath( this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), this.efxContext.peek().absolutePath()), this.symbols.getAttributeNameFromAttributeField(fieldId), - PathExpression.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); + ScalarPath.fromFieldType.get(FieldTypes.fromString(this.symbols.getTypeOfField(fieldId))))); } else { this.stack.push(this.script.composeFieldValueReference(path)); } @@ -1111,13 +1187,13 @@ public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx @Override public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - ctx.attributeReference().attributeName.getText(), StringPathExpression.class)); + ctx.attributeReference().attributeName.getText(), StringPath.class)); } @Override public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceContext ctx) { this.stack.push(this.script.composeFieldAttributeReference(this.stack.pop(PathExpression.class), - ctx.attributeReference().attributeName.getText(), StringPathExpression.class)); + ctx.attributeReference().attributeName.getText(), StringPath.class)); } // #endregion Value References ---------------------------------------------- @@ -1135,7 +1211,7 @@ public void exitContextFieldSpecifier(ContextFieldSpecifierContext ctx) { final String contextFieldId = getFieldId(ctx.fieldContext()); this.efxContext .push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), - this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol()))); } @@ -1164,7 +1240,7 @@ public void exitContextNodeSpecifier(ContextNodeSpecifierContext ctx) { final String contextNodeId = getNodeId(ctx.node); this.efxContext .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.symbol()))); } /** @@ -1183,18 +1259,24 @@ public void exitFieldReferenceWithNodeContextOverride( @Override public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { - Context variableContext = this.efxContext.getContextFromVariable(ctx.variableReference().variableName.getText()); + String variableName = ctx.variableReference().variableName.getText(); + Context variableContext = this.efxContext.getContextFromVariable(variableName); + if (variableContext == null) { + throw InvalidIdentifierException.notAContextVariable(variableName); + } if (variableContext.isFieldContext()) { this.efxContext.push(new FieldContext(variableContext.symbol(), this.symbols.getAbsolutePathOfField(variableContext.symbol()), this.symbols - .getRelativePathOfField(variableContext.symbol(), this.efxContext.absolutePath()))); + .getRelativePathOfField(variableContext.symbol(), this.efxContext.symbol()))); } else if (variableContext.isNodeContext()) { this.efxContext.push(new NodeContext(variableContext.symbol(), this.symbols.getAbsolutePathOfNode(variableContext.symbol()), this.symbols - .getRelativePathOfNode(variableContext.symbol(), this.efxContext.absolutePath()))); + .getRelativePathOfNode(variableContext.symbol(), this.efxContext.symbol()))); } else { - throw new IllegalStateException("Variable context is neither a field nor a node context."); + assert false : "Context variable must be either a field or node context: " + variableName; } + // Push the context variable's path onto the stack for use by exitFieldReferenceWithVariableContextOverride + this.stack.pushIdentifierReference(variableName); } @Override @@ -1220,8 +1302,14 @@ public void exitCodelistReference(CodelistReferenceContext ctx) { } @Override - public void exitVariableReference(VariableReferenceContext ctx) { - String variableName = ctx.variableName.getText(); + public void exitScalarFromVariableReference(ScalarFromVariableReferenceContext ctx) { + String variableName = ctx.variableReference().variableName.getText(); + this.stack.pushIdentifierReference(variableName); + } + + @Override + public void exitSequenceFromVariableReference(SequenceFromVariableReferenceContext ctx) { + String variableName = ctx.variableReference().variableName.getText(); this.stack.pushIdentifierReference(variableName); } @@ -1264,7 +1352,14 @@ public void exitDurationAtSequenceIndex(DurationAtSequenceIndexContext ctx) { private void exitSequenceAtIndex( Class itemType, Class listType) { NumericExpression index = this.stack.pop(NumericExpression.class); + + var addParenthesis = this.stack.peek() instanceof PathExpression; + T list = this.stack.pop(listType); + + if (addParenthesis) { + list = this.script.composeParenthesizedExpression(list, listType); + } this.stack.push(this.script.composeIndexer(list, index, itemType)); } @@ -1334,6 +1429,30 @@ public void exitDurationFunctionInvocation(DurationFunctionInvocationContext ctx this.stack.push(this.script.composeFunctionInvocation(ctx.functionInvocation().functionName.getText(), parameters, DurationExpression.class)); } + // Map from EfxDataType to sequence expression types for function invocations + private static final Map, Class> efxDataTypeToSequenceExpressionMap = Map.ofEntries( + entry(EfxDataType.String.class, StringSequenceExpression.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)); + + @Override + public void exitSequenceFromFunctionInvocation(SequenceFromFunctionInvocationContext ctx) { + var arguments = this.stack.pop(StrictArguments.class); + var function = (Function) arguments.identifier; + var baseType = EfxTypeLattice.toPrimitive(function.dataType); + var sequenceExpressionType = efxDataTypeToSequenceExpressionMap.get(baseType); + if (sequenceExpressionType == null) { + throw ConsistencyCheckException.missingTypeMapping(function.dataType, "efxDataTypeToSequenceExpressionMap"); + } + this.stack.push(this.script.composeFunctionInvocation( + ctx.functionInvocation().functionName.getText(), + arguments.getArgumentValues(), + sequenceExpressionType)); + } + // #endregion New in EFX-2: Function invocation ----------------------------- // #region Parameter Declarations ------------------------------------------- @@ -1369,6 +1488,37 @@ public void exitDurationParameterDeclaration(DurationParameterDeclarationContext this.exitParameterDeclaration(ctx.parameterName.getText(), DurationExpression.class); } + // Sequence parameter declarations + @Override + public void exitStringSequenceParameterDeclaration(StringSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), StringSequenceExpression.class); + } + + @Override + public void exitNumericSequenceParameterDeclaration(NumericSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), NumericSequenceExpression.class); + } + + @Override + public void exitBooleanSequenceParameterDeclaration(BooleanSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), BooleanSequenceExpression.class); + } + + @Override + public void exitDateSequenceParameterDeclaration(DateSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), DateSequenceExpression.class); + } + + @Override + public void exitTimeSequenceParameterDeclaration(TimeSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), TimeSequenceExpression.class); + } + + @Override + public void exitDurationSequenceParameterDeclaration(DurationSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), DurationSequenceExpression.class); + } + private void exitParameterDeclaration(String parameterName, Class parameterType) { if (this.expressionArguments.isEmpty()) { throw InvalidArgumentException.missingArgument(parameterName); @@ -1409,10 +1559,40 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) { this.stack.push(this.script.composeEndsWithCondition(text, endsWith)); } + // sequence-equal typed handlers @Override - public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { - final SequenceExpression two = this.stack.pop(SequenceExpression.class); - final SequenceExpression one = this.stack.pop(SequenceExpression.class); + public void exitStringSequenceEqualFunction(StringSequenceEqualFunctionContext ctx) { + exitSequenceEqualFunction(StringSequenceExpression.class); + } + + @Override + public void exitBooleanSequenceEqualFunction(BooleanSequenceEqualFunctionContext ctx) { + exitSequenceEqualFunction(BooleanSequenceExpression.class); + } + + @Override + public void exitNumericSequenceEqualFunction(NumericSequenceEqualFunctionContext ctx) { + exitSequenceEqualFunction(NumericSequenceExpression.class); + } + + @Override + public void exitDateSequenceEqualFunction(DateSequenceEqualFunctionContext ctx) { + exitSequenceEqualFunction(DateSequenceExpression.class); + } + + @Override + public void exitTimeSequenceEqualFunction(TimeSequenceEqualFunctionContext ctx) { + exitSequenceEqualFunction(TimeSequenceExpression.class); + } + + @Override + public void exitDurationSequenceEqualFunction(DurationSequenceEqualFunctionContext ctx) { + exitSequenceEqualFunction(DurationSequenceExpression.class); + } + + private void exitSequenceEqualFunction(Class sequenceType) { + final T two = this.stack.pop(sequenceType); + final T one = this.stack.pop(sequenceType); this.stack.push(this.script.composeSequenceEqualFunction(one, two)); } @@ -1421,9 +1601,33 @@ public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) { // #region Numeric functions ------------------------------------------------ @Override - public void exitCountFunction(CountFunctionContext ctx) { - final SequenceExpression expression = this.stack.pop(SequenceExpression.class); - this.stack.push(this.script.composeCountOperation(expression)); + public void exitCountStringsFunction(CountStringsFunctionContext ctx) { + this.stack.push(this.script.composeCountOperation(this.stack.pop(StringSequenceExpression.class))); + } + + @Override + public void exitCountBooleansFunction(CountBooleansFunctionContext ctx) { + this.stack.push(this.script.composeCountOperation(this.stack.pop(BooleanSequenceExpression.class))); + } + + @Override + public void exitCountNumbersFunction(CountNumbersFunctionContext ctx) { + this.stack.push(this.script.composeCountOperation(this.stack.pop(NumericSequenceExpression.class))); + } + + @Override + public void exitCountDatesFunction(CountDatesFunctionContext ctx) { + this.stack.push(this.script.composeCountOperation(this.stack.pop(DateSequenceExpression.class))); + } + + @Override + public void exitCountTimesFunction(CountTimesFunctionContext ctx) { + this.stack.push(this.script.composeCountOperation(this.stack.pop(TimeSequenceExpression.class))); + } + + @Override + public void exitCountDurationsFunction(CountDurationsFunctionContext ctx) { + this.stack.push(this.script.composeCountOperation(this.stack.pop(DurationSequenceExpression.class))); } @Override @@ -1506,12 +1710,12 @@ public void exitStringJoinFunction(StringJoinFunctionContext ctx) { @Override public void exitPreferredLanguageFunction(PreferredLanguageFunctionContext ctx) { - this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringPathExpression.class))); + this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringPath.class))); } @Override public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionContext ctx) { - this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringPathExpression.class))); + this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringPath.class))); } @Override @@ -1579,120 +1783,148 @@ public void exitYearMonthDurationFromStringFunction( // #region Sequence Functions ----------------------------------------------- + // distinct-values typed handlers @Override - public void exitDistinctValuesFunction(DistinctValuesFunctionContext ctx) { - 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 InvalidArgumentException.unsupportedSequenceType(sequenceType.getSimpleName(), - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.DistinctValuesFunction)); - } + public void exitStringDistinctValuesFunction(StringDistinctValuesFunctionContext ctx) { + exitDistinctValuesFunction(StringSequenceExpression.class); + } + + @Override + public void exitBooleanDistinctValuesFunction(BooleanDistinctValuesFunctionContext ctx) { + exitDistinctValuesFunction(BooleanSequenceExpression.class); + } + + @Override + public void exitNumericDistinctValuesFunction(NumericDistinctValuesFunctionContext ctx) { + exitDistinctValuesFunction(NumericSequenceExpression.class); + } + + @Override + public void exitDateDistinctValuesFunction(DateDistinctValuesFunctionContext ctx) { + exitDistinctValuesFunction(DateSequenceExpression.class); + } + + @Override + public void exitTimeDistinctValuesFunction(TimeDistinctValuesFunctionContext ctx) { + exitDistinctValuesFunction(TimeSequenceExpression.class); } - private void exitDistinctValuesFunction( - Class listType) { + @Override + public void exitDurationDistinctValuesFunction(DurationDistinctValuesFunctionContext ctx) { + exitDistinctValuesFunction(DurationSequenceExpression.class); + } + + private void exitDistinctValuesFunction(Class listType) { final T list = this.stack.pop(listType); this.stack.push(this.script.composeDistinctValuesFunction(list, listType)); } + // union typed handlers @Override - public void exitUnionFunction(UnionFunctionContext ctx) { - 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 InvalidArgumentException.unsupportedSequenceType(sequenceType.getSimpleName(), - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.UnionFunction)); - } + public void exitStringUnionFunction(StringUnionFunctionContext ctx) { + exitUnionFunction(StringSequenceExpression.class); + } + + @Override + public void exitBooleanUnionFunction(BooleanUnionFunctionContext ctx) { + exitUnionFunction(BooleanSequenceExpression.class); + } + + @Override + public void exitNumericUnionFunction(NumericUnionFunctionContext ctx) { + exitUnionFunction(NumericSequenceExpression.class); + } + + @Override + public void exitDateUnionFunction(DateUnionFunctionContext ctx) { + exitUnionFunction(DateSequenceExpression.class); + } + + @Override + public void exitTimeUnionFunction(TimeUnionFunctionContext ctx) { + exitUnionFunction(TimeSequenceExpression.class); + } + + @Override + public void exitDurationUnionFunction(DurationUnionFunctionContext ctx) { + exitUnionFunction(DurationSequenceExpression.class); } - private void exitUnionFunction( - Class 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)); } + // intersect typed handlers @Override - public void exitIntersectFunction(IntersectFunctionContext ctx) { - 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 InvalidArgumentException.unsupportedSequenceType(sequenceType.getSimpleName(), - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.IntersectFunction)); - } + public void exitStringIntersectFunction(StringIntersectFunctionContext ctx) { + exitIntersectFunction(StringSequenceExpression.class); + } + + @Override + public void exitBooleanIntersectFunction(BooleanIntersectFunctionContext ctx) { + exitIntersectFunction(BooleanSequenceExpression.class); + } + + @Override + public void exitNumericIntersectFunction(NumericIntersectFunctionContext ctx) { + exitIntersectFunction(NumericSequenceExpression.class); + } + + @Override + public void exitDateIntersectFunction(DateIntersectFunctionContext ctx) { + exitIntersectFunction(DateSequenceExpression.class); } - private void exitIntersectFunction( - Class listType) { + @Override + public void exitTimeIntersectFunction(TimeIntersectFunctionContext ctx) { + exitIntersectFunction(TimeSequenceExpression.class); + } + + @Override + public void exitDurationIntersectFunction(DurationIntersectFunctionContext ctx) { + exitIntersectFunction(DurationSequenceExpression.class); + } + + 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)); } + // except typed handlers @Override - public void exitExceptFunction(ExceptFunctionContext ctx) { - 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 InvalidArgumentException.unsupportedSequenceType(sequenceType.getSimpleName(), - EfxLexer.VOCABULARY.getLiteralName(EfxLexer.ExceptFunction)); - } + public void exitStringExceptFunction(StringExceptFunctionContext ctx) { + exitExceptFunction(StringSequenceExpression.class); } - private void exitExceptFunction( - Class listType) { + @Override + public void exitBooleanExceptFunction(BooleanExceptFunctionContext ctx) { + exitExceptFunction(BooleanSequenceExpression.class); + } + + @Override + public void exitNumericExceptFunction(NumericExceptFunctionContext ctx) { + exitExceptFunction(NumericSequenceExpression.class); + } + + @Override + public void exitDateExceptFunction(DateExceptFunctionContext ctx) { + exitExceptFunction(DateSequenceExpression.class); + } + + @Override + public void exitTimeExceptFunction(TimeExceptFunctionContext ctx) { + exitExceptFunction(TimeSequenceExpression.class); + } + + @Override + public void exitDurationExceptFunction(DurationExceptFunctionContext ctx) { + exitExceptFunction(DurationSequenceExpression.class); + } + + 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)); @@ -1784,6 +2016,7 @@ class ExpressionPreprocessor extends EfxBaseListener { final EfxParser parser; final TokenStreamRewriter rewriter; final CallStack stack = new CallStack(); + final ContextStack efxContext; ExpressionPreprocessor(String expression) { this(CharStreams.fromString(expression)); @@ -1794,6 +2027,7 @@ class ExpressionPreprocessor extends EfxBaseListener { this.symbols = EfxExpressionTranslatorV2.this.symbols; this.errorListener = EfxExpressionTranslatorV2.this.errorListener; + this.efxContext = new ContextStack(this.symbols); this.lexer = new EfxLexer(charStream); this.tokens = new CommonTokenStream(lexer); @@ -1815,19 +2049,212 @@ String processExpression() { return this.rewriter.getText(); } + // #region Context tracking ----------------------------------------------- + @Override - public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { - if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { - return; + 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()); + } else { + final TerminalNode alias = ctx.Identifier(); + if (alias != null) { + String fieldId = this.symbols.getFieldIdFromAlias(alias.getText()); + if (fieldId != null) { + this.efxContext.pushFieldContext(fieldId); + } else { + String nodeId = this.symbols.getNodeIdFromAlias(alias.getText()); + if (nodeId != null) { + this.efxContext.pushNodeContext(nodeId); + } else { + throw SymbolResolutionException.unknownSymbol(alias.getText()); + } + } + } + } + } + } + + @Override + public void exitSingleExpression(SingleExpressionContext ctx) { + this.efxContext.pop(); + } + + @Override + public void enterPredicate(EfxParser.PredicateContext ctx) { + var parent = ctx.getParent(); + if (parent instanceof NodeReferenceWithPredicateContext) { + final String nodeId = getNodeId((NodeReferenceWithPredicateContext) parent); + this.efxContext.pushNodeContext(nodeId); + } else if (parent instanceof FieldReferenceWithPredicateContext) { + final String fieldId = getFieldId((FieldReferenceWithPredicateContext) parent); + this.efxContext.pushFieldContext(fieldId); + } else { + throw new ParseCancellationException("Unexpected parent context for predicate: " + parent.getClass().getSimpleName()); + } + } + + @Override + public void exitPredicate(EfxParser.PredicateContext ctx) { + this.efxContext.pop(); + } + + @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(); + } + } + + @Override + public void enterFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext ctx) { + if (ctx.noticeReference() != null) { + this.efxContext.push(null); + } + } + + @Override + public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNoticeContext ctx) { + if (ctx.noticeReference() != null) { + this.efxContext.pop(); + } + } + + @Override + public void exitContextFieldSpecifier(ContextFieldSpecifierContext ctx) { + final String contextFieldId = getFieldId(ctx.fieldContext()); + this.efxContext + .push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), + this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol()))); + } + + @Override + public void exitFieldReferenceWithFieldContextOverride( + FieldReferenceWithFieldContextOverrideContext ctx) { + if (ctx.contextFieldSpecifier() != null) { + this.efxContext.pop(); } + } + @Override + public void exitContextNodeSpecifier(ContextNodeSpecifierContext ctx) { + final String contextNodeId = getNodeId(ctx.node); + this.efxContext + .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), + this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.symbol()))); + } + + @Override + public void exitFieldReferenceWithNodeContextOverride( + FieldReferenceWithNodeContextOverrideContext ctx) { + if (ctx.contextNodeSpecifier() != null) { + this.efxContext.pop(); + } + } + + @Override + public void exitContextVariableSpecifier(ContextVariableSpecifierContext ctx) { + String variableName = ctx.variableReference().variableName.getText(); + Context variableContext = this.efxContext.getContextFromVariable(variableName); + if (variableContext == null) { + throw InvalidIdentifierException.notAContextVariable(variableName); + } + if (variableContext.isFieldContext()) { + this.efxContext.push(new FieldContext(variableContext.symbol(), + this.symbols.getAbsolutePathOfField(variableContext.symbol()), this.symbols + .getRelativePathOfField(variableContext.symbol(), this.efxContext.symbol()))); + } else if (variableContext.isNodeContext()) { + this.efxContext.push(new NodeContext(variableContext.symbol(), + this.symbols.getAbsolutePathOfNode(variableContext.symbol()), this.symbols + .getRelativePathOfNode(variableContext.symbol(), this.efxContext.symbol()))); + } else { + assert false : "Context variable must be either a field or node context: " + variableName; + } + } + + @Override + public void exitFieldReferenceWithVariableContextOverride( + FieldReferenceWithVariableContextOverrideContext ctx) { + if (ctx.contextVariableSpecifier() != null) { + this.efxContext.pop(); + } + } + + // #endregion Context tracking -------------------------------------------- + + @Override + public void exitScalarFromFieldReference(ScalarFromFieldReferenceContext ctx) { String fieldId = getFieldId(ctx.fieldReference()); String fieldType = eFormsToEfxTypeMap.get(this.symbols.getTypeOfField(fieldId)); + // Skip repeatability check if context IS this field (e.g., inside a predicate on this field). + // In that case, we're referencing the current element being iterated, not the whole sequence. + if (this.efxContext.isEmpty() || !fieldId.equals(this.efxContext.symbol())) { + String contextNodeId = getContextNodeId(); + if (this.symbols.isFieldRepeatableFromContext(fieldId, contextNodeId)) { + // B2 Solution 3: If we're in a top-level expression context where sequences + // are valid (lateBoundScalar → lateBoundExpression → expression), auto-insert + // sequence cast instead of throwing. + if (isTopLevelLateBoundExpression(ctx)) { + this.rewriter.insertBefore(ctx.getStart(), "(" + fieldType + "*)"); + return; + } + throw TypeMismatchException.fieldMayRepeat(fieldId, this.efxContext.symbol()); + } + } + + if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { + return; + } + // Insert the type cast this.rewriter.insertBefore(ctx.getStart(), "(" + fieldType + ")"); } + /** + * Gets the node ID of the current context for use with isFieldRepeatableFromContext. + * If the context is a NodeContext, returns the node ID directly. + * If the context is a FieldContext, returns the parent node of that field. + * If the context is empty/null, returns null (root context). + */ + private String getContextNodeId() { + if (this.efxContext.isEmpty() || this.efxContext.peek() == null) { + return null; + } + Context context = this.efxContext.peek(); + if (context.isNodeContext()) { + return context.symbol(); + } else { + // FieldContext - get the parent node of the field + return this.symbols.getParentNodeOfField(context.symbol()); + } + } + @Override public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext ctx) { if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { @@ -1845,7 +2272,7 @@ public void exitScalarFromFunctionInvocation(ScalarFromFunctionInvocationContext } String functionName = ctx.functionInvocation().functionName.getText(); - String functionType = javaToEfxTypeMap.get(this.stack.getTypeOfIdentifier(functionName)); + String functionType = javaToEfxTypeMap.get(EfxTypeLattice.toPrimitive(this.stack.getTypeOfIdentifier(functionName))); if (functionType != null) { // Insert the type cast @@ -1864,8 +2291,8 @@ public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx String fieldType = eFormsToEfxTypeMap.get(this.symbols.getTypeOfField(fieldId)); if (fieldType != null) { - // Insert the type cast - this.rewriter.insertBefore(ctx.getStart(), "(" + fieldType + ")"); + // Insert the sequence type cast (type*) + this.rewriter.insertBefore(ctx.getStart(), "(" + fieldType + "*)"); } } @@ -1875,37 +2302,82 @@ public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceCon return; } - // Insert the type cast - this.rewriter.insertBefore(ctx.getStart(), "(" + textTypeName + ")"); + // Insert the sequence type cast (text*) + this.rewriter.insertBefore(ctx.getStart(), "(" + textTypeName + "*)"); } @Override public void exitSequenceFromFunctionInvocation(SequenceFromFunctionInvocationContext ctx) { - if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { + if (!hasParentContextOfType(ctx, LateBoundSequenceContext.class)) { return; } String functionName = ctx.functionInvocation().functionName.getText(); - String functionType = javaToEfxTypeMap.get(this.stack.getTypeOfIdentifier(functionName)); + String functionType = javaToEfxTypeMap.get(EfxTypeLattice.toPrimitive(this.stack.getTypeOfIdentifier(functionName))); if (functionType != null) { - // Insert the type cast - this.rewriter.insertBefore(ctx.functionInvocation().FunctionPrefix().getSymbol(), "(" + functionType + ")"); + // Insert the sequence type cast (type*) + this.rewriter.insertBefore(ctx.functionInvocation().FunctionPrefix().getSymbol(), "(" + functionType + "*)"); } } @Override - public void exitVariableReference(VariableReferenceContext ctx) { + public void exitScalarFromVariableReference(ScalarFromVariableReferenceContext ctx) { + String variableName = ctx.variableReference().variableName.getText(); + Context variableContext = this.efxContext.getContextFromVariable(variableName); + + // Guard: Node context variables cannot be used as values + if (variableContext != null && variableContext.isNodeContext()) { + throw TypeMismatchException.nodesHaveNoValue(variableName, variableContext.symbol()); + } + + // Guard: Field context variables must not be repeatable in scalar context + if (variableContext != null && variableContext.isFieldContext()) { + String fieldId = variableContext.symbol(); + if (this.symbols.isFieldRepeatableFromContext(fieldId, getContextNodeId())) { + throw TypeMismatchException.fieldMayRepeat(fieldId, this.efxContext.symbol()); + } + } + + // Guard: Skip type cast insertion if not in late-bound context (explicit cast already present) if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) { return; } - String variableName = ctx.variableName.getText(); - String variableType = javaToEfxTypeMap.get(this.stack.getTypeOfIdentifier(variableName)); + // Determine type for cast: field type for context variables, variable type for regular variables + String typeCast = (variableContext != null) + ? eFormsToEfxTypeMap.get(this.symbols.getTypeOfField(variableContext.symbol())) + : javaToEfxTypeMap.get(EfxTypeLattice.toPrimitive(this.stack.getTypeOfIdentifier(variableName))); - if (variableType != null) { - // Insert the type cast - this.rewriter.insertBefore(ctx.VariablePrefix().getSymbol(), "(" + variableType + ")"); + if (typeCast != null) { + this.rewriter.insertBefore(ctx.variableReference().VariablePrefix().getSymbol(), "(" + typeCast + ")"); + } + } + + @Override + public void exitSequenceFromVariableReference(SequenceFromVariableReferenceContext ctx) { + String variableName = ctx.variableReference().variableName.getText(); + Context variableContext = this.efxContext.getContextFromVariable(variableName); + + // Guard: Node context variables cannot be used as values + if (variableContext != null && variableContext.isNodeContext()) { + throw TypeMismatchException.nodesHaveNoValue(variableName, variableContext.symbol()); + } + + // No repeatability check needed - sequences can have multiple values + + // Guard: Skip type cast insertion if not in late-bound context (explicit cast already present) + if (!hasParentContextOfType(ctx, LateBoundSequenceContext.class)) { + return; + } + + // Determine type for cast: field type for context variables, variable type for regular variables + String typeCast = (variableContext != null) + ? eFormsToEfxTypeMap.get(this.symbols.getTypeOfField(variableContext.symbol())) + : javaToEfxTypeMap.get(EfxTypeLattice.toPrimitive(this.stack.getTypeOfIdentifier(variableName))); + + if (typeCast != null) { + this.rewriter.insertBefore(ctx.variableReference().VariablePrefix().getSymbol(), "(" + typeCast + "*)"); } } @@ -1916,7 +2388,7 @@ public void exitDictionaryLookup(DictionaryLookupContext ctx) { } String dictionaryName = ctx.dictionaryName.getText(); - String dictionaryType = javaToEfxTypeMap.get(this.stack.getTypeOfIdentifier(dictionaryName)); + String dictionaryType = javaToEfxTypeMap.get(EfxTypeLattice.toPrimitive(this.stack.getTypeOfIdentifier(dictionaryName))); if (dictionaryType != null) { // Insert the type cast @@ -1935,48 +2407,86 @@ boolean hasParentContextOfType(ParserRuleContext ctx, Class expressionType) { + var expression = this.stack.pop(expressionType); + var variable = new Variable(variableName, + this.script.composeVariableDeclaration(variableName, expression.getClass()), + expression, + this.script.composeVariableReference(variableName, expression.getClass())); + this.stack.push(variable); + } + + @Override + public void exitStringSequenceVariableInitializer( + EfxParser.StringSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), StringSequenceExpression.class); + } + + @Override + public void exitBooleanSequenceVariableInitializer( + EfxParser.BooleanSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), BooleanSequenceExpression.class); + } + + @Override + public void exitNumericSequenceVariableInitializer( + EfxParser.NumericSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), NumericSequenceExpression.class); + } + + @Override + public void exitDateSequenceVariableInitializer( + EfxParser.DateSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), DateSequenceExpression.class); + } + + @Override + public void exitTimeSequenceVariableInitializer( + EfxParser.TimeSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), TimeSequenceExpression.class); + } + + @Override + public void exitDurationSequenceVariableInitializer( + EfxParser.DurationSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), DurationSequenceExpression.class); + } + // #endregion Variable Initializers // #region Schema-level Variables @@ -389,9 +447,7 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) { } private void exitRootContextDeclaration() { - PathExpression contextPath = new NodePathExpression("/*"); - String symbol = "ND-Root"; - this.exitNodeContextDeclaration(symbol, contextPath, null); + this.exitNodeContextDeclaration(this.symbols.getRootNodeId(), this.symbols.getRootPath(), null); } private void exitFieldContextDeclaration(String fieldId, PathExpression contextPath, Variable contextVariable) { @@ -421,11 +477,11 @@ private Variable getContextVariable(ContextVariableInitializerContext ctx, } final String variableName = ctx.variableName.getText(); - final Class variableType = contextPath.getClass(); + final Class variableType = contextPath.asScalar().getClass(); return new Variable(variableName, this.script.composeVariableDeclaration(variableName, variableType), - this.symbols.getRelativePath(contextPath, contextPath), + this.script.contextualizePath(contextPath, contextPath), this.script.composeVariableReference(variableName, variableType)); } @@ -669,22 +725,71 @@ public void exitValidationStage(ValidationStageContext ctx) { @Override public void exitContextDeclaration(ContextDeclarationContext ctx) { + // Handle context shortcuts (., .., /) - these don't have context variable initializers + String shortcut = ctx.shortcut != null ? ctx.shortcut.getText() : "none"; + switch (shortcut) { + case "/": + this.efxContext.push(new NodeContext(this.symbols.getRootNodeId(), this.symbols.getRootPath())); + return; + case ".": + case "..": + // Same/parent context not supported in rules, but still need to push something + return; + default: + break; + } + + // Handle field or node context without variable + if (ctx.fieldContext() != null) { + final String fieldId = getFieldId(ctx.fieldContext()); + this.efxContext.push(new FieldContext(fieldId, this.symbols.getAbsolutePathOfField(fieldId), + this.symbols.getRelativePathOfField(fieldId, this.efxContext.symbol()))); + return; + } + if (ctx.nodeContext() != null) { + final String nodeId = getNodeId(ctx.nodeContext()); + this.efxContext.push(new NodeContext(nodeId, this.symbols.getAbsolutePathOfNode(nodeId), + this.symbols.getRelativePathOfNode(nodeId, this.efxContext.symbol()))); + return; + } + + // Handle context variable initializer final var initializer = ctx.contextVariableInitializer(); if (initializer == null) { - return; // No context variable initializer, nothing to do during pre-processing. + return; } + final String variableName = initializer.variableName.getText(); if (initializer.fieldContext() != null) { final String fieldId = getFieldId(initializer.fieldContext()); var fieldType = FieldTypes.fromString(this.symbols.getTypeOfField(fieldId)); this.stack.declareIdentifier( - new Variable(initializer.variableName.getText(), PathExpression.instantiate("", fieldType), - PathExpression.instantiate("", fieldType), PathExpression.instantiate("", fieldType))); + new Variable(variableName, ScalarPath.empty(fieldType), + ScalarPath.empty(fieldType), ScalarPath.empty(fieldType))); + var context = new FieldContext(fieldId, this.symbols.getAbsolutePathOfField(fieldId), + this.symbols.getRelativePathOfField(fieldId, this.efxContext.symbol())); + this.efxContext.push(context); + this.efxContext.declareContextVariable(variableName, context); } if (initializer.nodeContext() != null) { - this.stack.declareIdentifier(new Variable(initializer.variableName.getText(), NodePathExpression.empty(), - NodePathExpression.empty(), NodePathExpression.empty())); + final String nodeId = getNodeId(initializer.nodeContext()); + this.stack.declareIdentifier(new Variable(variableName, NodePath.empty(), + NodePath.empty(), NodePath.empty())); + var context = new NodeContext(nodeId, this.symbols.getAbsolutePathOfNode(nodeId), + this.symbols.getRelativePathOfNode(nodeId, this.efxContext.symbol())); + this.efxContext.push(context); + this.efxContext.declareContextVariable(variableName, context); } - return; + } + + @Override + public void enterRuleSet(RuleSetContext ctx) { + this.stack.pushStackFrame(); + } + + @Override + public void exitRuleSet(RuleSetContext ctx) { + this.stack.popStackFrame(); + this.efxContext.pop(); } /** @@ -693,10 +798,8 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) { @Override public void exitVariableInitializer( EfxParser.VariableInitializerContext ctx) { - if (!this.stack.empty()) { Variable variable = this.stack.pop(Variable.class); this.stack.declareIdentifier(variable); - } } // #endregion Rules-specific variable declarations @@ -743,6 +846,44 @@ public void exitDurationVariableInitializer(EfxParser.DurationVariableInitialize DurationExpression.empty(), DurationExpression.empty())); } + // Sequence variable initializers for type tracking + + @Override + public void exitStringSequenceVariableInitializer(EfxParser.StringSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), + new StringSequenceExpression(""), new StringSequenceExpression(""))); + } + + @Override + public void exitBooleanSequenceVariableInitializer(EfxParser.BooleanSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), + new BooleanSequenceExpression(""), new BooleanSequenceExpression(""))); + } + + @Override + public void exitNumericSequenceVariableInitializer(EfxParser.NumericSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), + new NumericSequenceExpression(""), new NumericSequenceExpression(""))); + } + + @Override + public void exitDateSequenceVariableInitializer(EfxParser.DateSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), + new DateSequenceExpression(""), new DateSequenceExpression(""))); + } + + @Override + public void exitTimeSequenceVariableInitializer(EfxParser.TimeSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), + new TimeSequenceExpression(""), new TimeSequenceExpression(""))); + } + + @Override + public void exitDurationSequenceVariableInitializer(EfxParser.DurationSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), + new DurationSequenceExpression(""), new DurationSequenceExpression(""))); + } + // #endregion Variable initializers for type tracking } 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 e6b2850..6080bb4 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.sdk2; import java.io.IOException; @@ -40,18 +53,23 @@ import eu.europa.ted.efx.model.Context.FieldContext; import eu.europa.ted.efx.model.Context.NodeContext; import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.PathExpression; import eu.europa.ted.efx.model.expressions.TypedExpression; -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.NodePath; 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.ScalarPath; import eu.europa.ted.efx.model.expressions.scalar.StringExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringPath; 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.templates.Conditional; @@ -76,6 +94,9 @@ import eu.europa.ted.efx.sdk2.EfxParser.AssetTypeContext; import eu.europa.ted.efx.sdk2.EfxParser.BooleanFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.BooleanParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.BooleanSequenceParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.BooleanSequenceFunctionDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.BooleanSequenceVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.BooleanVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.ChooseTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.ComputedLabelReferenceContext; @@ -84,12 +105,18 @@ import eu.europa.ted.efx.sdk2.EfxParser.ContextVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.DateFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.DateParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.DateSequenceParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.DateSequenceFunctionDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.DateSequenceVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.DateVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.DictionaryDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.DictionaryIndexClauseContext; import eu.europa.ted.efx.sdk2.EfxParser.DictionaryKeyClauseContext; import eu.europa.ted.efx.sdk2.EfxParser.DurationFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.DurationParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.DurationSequenceParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.DurationSequenceFunctionDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.DurationSequenceVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.DurationVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.ExpressionTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.VariableDeclarationContext; @@ -106,6 +133,9 @@ import eu.europa.ted.efx.sdk2.EfxParser.NavigationSectionContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.NumericSequenceParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.NumericSequenceFunctionDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.NumericSequenceVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.NumericVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.SecondaryTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.ShorthandBtLabelReferenceContext; @@ -118,6 +148,9 @@ import eu.europa.ted.efx.sdk2.EfxParser.StandardLabelReferenceContext; import eu.europa.ted.efx.sdk2.EfxParser.StringFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.StringParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.StringSequenceParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.StringSequenceFunctionDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.StringSequenceVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.StringVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.SummarySectionContext; import eu.europa.ted.efx.sdk2.EfxParser.TemplateDefinitionContext; @@ -128,6 +161,9 @@ import eu.europa.ted.efx.sdk2.EfxParser.TextTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.TimeFunctionDeclarationContext; import eu.europa.ted.efx.sdk2.EfxParser.TimeParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.TimeSequenceParameterDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.TimeSequenceFunctionDeclarationContext; +import eu.europa.ted.efx.sdk2.EfxParser.TimeSequenceVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.TimeVariableInitializerContext; import eu.europa.ted.efx.sdk2.EfxParser.WhenDisplayTemplateContext; import eu.europa.ted.efx.sdk2.EfxParser.WhenInvokeTemplateContext; @@ -335,7 +371,7 @@ public void enterStringFunctionDeclaration(StringFunctionDeclarationContext ctx) @Override public void exitStringFunctionDeclaration(StringFunctionDeclarationContext ctx) { - this.exitFunctionDeclaration(ctx.functionName.getText(), EfxDataType.String.class); + this.exitFunctionDeclaration(ctx.functionName.getText()); } @Override @@ -345,7 +381,7 @@ public void enterBooleanFunctionDeclaration(BooleanFunctionDeclarationContext ct @Override public void exitBooleanFunctionDeclaration(BooleanFunctionDeclarationContext ctx) { - this.exitFunctionDeclaration(ctx.functionName.getText(), EfxDataType.Boolean.class); + this.exitFunctionDeclaration(ctx.functionName.getText()); } @Override @@ -355,7 +391,7 @@ public void enterNumericFunctionDeclaration(NumericFunctionDeclarationContext ct @Override public void exitNumericFunctionDeclaration(NumericFunctionDeclarationContext ctx) { - this.exitFunctionDeclaration(ctx.functionName.getText(), EfxDataType.Number.class); + this.exitFunctionDeclaration(ctx.functionName.getText()); } @Override @@ -365,7 +401,7 @@ public void enterDateFunctionDeclaration(DateFunctionDeclarationContext ctx) { @Override public void exitDateFunctionDeclaration(DateFunctionDeclarationContext ctx) { - this.exitFunctionDeclaration(ctx.functionName.getText(), EfxDataType.Date.class); + this.exitFunctionDeclaration(ctx.functionName.getText()); } @Override @@ -375,7 +411,7 @@ public void enterTimeFunctionDeclaration(TimeFunctionDeclarationContext ctx) { @Override public void exitTimeFunctionDeclaration(TimeFunctionDeclarationContext ctx) { - this.exitFunctionDeclaration(ctx.functionName.getText(), EfxDataType.Time.class); + this.exitFunctionDeclaration(ctx.functionName.getText()); } @Override @@ -385,14 +421,76 @@ public void enterDurationFunctionDeclaration(DurationFunctionDeclarationContext @Override public void exitDurationFunctionDeclaration(DurationFunctionDeclarationContext ctx) { - this.exitFunctionDeclaration(ctx.functionName.getText(), EfxDataType.Duration.class); + this.exitFunctionDeclaration(ctx.functionName.getText()); + } + + // Sequence function declarations + + @Override + public void enterStringSequenceFunctionDeclaration(StringSequenceFunctionDeclarationContext ctx) { + this.stack.push(new ParsedParameters()); + } + + @Override + public void exitStringSequenceFunctionDeclaration(StringSequenceFunctionDeclarationContext ctx) { + this.exitFunctionDeclaration(ctx.functionName.getText()); + } + + @Override + public void enterNumericSequenceFunctionDeclaration(NumericSequenceFunctionDeclarationContext ctx) { + this.stack.push(new ParsedParameters()); + } + + @Override + public void exitNumericSequenceFunctionDeclaration(NumericSequenceFunctionDeclarationContext ctx) { + this.exitFunctionDeclaration(ctx.functionName.getText()); + } + + @Override + public void enterBooleanSequenceFunctionDeclaration(BooleanSequenceFunctionDeclarationContext ctx) { + this.stack.push(new ParsedParameters()); } - private void exitFunctionDeclaration(String functionName, Class returnType) { + @Override + public void exitBooleanSequenceFunctionDeclaration(BooleanSequenceFunctionDeclarationContext ctx) { + this.exitFunctionDeclaration(ctx.functionName.getText()); + } + + @Override + public void enterDateSequenceFunctionDeclaration(DateSequenceFunctionDeclarationContext ctx) { + this.stack.push(new ParsedParameters()); + } + + @Override + public void exitDateSequenceFunctionDeclaration(DateSequenceFunctionDeclarationContext ctx) { + this.exitFunctionDeclaration(ctx.functionName.getText()); + } + + @Override + public void enterTimeSequenceFunctionDeclaration(TimeSequenceFunctionDeclarationContext ctx) { + this.stack.push(new ParsedParameters()); + } + + @Override + public void exitTimeSequenceFunctionDeclaration(TimeSequenceFunctionDeclarationContext ctx) { + this.exitFunctionDeclaration(ctx.functionName.getText()); + } + + @Override + public void enterDurationSequenceFunctionDeclaration(DurationSequenceFunctionDeclarationContext ctx) { + this.stack.push(new ParsedParameters()); + } + + @Override + public void exitDurationSequenceFunctionDeclaration(DurationSequenceFunctionDeclarationContext ctx) { + this.exitFunctionDeclaration(ctx.functionName.getText()); + } + + private void exitFunctionDeclaration(String functionName) { var expression = this.stack.pop(TypedExpression.class); var parameters = this.stack.pop(ParsedParameters.class); - this.stack.declareFunction(new Function(functionName, returnType, parameters, expression)); + this.stack.declareFunction(new Function(functionName, parameters, expression)); } @Override @@ -430,10 +528,10 @@ public void exitTemplateFile(TemplateFileContext ctx) { for (Identifier identifier : this.stack.getGlobals()) { if (identifier instanceof Variable) { Variable variable = (Variable) identifier; - globals.add(this.markup.renderVariableDeclaration(variable.dataType, variable.name, variable.initializationExpression)); + globals.add(this.markup.renderVariableDeclaration(variable.name, variable.initializationExpression)); } else if (identifier instanceof Function) { Function function = (Function) identifier; - globals.add(this.markup.renderFunctionDeclaration(function.dataType, function.name, function.parameters.toMap(), function.expression)); + globals.add(this.markup.renderFunctionDeclaration(function.name, function.parameters.toMap(), function.expression)); } else if (identifier instanceof Dictionary) { Dictionary dictionary = (Dictionary) identifier; globals.add(this.markup.renderDictionaryDeclaration(dictionary.name, dictionary.pathExpression, dictionary.keyExpression)); @@ -643,11 +741,11 @@ private void shorthandIndirectLabelReference(final String fieldId, final Numeric final String fieldType = this.symbols.getTypeOfField(fieldId); final PathExpression valueReference = this.symbols.isAttributeField(fieldId) ? this.script.composeFieldAttributeReference( - this.symbols.getRelativePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), + this.script.contextualizePath(this.symbols.getAbsolutePathOfFieldWithoutTheAttribute(fieldId), currentContext.absolutePath()), - this.symbols.getAttributeNameFromAttributeField(fieldId), StringPathExpression.class) + this.symbols.getAttributeNameFromAttributeField(fieldId), StringPath.class) : this.script.composeFieldValueReference( - this.symbols.getRelativePathOfField(fieldId, currentContext.absolutePath())); + this.symbols.getRelativePathOfField(fieldId, currentContext.symbol())); Variable loopVariable = new Variable("item", this.script.composeVariableDeclaration("item", StringExpression.class), StringExpression.empty(), this.script.composeVariableReference("item", StringExpression.class)); @@ -658,7 +756,7 @@ private void shorthandIndirectLabelReference(final String fieldId, final Numeric this.script.composeForExpression( this.script.composeIteratorList( List.of( - this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))), + this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference.asSequence()))), this.script.composeStringConcatenation( List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR), this.script.getStringLiteralFromUnquotedString("|"), @@ -677,7 +775,7 @@ private void shorthandIndirectLabelReference(final String fieldId, final Numeric this.script.composeForExpression( this.script.composeIteratorList( List.of( - this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))), + this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference.asSequence()))), this.script.composeStringConcatenation(List.of( this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE), this.script.getStringLiteralFromUnquotedString("|"), @@ -859,7 +957,7 @@ public void exitShorthandFieldValueReferenceFromContextField( throw InvalidUsageException.shorthandRequiresFieldContext("$value"); } this.stack.push(this.script.composeFieldValueReference( - this.symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.absolutePath()))); + this.symbols.getRelativePathOfField(this.efxContext.symbol(), this.efxContext.symbol()))); } // #endregion Expression Blocks ${...} -------------------------------------- @@ -910,6 +1008,12 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) { private void exitSameContextDeclaration() { Context currentContext = this.blockStack.currentContext(); + + // Verify that efxContext.peek() gives the same result as blockStack.currentContext() + assert this.efxContext.isEmpty() || currentContext.symbol().equals(this.efxContext.peek().symbol()) + : "Context mismatch: blockStack.currentContext()=" + currentContext.symbol() + + " but efxContext.peek()=" + this.efxContext.peek().symbol(); + PathExpression contextPath = currentContext.absolutePath(); String symbol = currentContext.symbol(); if (currentContext.isFieldContext()) { @@ -921,6 +1025,13 @@ private void exitSameContextDeclaration() { private void exitParentContextDeclaration() { Context parentContext = this.blockStack.parentContext(); + + // Verify that efxContext.peekParent() gives the same result as blockStack.parentContext() + Context efxParentContext = this.efxContext.peekParentContext(); + assert efxParentContext == null || parentContext.symbol().equals(efxParentContext.symbol()) + : "Context mismatch: blockStack.parentContext()=" + parentContext.symbol() + + " but efxContext.peekParent()=" + efxParentContext.symbol(); + PathExpression contextPath = parentContext.absolutePath(); String symbol = parentContext.symbol(); if (parentContext.isFieldContext()) { @@ -931,9 +1042,7 @@ private void exitParentContextDeclaration() { } private void exitRootContextDeclaration() { - PathExpression contextPath = new NodePathExpression("/*"); - String symbol = "ND-Root"; - this.exitNodeContextDeclaration(symbol, contextPath, null); + this.exitNodeContextDeclaration(this.symbols.getRootNodeId(), this.symbols.getRootPath(), null); } private void exitFieldContextDeclaration(String fieldId, PathExpression contextPath, Variable contextVariable) { @@ -961,11 +1070,11 @@ private Variable getContextVariable(ContextVariableInitializerContext ctx, } final String variableName = ctx.variableName.getText(); - final Class variableType = contextPath.getClass(); + final Class variableType = contextPath.asScalar().getClass(); return new Variable(variableName, this.script.composeVariableDeclaration(variableName, variableType), - this.symbols.getRelativePath(contextPath, contextPath), + this.script.contextualizePath(contextPath, contextPath), this.script.composeVariableReference(variableName, variableType)); } @@ -1059,6 +1168,48 @@ public void exitVariableInitializer(VariableInitializerContext arg0) { this.stack.peek(Variables.class).add(variable); } + // Sequence variable initializers + + @Override + public void exitStringSequenceVariableInitializer(StringSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), StringSequenceExpression.class); + } + + @Override + public void exitNumericSequenceVariableInitializer(NumericSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), NumericSequenceExpression.class); + } + + @Override + public void exitBooleanSequenceVariableInitializer(BooleanSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), BooleanSequenceExpression.class); + } + + @Override + public void exitDateSequenceVariableInitializer(DateSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), DateSequenceExpression.class); + } + + @Override + public void exitTimeSequenceVariableInitializer(TimeSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), TimeSequenceExpression.class); + } + + @Override + public void exitDurationSequenceVariableInitializer(DurationSequenceVariableInitializerContext ctx) { + this.exitSequenceVariableInitializer(ctx.variableName.getText(), DurationSequenceExpression.class); + } + + private void exitSequenceVariableInitializer( + String variableName, Class expressionType) { + var expression = this.stack.pop(expressionType); + var variable = new Variable(variableName, + this.script.composeVariableDeclaration(variableName, expression.getClass()), + expression, + this.script.composeVariableReference(variableName, expression.getClass())); + this.stack.push(variable); + } + // #endregion Variable Initializers ----------------------------------------- // #region Hyperlinks ------------------------------------------------------- @@ -1130,6 +1281,38 @@ public void exitDurationParameterDeclaration(DurationParameterDeclarationContext this.exitParameterDeclaration(ctx.parameterName.getText(), DurationExpression.class); } + // Sequence parameter declarations + + @Override + public void exitStringSequenceParameterDeclaration(StringSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), StringSequenceExpression.class); + } + + @Override + public void exitNumericSequenceParameterDeclaration(NumericSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), NumericSequenceExpression.class); + } + + @Override + public void exitBooleanSequenceParameterDeclaration(BooleanSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), BooleanSequenceExpression.class); + } + + @Override + public void exitDateSequenceParameterDeclaration(DateSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), DateSequenceExpression.class); + } + + @Override + public void exitTimeSequenceParameterDeclaration(TimeSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), TimeSequenceExpression.class); + } + + @Override + public void exitDurationSequenceParameterDeclaration(DurationSequenceParameterDeclarationContext ctx) { + this.exitParameterDeclaration(ctx.parameterName.getText(), DurationSequenceExpression.class); + } + private void exitParameterDeclaration(String parameterName, Class parameterType) { ParsedParameter parameter = new ParsedParameter(parameterName, this.script.composeParameterReference(parameterName, parameterType)); @@ -1275,13 +1458,13 @@ private Context relativizeContext(Context childContext, Context parentContext) { if (childContext.isFieldContext()) { return new FieldContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContextAbsolutePath), childContext.variable()); + this.script.contextualizePath(childContext.absolutePath(), parentContextAbsolutePath), childContext.variable()); } assert childContext.isNodeContext() : "Child context should be either a FieldContext or a NodeContext."; return new NodeContext(childContext.symbol(), childContext.absolutePath(), - this.symbols.getRelativePath(childContext.absolutePath(), parentContextAbsolutePath)); + this.script.contextualizePath(childContext.absolutePath(), parentContextAbsolutePath)); } // #endregion Template lines ----------------------------------------------- @@ -1359,22 +1542,110 @@ public void exitVariableInitializer(VariableInitializerContext arg0) { @Override public void exitContextDeclaration(ContextDeclarationContext ctx) { + // Handle context shortcuts (., .., /) + String shortcut = ctx.shortcut != null ? ctx.shortcut.getText() : "none"; + switch (shortcut) { + case ".": + this.exitSameContextDeclaration(); + return; + case "..": + this.exitParentContextDeclaration(); + return; + case "/": + this.exitRootContextDeclaration(); + return; + default: + break; + } + + // Handle field or node context without variable + if (ctx.fieldContext() != null) { + final String fieldId = getFieldId(ctx.fieldContext()); + this.efxContext.push(new FieldContext(fieldId, this.symbols.getAbsolutePathOfField(fieldId), + this.symbols.getRelativePathOfField(fieldId, this.efxContext.symbol()))); + return; + } + if (ctx.nodeContext() != null) { + final String nodeId = getNodeId(ctx.nodeContext()); + this.efxContext.push(new NodeContext(nodeId, this.symbols.getAbsolutePathOfNode(nodeId), + this.symbols.getRelativePathOfNode(nodeId, this.efxContext.symbol()))); + return; + } + + // Handle context variable initializer final var initializer = ctx.contextVariableInitializer(); if (initializer == null) { - return; // No context variable initializer, nothing to do during pre-processing. + return; } + final String variableName = initializer.variableName.getText(); if (initializer.fieldContext() != null) { final String fieldId = getFieldId(initializer.fieldContext()); var fieldType = FieldTypes.fromString(this.symbols.getTypeOfField(fieldId)); this.stack.declareIdentifier( - new Variable(initializer.variableName.getText(), PathExpression.instantiate("", fieldType), - PathExpression.instantiate("", fieldType), PathExpression.instantiate("", fieldType))); + new Variable(variableName, ScalarPath.empty(fieldType), + ScalarPath.empty(fieldType), ScalarPath.empty(fieldType))); + var context = new FieldContext(fieldId, this.symbols.getAbsolutePathOfField(fieldId), + this.symbols.getRelativePathOfField(fieldId, this.efxContext.symbol())); + this.efxContext.push(context); + this.efxContext.declareContextVariable(variableName, context); } if (initializer.nodeContext() != null) { - this.stack.declareIdentifier(new Variable(initializer.variableName.getText(), NodePathExpression.empty(), - NodePathExpression.empty(), NodePathExpression.empty())); + final String nodeId = getNodeId(initializer.nodeContext()); + this.stack.declareIdentifier(new Variable(variableName, NodePath.empty(), + NodePath.empty(), NodePath.empty())); + var context = new NodeContext(nodeId, this.symbols.getAbsolutePathOfNode(nodeId), + this.symbols.getRelativePathOfNode(nodeId, this.efxContext.symbol())); + this.efxContext.push(context); + this.efxContext.declareContextVariable(variableName, context); + } + } + + private void exitSameContextDeclaration() { + // "." means same context as parent - reuse the current top of efxContext + // This mirrors the main translator's blockStack.currentContext() behavior + if (this.efxContext.isEmpty()) { + this.exitRootContextDeclaration(); + return; + } + Context currentContext = this.efxContext.peek(); + PathExpression contextPath = currentContext.absolutePath(); + String symbol = currentContext.symbol(); + if (currentContext.isFieldContext()) { + this.efxContext.push(new FieldContext(symbol, contextPath)); + } else { + Variable variable = currentContext.variable(); + if (variable != null) { + this.efxContext.push(new NodeContext(symbol, contextPath, variable)); + } else { + this.efxContext.push(new NodeContext(symbol, contextPath)); + } + } + } + + private void exitParentContextDeclaration() { + // ".." means parent context - go one level up from current context + // This mirrors the main translator's blockStack.parentContext() behavior + Context parentContext = this.efxContext.peekParentContext(); + if (parentContext == null) { + this.exitRootContextDeclaration(); + return; + } + PathExpression contextPath = parentContext.absolutePath(); + String symbol = parentContext.symbol(); + if (parentContext.isFieldContext()) { + this.efxContext.push(new FieldContext(symbol, contextPath)); + } else { + Variable variable = parentContext.variable(); + if (variable != null) { + this.efxContext.push(new NodeContext(symbol, contextPath, variable)); + } else { + this.efxContext.push(new NodeContext(symbol, contextPath)); + } } - return; + } + + private void exitRootContextDeclaration() { + this.efxContext.push(new NodeContext(this.symbols.getRootNodeId(), this.symbols.getRootPath())); } @Override @@ -1406,7 +1677,39 @@ public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), DurationExpression.empty(), DurationExpression.empty())); } - + + // Sequence variable initializers + + @Override + public void exitStringSequenceVariableInitializer(StringSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), new StringSequenceExpression(""), new StringSequenceExpression(""))); + } + + @Override + public void exitNumericSequenceVariableInitializer(NumericSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), new NumericSequenceExpression(""), new NumericSequenceExpression(""))); + } + + @Override + public void exitBooleanSequenceVariableInitializer(BooleanSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), new BooleanSequenceExpression(""), new BooleanSequenceExpression(""))); + } + + @Override + public void exitDateSequenceVariableInitializer(DateSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), new DateSequenceExpression(""), new DateSequenceExpression(""))); + } + + @Override + public void exitTimeSequenceVariableInitializer(TimeSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), new TimeSequenceExpression(""), new TimeSequenceExpression(""))); + } + + @Override + public void exitDurationSequenceVariableInitializer(DurationSequenceVariableInitializerContext ctx) { + this.stack.push(new Variable(ctx.variableName.getText(), new DurationSequenceExpression(""), new DurationSequenceExpression(""))); + } + // #endregion Template Variables ------------------------------------------ // #region Scope management -------------------------------------------- @@ -1417,6 +1720,10 @@ public void exitDurationVariableInitializer(DurationVariableInitializerContext c public void enterTemplateLine(TemplateLineContext ctx) { final int indentLevel = EfxTemplateTranslatorV2.this.getIndentLevel(ctx.indentation()); this.enterTemplateLine(indentLevel); + // Push root context if no context declaration block (same as main translator) + if (ctx.contextDeclarationBlock() == null) { + this.exitRootContextDeclaration(); + } } @Override @@ -1449,17 +1756,23 @@ private void enterTemplateLine(final int indentLevel) { } } - @Override - public void exitTemplateDeclaration(TemplateDeclarationContext ctx) { - this.exitTemplateLine(0); - } - @Override public void exitTemplateLine(TemplateLineContext ctx) { + // Pop context for this line (same as main translator) + if (!this.efxContext.isEmpty()) { + this.efxContext.pop(); + } final int indentLevel = EfxTemplateTranslatorV2.this.getIndentLevel(ctx.indentation()); this.exitTemplateLine(indentLevel); } + @Override + public void exitTemplateDeclaration(TemplateDeclarationContext ctx) { + // Note: Template declarations don't push context (they have parameters instead of context block) + // So we don't pop context here, unlike regular template lines + this.exitTemplateLine(0); + } + private void exitTemplateLine(final int indentLevel) { final int indentChange = indentLevel - (this.levels.isEmpty() ? 0 : this.levels.peek()); assert this.stack.empty() : "Stack should be empty at this point."; @@ -1479,6 +1792,22 @@ private void exitTemplateLine(final int indentLevel) { // #endregion Scope management -------------------------------------------- + // #region Dictionary context handling ------------------------------------ + + @Override + public void exitDictionaryIndexClause(DictionaryIndexClauseContext ctx) { + // Push field context for dictionary index - same as main translator + this.efxContext.pushFieldContext(getFieldId(ctx.fieldContext())); + } + + @Override + public void exitDictionaryKeyClause(DictionaryKeyClauseContext ctx) { + // Pop the dictionary index context - same as main translator + this.efxContext.pop(); + } + + // #endregion Dictionary context handling --------------------------------- + @Override public void exitDictionaryDeclaration(DictionaryDeclarationContext ctx) { var dictionaryName = ctx.dictionaryName.getText(); diff --git a/src/main/java/eu/europa/ted/efx/sdk2/TypeCheckerV2.java b/src/main/java/eu/europa/ted/efx/sdk2/TypeCheckerV2.java new file mode 100644 index 0000000..9d2a8c6 --- /dev/null +++ b/src/main/java/eu/europa/ted/efx/sdk2/TypeCheckerV2.java @@ -0,0 +1,72 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.sdk2; + +import eu.europa.ted.efx.interfaces.TypeChecker; +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.EfxDataTypeAssociation; +import eu.europa.ted.efx.model.types.EfxTypeLattice; + +/** + * V2 strict type checker that requires concrete types. + * Rejects abstract Sequence.class and Scalar.class targets. + */ +public class TypeCheckerV2 implements TypeChecker { + + public static final TypeCheckerV2 INSTANCE = new TypeCheckerV2(); + + private TypeCheckerV2() {} + + @Override + public boolean canConvert(Class from, + Class to) { + // Direct class compatibility + if (to.isAssignableFrom(from)) { + return true; + } + + var fromAnnotation = from.getAnnotation(EfxDataTypeAssociation.class); + var toAnnotation = to.getAnnotation(EfxDataTypeAssociation.class); + assert fromAnnotation != null : "Missing @EfxDataTypeAssociation on " + from.getName(); + assert toAnnotation != null : "Missing @EfxDataTypeAssociation on " + to.getName(); + + var fromType = fromAnnotation.dataType(); + var toType = toAnnotation.dataType(); + + // V2 requires concrete types - reject abstract Sequence/Scalar + assert to != SequenceExpression.class : "Cannot convert to untyped Sequence - use a typed sequence"; + assert to != ScalarExpression.class : "Cannot convert to untyped Scalar - use a typed scalar"; + + // Direct type compatibility + if (toType.isAssignableFrom(fromType)) { + return true; + } + + // Primitive types must be compatible (from can be a subtype of to) + var fromPrimitive = EfxTypeLattice.toPrimitive(fromType); + var toPrimitive = EfxTypeLattice.toPrimitive(toType); + if (!toPrimitive.isAssignableFrom(fromPrimitive)) { + return false; + } + + // Scalar can promote to Sequence + if (EfxTypeLattice.isScalar(fromType) && EfxTypeLattice.isSequence(toType)) { + return true; + } + + return false; + } +} 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 deleted file mode 100644 index 1794738..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkCodelistV2.java +++ /dev/null @@ -1,21 +0,0 @@ -package eu.europa.ted.efx.sdk2.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.efx.sdk1.entity.SdkCodelistV1; - -/** - * Representation of an SdkCodelist for usage in the symbols map. - * - * @author rouschr - */ -@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); - } -} 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 deleted file mode 100644 index 92e9217..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkFieldV2.java +++ /dev/null @@ -1,41 +0,0 @@ -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; -import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; - -@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 alias) { - super(id, type, parentNodeId, xpathAbsolute, xpathRelative, rootCodelistId); - this.alias = alias; - } - - public SdkFieldV2(JsonNode fieldNode) { - super(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/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java deleted file mode 100644 index 9865a63..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNodeV2.java +++ /dev/null @@ -1,29 +0,0 @@ -package eu.europa.ted.efx.sdk2.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.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; - - 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 String getAlias() { - return alias; - } -} diff --git a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNoticeSubtypeV2.java b/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNoticeSubtypeV2.java deleted file mode 100644 index 337d543..0000000 --- a/src/main/java/eu/europa/ted/efx/sdk2/entity/SdkNoticeSubtypeV2.java +++ /dev/null @@ -1,21 +0,0 @@ -package eu.europa.ted.efx.sdk2.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.efx.sdk1.entity.SdkNoticeSubtypeV1; - -/** - * Represents a notice subtype from the SDK's notice-types.json file. - */ -@SdkComponent(versions = {"2"}, componentType = SdkComponentType.NOTICE_TYPE) -public class SdkNoticeSubtypeV2 extends SdkNoticeSubtypeV1 { - - public SdkNoticeSubtypeV2(String subTypeId, String documentType, String type) { - super(subTypeId, documentType, type); - } - - public SdkNoticeSubtypeV2(JsonNode json) { - super(json); - } -} 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 9f95c1c..cfd0eb5 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -1,13 +1,27 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.xpath; import eu.europa.ted.eforms.xpath.XPathProcessor; -import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.PathExpression; public class XPathContextualizer { /** * Makes the given xpath relative to the given context xpath. - * + * * @param contextXpath the context xpath * @param xpath the xpath to contextualize * @return the contextualized xpath @@ -22,13 +36,13 @@ public static PathExpression contextualize(final PathExpression contextXpath, String result = XPathProcessor.contextualize(contextXpath.getScript(), xpath.getScript()); - return PathExpression.instantiate(result, xpath.getDataType()); + return Expression.instantiate(result, xpath.getClass()); } public static PathExpression join(final PathExpression first, final PathExpression second) { String joinedXPath = XPathProcessor.join(first.getScript(), second.getScript()); - return PathExpression.instantiate(joinedXPath, second.getDataType()); + return Expression.instantiate(joinedXPath, second.getClass()); } } 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 8cb20d7..3bd4a92 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.xpath; import static java.util.Map.entry; @@ -18,18 +31,25 @@ import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.TranslatorOptions; import eu.europa.ted.efx.model.expressions.Expression; +import eu.europa.ted.efx.model.expressions.PathExpression; 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.LiteralExpression; import eu.europa.ted.efx.model.expressions.scalar.BooleanExpression; +import eu.europa.ted.efx.model.expressions.scalar.BooleanLiteral; import eu.europa.ted.efx.model.expressions.scalar.DateExpression; +import eu.europa.ted.efx.model.expressions.scalar.DateLiteral; import eu.europa.ted.efx.model.expressions.scalar.DurationExpression; +import eu.europa.ted.efx.model.expressions.scalar.DurationLiteral; +import eu.europa.ted.efx.model.expressions.scalar.NodePath; import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericLiteral; 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.StringLiteral; import eu.europa.ted.efx.model.expressions.scalar.TimeExpression; +import eu.europa.ted.efx.model.expressions.scalar.TimeLiteral; 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; @@ -63,38 +83,38 @@ public XPathScriptGenerator(TranslatorOptions translatorOptions) { @Override public PathExpression composeNodeReferenceWithPredicate(PathExpression nodeReference, BooleanExpression predicate) { - return PathExpression.instantiate(nodeReference.getScript() + '[' + predicate.getScript() + ']', EfxDataType.Node.class); + return Expression.instantiate(nodeReference.getScript() + '[' + predicate.getScript() + ']', nodeReference.getClass()); } @Override public PathExpression composeFieldReferenceWithPredicate(PathExpression fieldReference, BooleanExpression predicate) { - return PathExpression.instantiate(fieldReference.getScript() + '[' + predicate.getScript() + ']', fieldReference.getDataType()); + return Expression.instantiate(fieldReference.getScript() + '[' + predicate.getScript() + ']', fieldReference.getClass()); } @Override public PathExpression composeFieldReferenceWithAxis(final PathExpression fieldReference, final String axis) { String resultXPath = XPathProcessor.addAxis(axis, fieldReference.getScript()); - return PathExpression.instantiate(resultXPath, fieldReference.getDataType()); + return Expression.instantiate(resultXPath, fieldReference.getClass()); } @Override public PathExpression composeFieldValueReference(PathExpression fieldReference) { if (fieldReference.is(EfxDataType.String.class)) { - return PathExpression.instantiate(fieldReference.getScript() + "/normalize-space(text())", fieldReference.getDataType()); + return Expression.instantiate(fieldReference.getScript() + "/normalize-space(text())", fieldReference.getClass()); } if (fieldReference.is(EfxDataType.Number.class)) { - return PathExpression.instantiate(fieldReference.getScript() + "/number()", fieldReference.getDataType()); + return Expression.instantiate(fieldReference.getScript() + "/number()", fieldReference.getClass()); } if (fieldReference.is(EfxDataType.Date.class)) { - return PathExpression.instantiate(fieldReference.getScript() + "/xs:date(text())", fieldReference.getDataType()); + return Expression.instantiate(fieldReference.getScript() + "/xs:date(text())", fieldReference.getClass()); } if (fieldReference.is(EfxDataType.Time.class)) { - return PathExpression.instantiate(fieldReference.getScript() + "/xs:time(text())", fieldReference.getDataType()); + return Expression.instantiate(fieldReference.getScript() + "/xs:time(text())", fieldReference.getClass()); } if (fieldReference.is(EfxDataType.Duration.class)) { - return PathExpression.instantiate("(for $F in " + fieldReference.getScript() + " return (if ($F/@unitCode='WEEK')" + // + return Expression.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'))" + // @@ -104,10 +124,10 @@ public PathExpression composeFieldValueReference(PathExpression fieldReference) " then xs:yearMonthDuration(concat('P', $F/number(), 'M'))" + // // " else if (" + fieldReference.script + ")" + // // " then fn:error('Invalid @unitCode')" + // - " else ()))", fieldReference.getDataType()); + " else ()))", fieldReference.getClass()); } - return PathExpression.instantiate(fieldReference.getScript(), fieldReference.getDataType()); + return Expression.instantiate(fieldReference.getScript(), fieldReference.getClass()); } @Override @@ -161,41 +181,41 @@ public T composeList(List iterators) { return new IteratorListExpression( @@ -269,7 +283,7 @@ public T composeParenthesizedExpression(T expression, Cla @Override public PathExpression composeExternalReference(StringExpression externalReference) { - return new NodePathExpression( + return new NodePath( "fn:doc(concat($urlPrefix, " + externalReference.getScript() + "))"); } @@ -277,7 +291,7 @@ public PathExpression composeExternalReference(StringExpression externalReferenc @Override public PathExpression composeFieldInExternalReference(PathExpression externalReference, PathExpression fieldReference) { - return PathExpression.instantiate(externalReference.getScript() + fieldReference.getScript(), fieldReference.getDataType()); + return Expression.instantiate(externalReference.getScript() + fieldReference.getScript(), fieldReference.getClass()); } @@ -286,6 +300,12 @@ public PathExpression joinPaths(final PathExpression first, final PathExpression return XPathContextualizer.join(first, second); } + @Override + public PathExpression contextualizePath(final PathExpression absolutePath, + final PathExpression contextPath) { + return XPathContextualizer.contextualize(contextPath, absolutePath); + } + //#region Indexers ---------------------------------------------------------- @Override @@ -451,13 +471,13 @@ public StringExpression composeStringJoin(StringSequenceExpression list, StringE @Override public StringExpression composeNumberFormatting(NumericExpression number, StringExpression format) { - String formatString = format.isLiteral() ? this.translatorOptions.getDecimalFormat().adaptFormatString(format.getScript()) : format.getScript(); + String formatString = format instanceof LiteralExpression ? this.translatorOptions.getDecimalFormat().adaptFormatString(format.getScript()) : format.getScript(); return new StringExpression("format-number(" + number.getScript() + ", " + formatString + ")"); } @Override - public StringExpression getStringLiteralFromUnquotedString(String value) { - return new StringExpression("'" + value + "'", true); + public StringLiteral getStringLiteralFromUnquotedString(String value) { + return new StringLiteral("'" + value + "'"); } @Override diff --git a/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java b/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java deleted file mode 100644 index 4799ba4..0000000 --- a/src/test/java/eu/europa/ted/efx/mock/AbstractSymbolResolverMock.java +++ /dev/null @@ -1,174 +0,0 @@ -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.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.exceptions.SymbolResolutionException; -import eu.europa.ted.efx.interfaces.SymbolResolver; -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 - 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 SymbolResolutionException.unknownField(fieldId); - } - return sdkField.getType(); - } - - @Override - public String getRootCodelistOfField(final String fieldId) { - final SdkField sdkField = getFieldById(fieldId); - if (sdkField == null) { - throw SymbolResolutionException.unknownField(fieldId); - } - final String codelistId = sdkField.getCodelistId(); - if (codelistId == null) { - throw SymbolResolutionException.noCodelistForField(fieldId); - } - - final SdkCodelist sdkCodelist = getCodelistById(codelistId); - if (sdkCodelist == null) { - throw SymbolResolutionException.unknownCodelist(codelistId); - } - - return sdkCodelist.getRootCodelistId(); - } - - @Override - public List expandCodelist(String codelistId) { - SdkCodelist codelist = getCodelistById(codelistId); - if (codelist == null) { - throw SymbolResolutionException.unknownCodelist(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 SymbolResolutionException.unknownField(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 SymbolResolutionException.unknownField(fieldId); - } - return PathExpression.instantiate(sdkField.getXpathAbsolute(), FieldTypes.fromString(sdkField.getType())); - } - - /** - * @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 SymbolResolutionException.unknownNode(nodeId); - } - - 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 be99529..8db2788 100644 --- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java @@ -1,3 +1,16 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.mock; import java.util.List; @@ -11,16 +24,18 @@ import eu.europa.ted.efx.interfaces.Parameter; import eu.europa.ted.efx.interfaces.TranslatorContext; import eu.europa.ted.efx.model.expressions.Expression; -import eu.europa.ted.efx.model.expressions.path.PathExpression; +import eu.europa.ted.efx.model.expressions.PathExpression; +import eu.europa.ted.efx.model.expressions.TypedExpression; 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.Conditional; import eu.europa.ted.efx.model.templates.Markup; import eu.europa.ted.efx.model.types.EfxDataType; +import eu.europa.ted.efx.model.types.EfxTypeLattice; public class MarkupGeneratorMock implements MarkupGenerator { - static final Map, Markup> typeFromEfxDataType = Map + static final Map, Markup> typeFromEfxDataType = Map .ofEntries( Map.entry(EfxDataType.String.class, new Markup("string")), // Map.entry(EfxDataType.MultilingualString.class, new Markup("string")), // @@ -34,19 +49,33 @@ public class MarkupGeneratorMock implements MarkupGenerator { @Override - public Markup renderVariableDeclaration(Class dataType, String variableName, - Expression initialiser) { - return new Markup(String.format("%s:%s=%s", this.getEfxDataTypeEquivalent(dataType).script, variableName, initialiser.getScript())); + public Markup renderVariableDeclaration(String variableName, TypedExpression initialiser) { + Class dataType = initialiser.getDataType(); + String typeName = this.getEfxDataTypeEquivalent(EfxTypeLattice.toPrimitive(dataType)).script; + if (EfxTypeLattice.isSequence(dataType)) { + typeName += "*"; + } + return new Markup(String.format("%s:%s=%s", typeName, variableName, initialiser.getScript())); } @Override - public Markup renderFunctionDeclaration(Class type, String name, Map> parameters, - Expression expression) { + public Markup renderFunctionDeclaration(String name, Map> parameters, TypedExpression expression) { + Class type = expression.getDataType(); + String typeName = this.getEfxDataTypeEquivalent(EfxTypeLattice.toPrimitive(type)).script; + if (EfxTypeLattice.isSequence(type)) { + typeName += "*"; + } return new Markup( - String.format("%s:%s(%s) -> { %s }", this.getEfxDataTypeEquivalent(type).script, name, + String.format("%s:%s(%s) -> { %s }", typeName, name, parameters.entrySet().stream() - .map(entry -> this.getEfxDataTypeEquivalent(entry.getValue()).script + ":" + entry.getKey()) - .collect(Collectors.joining(", ")), + .map(entry -> { + String paramType = this.getEfxDataTypeEquivalent(EfxTypeLattice.toPrimitive(entry.getValue())).script; + if (EfxTypeLattice.isSequence(entry.getValue())) { + paramType += "*"; + } + return paramType + ":" + entry.getKey(); + }) + .collect(Collectors.joining(", ")), expression.getScript())); } @@ -182,7 +211,8 @@ private String renderSection(String heading, List markupList) { @Override public Markup getEfxDataTypeEquivalent(Class type) { - return typeFromEfxDataType.getOrDefault(type, Markup.empty()); + // Use toPrimitive() to handle both scalar and sequence types + return typeFromEfxDataType.getOrDefault(EfxTypeLattice.toPrimitive(type), Markup.empty()); } @Override 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 c0408de..b985ee1 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,102 +1,87 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.mock.sdk1; import static java.util.Map.entry; -import java.io.IOException; +import java.nio.file.Path; 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 eu.europa.ted.eforms.xpath.XPathInfo; -import eu.europa.ted.eforms.xpath.XPathProcessor; -import eu.europa.ted.efx.mock.AbstractSymbolResolverMock; -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.sdk1.entity.SdkCodelistV1; -import eu.europa.ted.efx.sdk1.entity.SdkFieldV1; -import eu.europa.ted.efx.sdk1.entity.SdkNodeV1; +import eu.europa.ted.eforms.sdk.SdkSymbolResolver; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.SdkCodelist; +import eu.europa.ted.eforms.sdk.entity.v1.SdkCodelistV1; +import eu.europa.ted.eforms.sdk.repository.SdkFieldRepository; +import eu.europa.ted.eforms.sdk.repository.SdkNodeRepository; -public class SymbolResolverMockV1 - extends AbstractSymbolResolverMock { +@SdkComponent(versions = {"1"}, componentType = SdkComponentType.SYMBOL_RESOLVER, qualifier = "mock") +public class SymbolResolverMockV1 extends SdkSymbolResolver { - public SymbolResolverMockV1() throws IOException { + private static final String SDK_VERSION = "eforms-sdk-1.0"; + private static final Path JSON_PATH = Path.of("src", "test", "resources", "json", "sdk1-fields.json"); + + public SymbolResolverMockV1() throws InstantiationException { super(); + loadTestData(); } - private static Entry buildCodelistMock(final String codelistId, + private void loadTestData() throws InstantiationException { + // Load nodes and fields using SDK repositories + this.nodeById = new SdkNodeRepository(SDK_VERSION, JSON_PATH); + this.nodeByAlias = new HashMap<>(); // SDK1 doesn't support aliases + + this.fieldById = new SdkFieldRepository(SDK_VERSION, JSON_PATH, this.nodeById); + this.fieldByAlias = new HashMap<>(); // SDK1 doesn't support aliases + + // Mock codelists + this.codelistById = createMockCodelists(); + + // Mock notice types - not needed, we override getAllNoticeSubtypeIds() + this.noticeTypesById = new HashMap<>(); + } + + 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)); } - @Override - 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))); - } - - @Override - protected Map createCodelistById() { + private Map createMockCodelists() { return new HashMap<>(Map.ofEntries( buildCodelistMock("accessibility", Optional.empty()), buildCodelistMock("authority-activity", Optional.of("main-activity")), buildCodelistMock("main-activity", Optional.empty()))); } - @Override - protected Class getSdkFieldClass() { - return SdkFieldV1.class; - } - - @Override - protected String getFieldsJsonFilename() { - return "fields-sdk1.json"; - } - - @Override - public boolean isAttributeField(final String fieldId) { - XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript()); - return xpathInfo.isAttribute(); - } - - @Override - public String getAttributeNameFromAttributeField(String fieldId) { - XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript()); - return xpathInfo.getAttributeName(); - } - - @Override - public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) { - XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript()); - return Expression.instantiate(xpathInfo.getPathToLastElement(), NodePathExpression.class); - } - @Override public String getFieldIdFromAlias(String alias) { - throw new UnsupportedOperationException( - "Alias resolution is not supported in SDK-1."); + throw new UnsupportedOperationException("Alias resolution is not supported in SDK-1."); } @Override public String getNodeIdFromAlias(String alias) { - throw new UnsupportedOperationException( - "Alias resolution is not supported in SDK-1."); + throw new UnsupportedOperationException("Alias resolution is not supported in SDK-1."); } - /** - * Returns a list of all valid notice type IDs for testing. - * Uses a small set that covers all testing scenarios: - * - Numeric types (1-5) for basic testing and ranges - * - Alphanumeric types (E1, E2, X01) for non-numeric ID testing - */ @Override - public java.util.List getAllNoticeSubtypeIds() { - return java.util.Arrays.asList("1", "2", "3", "4", "5", "E1", "E2", "X01"); + public List getAllNoticeSubtypeIds() { + return Arrays.asList("1", "2", "3", "4", "5", "E1", "E2", "X01"); } - } 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 3bf2860..73a24d0 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,73 +1,69 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.mock.sdk2; import static java.util.Map.entry; -import java.io.IOException; +import java.nio.file.Path; 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 eu.europa.ted.eforms.xpath.XPathInfo; -import eu.europa.ted.eforms.xpath.XPathProcessor; -import eu.europa.ted.efx.mock.AbstractSymbolResolverMock; -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.sdk2.entity.SdkCodelistV2; -import eu.europa.ted.efx.sdk2.entity.SdkFieldV2; -import eu.europa.ted.efx.sdk2.entity.SdkNodeV2; +import eu.europa.ted.eforms.sdk.SdkSymbolResolver; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.SdkCodelist; +import eu.europa.ted.eforms.sdk.entity.v2.SdkCodelistV2; +import eu.europa.ted.eforms.sdk.repository.SdkFieldRepository; +import eu.europa.ted.eforms.sdk.repository.SdkNodeRepository; -public class SymbolResolverMockV2 - extends AbstractSymbolResolverMock { - protected Map fieldByAlias; - protected Map nodeByAlias; +@SdkComponent(versions = {"2"}, componentType = SdkComponentType.SYMBOL_RESOLVER, qualifier = "mock") +public class SymbolResolverMockV2 extends SdkSymbolResolver { - public SymbolResolverMockV2() throws IOException { - super(); - } + private static final String SDK_VERSION = "eforms-sdk-2.0"; + private static final Path JSON_PATH = Path.of("src", "test", "resources", "json", "sdk2-fields.json"); - private static Entry buildCodelistMock(final String codelistId, - final Optional parentId) { - return entry(codelistId, new SdkCodelistV2(codelistId, "0.0.1", - Arrays.asList("code1", "code2", "code3"), parentId)); + public SymbolResolverMockV2() throws InstantiationException { + super(); + loadTestData(); } - @Override - public void loadMapData() throws IOException { - super.loadMapData(); + private void loadTestData() throws InstantiationException { + // Load nodes and fields using SDK repositories + this.nodeById = new SdkNodeRepository(SDK_VERSION, JSON_PATH); + this.nodeByAlias = indexNodesByAlias(); - this.fieldByAlias = this.fieldById.entrySet().stream() - .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); + this.fieldById = new SdkFieldRepository(SDK_VERSION, JSON_PATH, this.nodeById); + this.fieldByAlias = indexFieldsByAlias(); - this.nodeByAlias = this.nodeById.entrySet().stream() - .collect(Collectors.toMap(e -> e.getValue().getAlias(), e -> e.getValue())); - } - - @Override - public SdkFieldV2 getFieldById(final String fieldId) { - return this.fieldById.containsKey(fieldId) ? this.fieldById.get(fieldId) - : this.fieldByAlias.get(fieldId); - } + // Mock codelists + this.codelistById = createMockCodelists(); - @Override - public SdkNodeV2 getNodeById(final String nodeId) { - return this.nodeById.containsKey(nodeId) ? this.nodeById.get(nodeId) - : this.nodeByAlias.get(nodeId); + // Mock notice types - not needed, we override getAllNoticeSubtypeIds() + this.noticeTypesById = new HashMap<>(); } - @Override - 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")), - entry("ND-SubSubNode", new SdkNodeV2("ND-SubSubNode", "ND-SubNode", "/*/SubNode/SubSubNode", "SubSubNode", false, "SubSubNode"))); + private static Entry buildCodelistMock(final String codelistId, + final Optional parentId) { + return entry(codelistId, new SdkCodelistV2(codelistId, "0.0.1", + Arrays.asList("code1", "code2", "code3"), parentId)); } - @Override - protected Map createCodelistById() { + private Map createMockCodelists() { return new HashMap<>(Map.ofEntries( buildCodelistMock("accessibility", Optional.empty()), buildCodelistMock("authority-activity", Optional.of("main-activity")), @@ -75,58 +71,7 @@ protected Map createCodelistById() { } @Override - protected Class getSdkFieldClass() { - return SdkFieldV2.class; - } - - @Override - protected String getFieldsJsonFilename() { - return "fields-sdk2.json"; - } - - @Override - public boolean isAttributeField(final String fieldId) { - XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript()); - return xpathInfo.isAttribute(); - } - - @Override - public String getAttributeNameFromAttributeField(String fieldId) { - XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript()); - return xpathInfo.getAttributeName(); - } - - @Override - public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) { - XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript()); - return Expression.instantiate(xpathInfo.getPathToLastElement(), NodePathExpression.class); - } - - @Override - public String getFieldIdFromAlias(String alias) { - if (this.fieldByAlias.containsKey(alias)) { - return this.fieldByAlias.get(alias).getId(); - } - return null; - - } - - @Override - public String getNodeIdFromAlias(String alias) { - if (this.nodeByAlias.containsKey(alias)) { - return this.nodeByAlias.get(alias).getId(); - } - return null; - } - - /** - * Returns a list of all valid notice type IDs for testing. - * Uses a small set that covers all testing scenarios: - * - Numeric types (1-5) for basic testing and ranges - * - Alphanumeric types (E1, E2, X01) for non-numeric ID testing - */ - @Override - public java.util.List getAllNoticeSubtypeIds() { - return java.util.Arrays.asList("1", "2", "3", "4", "5", "E1", "E2", "X01"); + public List getAllNoticeSubtypeIds() { + return Arrays.asList("1", "2", "3", "4", "5", "E1", "E2", "X01"); } } diff --git a/src/test/java/eu/europa/ted/efx/model/types/EfxTypeLatticeTest.java b/src/test/java/eu/europa/ted/efx/model/types/EfxTypeLatticeTest.java new file mode 100644 index 0000000..9a92bf4 --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/model/types/EfxTypeLatticeTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.model.types; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.reflect.Field; +import java.util.List; + +import org.junit.jupiter.api.Test; + +class EfxTypeLatticeTest { + + /** + * Verifies that all concrete EfxDataType types (those implementing Cardinality.Scalar or Cardinality.Sequence) + * are registered in EfxTypeLattice.TYPE_VARIANTS. + * + * This test catches "forgot to register" errors at build time when adding new types. + */ + @Test + void allConcreteTypesShouldBeRegistered() { + for (Class nested : EfxDataType.class.getDeclaredClasses()) { + // Skip marker interfaces (Cardinality, Primitive, ConcreteScalar, ConcreteSequence) + if (nested == EfxDataType.Cardinality.class + || nested == EfxDataType.Primitive.class + || nested == EfxDataType.ConcreteScalar.class + || nested == EfxDataType.ConcreteSequence.class) { + continue; + } + + boolean isScalar = EfxDataType.Cardinality.Scalar.class.isAssignableFrom(nested); + boolean isSequence = EfxDataType.Cardinality.Sequence.class.isAssignableFrom(nested); + + if (isScalar || isSequence) { + assertTrue( + EfxTypeLattice.isRegistered(nested), + "Concrete type " + nested.getSimpleName() + " not registered in TYPE_VARIANTS" + ); + } + } + } + + /** + * Verifies that TYPE_VARIANTS is ordered correctly: subtypes must come before their supertypes. + * This is critical because the lookup methods use isAssignableFrom() which would match the wrong type + * if supertypes came first. + * + * For example, MultilingualString extends String, so MultilingualString must appear before String + * in TYPE_VARIANTS, otherwise a MultilingualString type would incorrectly match String. + */ + @Test + @SuppressWarnings("unchecked") + void typeVariantsShouldBeOrderedWithSubtypesBeforeSupertypes() throws Exception { + // Use reflection to access the private TYPE_VARIANTS field + Field typeVariantsField = EfxTypeLattice.class.getDeclaredField("TYPE_VARIANTS"); + typeVariantsField.setAccessible(true); + List typeVariants = (List) typeVariantsField.get(null); + + // Get the TypeVariants inner class + Class typeVariantsClass = Class.forName("eu.europa.ted.efx.model.types.EfxTypeLattice$TypeVariants"); + Field primitiveField = typeVariantsClass.getDeclaredField("primitive"); + primitiveField.setAccessible(true); + + // Check each pair: earlier types should not be supertypes of later types + for (int i = 0; i < typeVariants.size(); i++) { + Object currentVariant = typeVariants.get(i); + Class currentPrimitive = (Class) primitiveField.get(currentVariant); + + for (int j = i + 1; j < typeVariants.size(); j++) { + Object laterVariant = typeVariants.get(j); + Class laterPrimitive = (Class) primitiveField.get(laterVariant); + + // Check if the current type is a supertype of the later type + // This would be a violation: supertypes must come AFTER subtypes + if (currentPrimitive.isAssignableFrom(laterPrimitive) + && !currentPrimitive.equals(laterPrimitive)) { + fail(String.format( + "TYPE_VARIANTS ordering violation: %s (at index %d) is a supertype of %s (at index %d). " + + "Supertypes must come after their subtypes to ensure correct matching with isAssignableFrom(). " + + "Please move %s after %s in the TYPE_VARIANTS list.", + currentPrimitive.getSimpleName(), i, + laterPrimitive.getSimpleName(), j, + laterPrimitive.getSimpleName(), + currentPrimitive.getSimpleName())); + } + } + } + } +} 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 c0ce95a..00d5d4c 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1,3 +1,16 @@ +/* + * Copyright 2023 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.sdk2; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -5,6 +18,8 @@ import org.antlr.v4.runtime.misc.ParseCancellationException; import org.junit.jupiter.api.Test; import eu.europa.ted.efx.EfxTestsBase; +import eu.europa.ted.efx.exceptions.InvalidIdentifierException; +import eu.europa.ted.efx.exceptions.TypeMismatchException; class EfxExpressionTranslatorV2Test extends EfxTestsBase { @Override @@ -543,6 +558,15 @@ void testStringsFromStringIteration_UsingFieldReference() { "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"); } + @Test + void testStringsFromStringIteration_UsingMultilingualFieldReference() { + // This test verifies that iterating over a multilingual text field with a text iterator works correctly + // The iterator variable should inherit the actual type (multilingual string) from the sequence + testExpressionTranslationWithContext( + "'a' = (for $x in PathNode/TextMultilingualField/normalize-space(text()) return concat($x, 'text'))", "ND-Root", + "'a' in (for text:$x in BT-00-Text-Multilingual return concat($x, 'text'))"); + } + @Test void testStringsFromBooleanIteration_UsingLiterals() { @@ -1679,6 +1703,42 @@ void testParameterizedExpression_WithDurationParameter() { "{ND-Root, measure:$p1, measure:$p2} ${$p1 == $p2}", "P1Y", "P2Y"); } + @Test + void testParameterizedExpression_WithTextSequenceParameter() { + testExpressionTranslation("count(('a','b','c'))", + "{ND-Root, text*:$items} ${count($items)}", "('a', 'b', 'c')"); + } + + @Test + void testParameterizedExpression_WithNumericSequenceParameter() { + testExpressionTranslation("count((1,2,3))", + "{ND-Root, number*:$items} ${count($items)}", "(1, 2, 3)"); + } + + @Test + void testParameterizedExpression_WithBooleanSequenceParameter() { + testExpressionTranslation("count((true(),false(),true()))", + "{ND-Root, indicator*:$items} ${count($items)}", "(TRUE, FALSE, ALWAYS)"); + } + + @Test + void testParameterizedExpression_WithDateSequenceParameter() { + testExpressionTranslation("count((xs:date('2024-01-01Z'),xs:date('2024-12-31Z')))", + "{ND-Root, date*:$items} ${count($items)}", "(2024-01-01Z, 2024-12-31Z)"); + } + + @Test + void testParameterizedExpression_WithTimeSequenceParameter() { + testExpressionTranslation("count((xs:time('10:00:00Z'),xs:time('18:00:00Z')))", + "{ND-Root, time*:$items} ${count($items)}", "(10:00:00Z, 18:00:00Z)"); + } + + @Test + void testParameterizedExpression_WithDurationSequenceParameter() { + testExpressionTranslation("count((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')))", + "{ND-Root, measure*:$items} ${count($items)}", "(P1Y, P2M)"); + } + // #endregion: Compare sequences // #endregion Sequence Functions @@ -1686,15 +1746,35 @@ void testParameterizedExpression_WithDurationParameter() { // #region: Indexers -------------------------------------------------------- @Test - void testIndexer_WithFieldReference() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text())[1]", "ND-Root", "(text)BT-00-Text[1]"); + void testIndexer_WithNonRepeatableField() { + // Indexing a non-repeatable field - currently allowed (semantically odd but syntactically valid) + testExpressionTranslationWithContext("(PathNode/TextField/normalize-space(text()))[1]", "ND-Root", + "BT-00-Text[1]"); + } + + @Test + void testIndexer_WithNonRepeatableFieldAndPredicate() { + // Indexing a non-repeatable field with predicate + testExpressionTranslationWithContext( + "(PathNode/TextField[./normalize-space(text()) = 'hello']/normalize-space(text()))[1]", "ND-Root", + "BT-00-Text[BT-00-Text == 'hello'][1]"); } @Test - void testIndexer_WithFieldReferenceAndPredicate() { + void testIndexer_WithRepeatableField() { + // Indexing a repeatable field should work - BT-00-Repeatable-Text is repeatable + // No explicit cast - preprocessor inserts (text*) making it stringSequence[indexer] + testExpressionTranslationWithContext("(PathNode/RepeatableTextField/normalize-space(text()))[1]", "ND-Root", + "BT-00-Repeatable-Text[1]"); + } + + @Test + void testIndexer_WithRepeatableFieldAndPredicate() { + // Indexing a repeatable field with predicate should work + // No explicit cast - preprocessor handles typing testExpressionTranslationWithContext( - "PathNode/TextField[./normalize-space(text()) = 'hello']/normalize-space(text())[1]", "ND-Root", - "(text)BT-00-Text[BT-00-Text == 'hello'][1]"); + "(PathNode/RepeatableTextField[(./normalize-space(text()))[1] = 'hello']/normalize-space(text()))[1]", "ND-Root", + "BT-00-Repeatable-Text[BT-00-Repeatable-Text[1] == 'hello'][1]"); } @Test @@ -1703,4 +1783,292 @@ void testIndexer_WithTextSequence() { } // #endregion: Indexers + + // #region: Scalar/Sequence Validation -------------------------------------- + + @Test + void testScalarFromRepeatableField_ThrowsError() { + // A repeatable field used as scalar should throw TypeMismatchException.fieldMayRepeat() + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "BT-00-Repeatable-Text == 'test'")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_SEQUENCE, ex.getErrorCode()); + } + + @Test + void testScalarFromFieldInRepeatableNode_ThrowsErrorFromRootContext() { + // Field in ND-RepeatableNode (repeatable) used as scalar from ND-Root should throw + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "BT-00-Text-In-Repeatable-Node == 'test'")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_SEQUENCE, ex.getErrorCode()); + } + + @Test + void testScalarFromFieldInRepeatableNode_OkFromSameContext() { + // Field in ND-RepeatableNode used as scalar from ND-RepeatableNode context should NOT throw + testExpressionTranslationWithContext("TextField/normalize-space(text()) = 'test'", + "ND-RepeatableNode", "BT-00-Text-In-Repeatable-Node == 'test'"); + } + + @Test + void testScalarFromFieldInNestedRepeatableNode_ThrowsErrorFromRootContext() { + // Field in ND-RepeatableSubSubNode (inside ND-NonRepeatableSubNode inside ND-RepeatableNode) used from root should throw + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "BT-00-Text-In-RepeatableSubSubNode == 'test'")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_SEQUENCE, ex.getErrorCode()); + } + + @Test + void testScalarFromFieldInNestedRepeatableNode_ThrowsErrorFromRepeatableNodeContext() { + // Field in ND-RepeatableSubSubNode used from ND-RepeatableNode should still throw (ND-RepeatableSubSubNode is also repeatable) + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-RepeatableNode", "BT-00-Text-In-RepeatableSubSubNode == 'test'")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_SEQUENCE, ex.getErrorCode()); + } + + @Test + void testScalarFromFieldInNestedRepeatableNode_OkFromRepeatableSubSubNodeContext() { + // Field in ND-RepeatableSubSubNode used from ND-RepeatableSubSubNode context should NOT throw + testExpressionTranslationWithContext("TextField/normalize-space(text()) = 'test'", + "ND-RepeatableSubSubNode", "BT-00-Text-In-RepeatableSubSubNode == 'test'"); + } + + @Test + void testScalarFromFieldInNonRepeatableNestedInRepeatable_ThrowsErrorFromRootContext() { + // Field in ND-NonRepeatableSubNode (non-repeatable) inside ND-RepeatableNode (repeatable) used from root should throw + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "BT-00-Text-In-NonRepeatableSubNode == 'test'")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_SEQUENCE, ex.getErrorCode()); + } + + @Test + void testScalarFromFieldInNonRepeatableNestedInRepeatable_OkFromRepeatableNodeContext() { + // Field in ND-NonRepeatableSubNode used from ND-RepeatableNode context should NOT throw + testExpressionTranslationWithContext("NonRepeatableSubNode/TextField/normalize-space(text()) = 'test'", + "ND-RepeatableNode", "BT-00-Text-In-NonRepeatableSubNode == 'test'"); + } + + // #endregion: Scalar/Sequence Validation + + // #region: InvalidIdentifierException Tests -------------------------------- + + @Test + void testContextSpecifier_WithRegularVariable_ThrowsNotAContextVariable() { + // A regular variable (not declared with context:) used as context specifier should throw + InvalidIdentifierException ex = assertThrows(InvalidIdentifierException.class, + () -> translateExpressionWithContext("ND-Root", "for text:$x in BT-00-Text return $x::BT-00-Number")); + assertEquals(InvalidIdentifierException.ErrorCode.NOT_A_CONTEXT_VARIABLE, ex.getErrorCode()); + } + + @Test + void testContextSpecifier_UndeclaredVariable_ThrowsNotAContextVariable() { + // An undeclared variable used as context specifier should throw (NOT_A_CONTEXT_VARIABLE + // because the lookup for context variables fails before checking if the variable exists) + InvalidIdentifierException ex = assertThrows(InvalidIdentifierException.class, + () -> translateExpressionWithContext("ND-Root", "$undefined::BT-00-Text")); + assertEquals(InvalidIdentifierException.ErrorCode.NOT_A_CONTEXT_VARIABLE, ex.getErrorCode()); + } + + // #endregion: InvalidIdentifierException Tests + + // #region: TypeMismatchException - nodeCannotBeValue ------------------------- + + @Test + void testScalarFromNodeContextVariable_ThrowsNodeCannotBeValue() { + // A node context variable used as scalar value should throw + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "for context:$n in ND-SubNode return $n == 'test'")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_FIELD_CONTEXT, ex.getErrorCode()); + } + + @Test + void testSequenceFromNodeContextVariable_ThrowsNodeCannotBeValue() { + // A node context variable used in count() should throw + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "for context:$n in ND-SubNode return count($n)")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_FIELD_CONTEXT, ex.getErrorCode()); + } + + // #endregion: TypeMismatchException - nodeCannotBeValue + + // #region: TypeMismatchException - fieldMayRepeat (Context Variables) -------- + + @Test + void testScalarFromFieldContextVariable_NonRepeatable_Works() { + // A non-repeatable field context variable used as scalar should work + testExpressionTranslationWithContext( + "for $f in PathNode/TextField return $f = 'test'", + "ND-Root", + "for context:$f in BT-00-Text return $f == 'test'"); + } + + @Test + void testScalarFromFieldContextVariable_Repeatable_ThrowsFieldMayRepeat() { + // A repeatable field context variable used as scalar should throw + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "for context:$f in BT-00-Repeatable-Text return $f == 'test'")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_SEQUENCE, ex.getErrorCode()); + } + + // #endregion: TypeMismatchException - fieldMayRepeat (Context Variables) + + // #region: Context Override Syntax Tests ------------------------------------ + + @Test + void testContextOverride_NodeContextVariable_Works() { + // A node context variable used with context specifier syntax should work + testExpressionTranslationWithContext( + "for $n in SubNode return $n/SubTextField/normalize-space(text())", + "ND-Root", + "for context:$n in ND-SubNode return $n::BT-01-SubNode-Text"); + } + + // Note: Test 4.2 (regular variable as context specifier) is already covered by + // testContextSpecifier_WithRegularVariable_ThrowsNotAContextVariable above + + // #endregion: Context Override Syntax Tests + + // #region: For Loop Iterator Sequence Validation ----------------------------- + // These tests verify that repeatable fields are correctly allowed when used as + // sequences in for loop iterators, even though they would fail as scalars in return. + + @Test + void testForLoopIterator_RepeatableFieldAsSequence_Works() { + // A repeatable field used as a sequence in a for loop iterator should work. + // The return expression uses the iterator variable which is scalar. + // This is the correct way to handle repeatable fields in for loops. + testExpressionTranslationWithContext( + "for $b in PathNode/RepeatableTextField/normalize-space(text()) return $b", + "ND-Root", + "for text:$b in BT-00-Repeatable-Text return $b"); + } + + @Test + void testForLoopIterator_RepeatableFieldWithPredicate_Works() { + // A repeatable field with a predicate used as sequence in iterator should work. + // This pattern is equivalent to the problematic SDK template rewritten correctly. + testExpressionTranslationWithContext( + "for $a in PathNode/TextField/normalize-space(text()), $b in SubNode/RepeatableInSubNode/Text[./normalize-space(text()) = $a]/normalize-space(text()) return $b", + "ND-Root", + "for text:$a in BT-00-Text, text:$b in BT-13-Text[BT-13-Text == $a] return $b"); + } + + // #endregion: For Loop Iterator Sequence Validation + + // #region: Predicate Comparison with Repeatable Fields ------------------------- + // These tests verify that repeatable fields in predicate comparisons are handled correctly. + + @Test + void testPredicateComparison_RepeatableFieldAsScalar_ThrowsError() { + // A repeatable field used as scalar in a predicate comparison should throw. + // Pattern: FIELD[REPEATABLE_FIELD == $var] - the repeatable field is used as scalar + TypeMismatchException ex = assertThrows(TypeMismatchException.class, + () -> translateExpressionWithContext("ND-Root", "BT-00-Text[BT-00-Repeatable-Text == 'test']")); + assertEquals(TypeMismatchException.ErrorCode.EXPECTED_SEQUENCE, ex.getErrorCode()); + } + + @Test + void testPredicateComparison_RepeatableFieldWithSomeSatisfies_Works() { + // Using "some ... satisfies" to check if any value matches should work. + // This is the correct way to compare against a repeatable field. + // Pattern: FIELD[some text:$x in REPEATABLE_FIELD satisfies $x == $var] + testExpressionTranslationWithContext( + "PathNode/TextField[some $x in ../RepeatableTextField/normalize-space(text()) satisfies $x = 'test']/normalize-space(text())", + "ND-Root", + "BT-00-Text[some text:$x in BT-00-Repeatable-Text satisfies $x == 'test']"); + } + + // #endregion: Predicate Comparison with Repeatable Fields + + // #region: B1 Grammar Issue - Parentheses in some...in expressions -------- + // These tests verify that parentheses around field references in some...in + // expressions are correctly parsed as sequences, not as single-element lists. + // + // The issue: (FIELD) in "some text:$x in (FIELD) satisfies ..." is parsed + // as a scalar list instead of a parenthesized sequence reference. + // See: EFX-TYPE-SAFETY-FIXES.md Category B1 + + @Test + void testSomeSatisfies_ParenthesizedFieldReference_String() { + // Parentheses around a string field reference should still be treated as a sequence. + // The parentheses are preserved in the XPath output. + testExpressionTranslationWithContext( + "some $x in (PathNode/RepeatableTextField/normalize-space(text())) satisfies $x = 'test'", + "ND-Root", + "some text:$x in (BT-00-Repeatable-Text) satisfies $x == 'test'"); + } + + @Test + void testSomeSatisfies_ParenthesizedFieldReferenceWithPredicate_String() { + // Parentheses around a field reference with predicate should work as sequence. + // Note: The predicate comparison still needs some...satisfies pattern for repeatable fields. + testExpressionTranslationWithContext( + "some $x in (PathNode/TextField[some $y in ../RepeatableTextField/normalize-space(text()) satisfies $y = 'filter']/normalize-space(text())) satisfies $x = 'test'", + "ND-Root", + "some text:$x in (BT-00-Text[some text:$y in BT-00-Repeatable-Text satisfies $y == 'filter']) satisfies $x == 'test'"); + } + + @Test + void testSomeSatisfies_ParenthesizedFieldFromRepeatableNode_String() { + // A field from a repeatable node, wrapped in parentheses, should work as sequence. + testExpressionTranslationWithContext( + "some $x in (RepeatableNode/TextField/normalize-space(text())) satisfies $x = 'test'", + "ND-Root", + "some text:$x in (BT-00-Text-In-Repeatable-Node) satisfies $x == 'test'"); + } + + @Test + void testSomeSatisfies_NestedParenthesizedFieldReference_String() { + // Double parentheses should also work. + testExpressionTranslationWithContext( + "some $x in ((PathNode/RepeatableTextField/normalize-space(text()))) satisfies $x = 'test'", + "ND-Root", + "some text:$x in ((BT-00-Repeatable-Text)) satisfies $x == 'test'"); + } + + @Test + void testForLoop_ParenthesizedFieldReference_String() { + // Parentheses around a field reference in a for loop should work as sequence. + testExpressionTranslationWithContext( + "for $x in (PathNode/RepeatableTextField/normalize-space(text())) return $x", + "ND-Root", + "for text:$x in (BT-00-Repeatable-Text) return $x"); + } + + @Test + void testEverySatisfies_ParenthesizedFieldReference_String() { + // "every" quantifier with parenthesized field reference should work as sequence. + testExpressionTranslationWithContext( + "every $x in (PathNode/RepeatableTextField/normalize-space(text())) satisfies $x != ''", + "ND-Root", + "every text:$x in (BT-00-Repeatable-Text) satisfies $x != ''"); + } + + @Test + void testCount_ParenthesizedFieldReference() { + // count() with parenthesized field reference should work as sequence. + testExpressionTranslationWithContext( + "count((PathNode/RepeatableTextField/normalize-space(text())))", + "ND-Root", + "count((BT-00-Repeatable-Text))"); + } + + @Test + void testDistinctValues_ParenthesizedFieldReference_String() { + // distinct-values() with parenthesized field reference should work as sequence. + testExpressionTranslationWithContext( + "distinct-values((PathNode/RepeatableTextField/normalize-space(text())))", + "ND-Root", + "distinct-values((BT-00-Repeatable-Text))"); + } + + @Test + void testStringJoin_ParenthesizedFieldReference() { + // string-join() with parenthesized field reference should work as sequence. + testExpressionTranslationWithContext( + "string-join((PathNode/RepeatableTextField/normalize-space(text())), ', ')", + "ND-Root", + "string-join((BT-00-Repeatable-Text), ', ')"); + } + + // #endregion: B1 Grammar Issue } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test.java index bb675be..df35f34 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test.java @@ -152,6 +152,15 @@ void testWithClause_VariablePositioning() throws IOException { assertAllOutputs(testName, outputFiles); } + @Test + void testWithClause_ContextVariableOverride() throws IOException { + String testName = "testWithClause_ContextVariableOverride"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + //#endregion WITH clause tests //#region ASSERT and REPORT tests @@ -272,6 +281,64 @@ void testVariable_StageLevel() throws IOException { //#endregion Variable tests (output verification) + //#region Sequence variable tests + + @Test + void testVariable_TextSequence_GlobalLevel() throws IOException { + String testName = "testVariable_TextSequence_GlobalLevel"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + + @Test + void testVariable_NumericSequence_GlobalLevel() throws IOException { + String testName = "testVariable_NumericSequence_GlobalLevel"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + + @Test + void testVariable_BooleanSequence_GlobalLevel() throws IOException { + String testName = "testVariable_BooleanSequence_GlobalLevel"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + + @Test + void testVariable_DateSequence_GlobalLevel() throws IOException { + String testName = "testVariable_DateSequence_GlobalLevel"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + + @Test + void testVariable_TimeSequence_GlobalLevel() throws IOException { + String testName = "testVariable_TimeSequence_GlobalLevel"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + + @Test + void testVariable_DurationSequence_GlobalLevel() throws IOException { + String testName = "testVariable_DurationSequence_GlobalLevel"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + + //#endregion Sequence variable tests + //#region Variable tests (error verification) @Test @@ -438,4 +505,22 @@ void testOutput_ComprehensiveMixedRules() throws IOException { } //#endregion Comprehensive/Integration tests + + //#region Context variable type tests + + /** + * Context variables should always be scalar, even when pointing to a repeatable field. + * This is because the context iterates, so the variable holds the current iteration value. + * Using $ctx in scalar arithmetic ($ctx + 1) should work. + */ + @Test + void testContextVariable_RepeatableField_UsedAsScalar() throws IOException { + String testName = "testContextVariable_RepeatableField_UsedAsScalar"; + Map outputFiles = translator.translateRules(readInput(testName)); + + assertEquals(7, outputFiles.size(), "Should generate exactly 7 files"); + assertAllOutputs(testName, outputFiles); + } + + //#endregion Context variable type tests } 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 6e2d4ac..fa775c0 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -1,7 +1,22 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ package eu.europa.ted.efx.sdk2; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.junit.jupiter.api.Test; import eu.europa.ted.efx.EfxTestsBase; @@ -14,9 +29,9 @@ protected String getSdkVersion() { return "eforms-sdk-2.0"; } - // #region Core Template Structure ------------------------------------------ + // #region Core Template Structure ------------------------------------------- - // #region templateDefinition ----------------------------------------------- + // #region templateDefinition ------------------------------------------------ @Test void testTemplateDefinition_InvokeTemplate() { @@ -79,9 +94,9 @@ void testTemplateDefinition_ParameterValidation() { "invoke param-validation-template('test', 42);"))); } - // #endregion templateDefinition -------------------------------------------- + // #endregion templateDefinition --------------------------------------------- - // #region templateDeclaration ---------------------------------------------- + // #region templateDeclaration ----------------------------------------------- @Test void testTemplateDeclaration_NoParameters() { @@ -153,9 +168,9 @@ void testTemplateDeclaration_SpecialCharactersInName() { "invoke template-with-dashes('test');"))); } - // #endregion templateDeclaration ------------------------------------------- + // #endregion templateDeclaration -------------------------------------------- - // #region templateFragment ------------------------------------------------- + // #region templateFragment -------------------------------------------------- @Test void testTemplateFragment_TextAndExpression() { @@ -223,7 +238,7 @@ void testTemplateFragment_MultipleExpressions() { translateTemplate("{BT-00-Number} ${BT-00-Number} + ${BT-00-Number} = ${BT-00-Number + BT-00-Number}")); } - // #region textBlock ------------------------------------------------------- + // #region textBlock --------------------------------------------------------- @Test void testTextBlock_SimpleText() { @@ -291,9 +306,9 @@ void testTextBlock_WithEscapedCharacters() { translateTemplate("display Text with quotes: \"' and backslash: \\\\;")); } - // #endregion textBlock ----------------------------------------------------- + // #endregion textBlock ------------------------------------------------------ - // #region linkedTextBlock -------------------------------------------------- + // #region linkedTextBlock --------------------------------------------------- @Test void testLinkedTextBlock() { @@ -328,9 +343,9 @@ void testLinkedExpressionBlock() { translateTemplate("DISPLAY here is a ${'multi word link'}@{'http://example.com'}. How about it?;")); } - // #endregion linkedTextBlock ----------------------------------------------- + // #endregion linkedTextBlock ------------------------------------------------ - // #endregion templateFragment ---------------------------------------------- + // #endregion templateFragment ----------------------------------------------- @Test void testTemplate_ComplexNesting() { @@ -346,9 +361,9 @@ void testTemplate_ComplexNesting() { "invoke complex-template('BT-00-Text');"))); } - // #endregion Core Template Structure --------------------------------------- + // #endregion Core Template Structure ---------------------------------------- - // #region Globals ---------------------------------------------------------- + // #region Globals ----------------------------------------------------------- @Test void testGlobals_VariableDeclaration() { @@ -470,7 +485,7 @@ void testGlobals_DictionaryDeclaration() { "display ${$dic['key']};"))); } - // #endregion Globals ------------------------------------------------------- + // #endregion Globals -------------------------------------------------------- @Test void testDisplayTemplate() { @@ -487,7 +502,289 @@ void testDisplayTemplate() { "display this is a ${$t};"))); } - // #region templateLine ----------------------------------------------------- + // #region Sequence variable declarations ------------------------------------ + + @Test + void testDisplayTemplate_WithTextSequenceVariable() { + assertEquals( + lines( + "GLOBALS:", + "string*:items=('a','b','c')", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count($items)) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let text*:$items = ('a', 'b', 'c');", + "display count: ${count($items)};"))); + } + + @Test + void testDisplayTemplate_WithNumericSequenceVariable() { + assertEquals( + lines( + "GLOBALS:", + "decimal*:nums=(1,2,3)", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count($nums)) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let number*:$nums = (1, 2, 3);", + "display count: ${count($nums)};"))); + } + + @Test + void testDisplayTemplate_WithBooleanSequenceVariable() { + assertEquals( + lines( + "GLOBALS:", + "boolean*:flags=(true(),false())", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count($flags)) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let indicator*:$flags = (TRUE, FALSE);", + "display count: ${count($flags)};"))); + } + + @Test + void testDisplayTemplate_WithDateSequenceVariable() { + assertEquals( + lines( + "GLOBALS:", + "date*:dates=(xs:date('2024-01-01Z'),xs:date('2024-12-31Z'))", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count($dates)) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let date*:$dates = (2024-01-01Z, 2024-12-31Z);", + "display count: ${count($dates)};"))); + } + + @Test + void testDisplayTemplate_WithTimeSequenceVariable() { + assertEquals( + lines( + "GLOBALS:", + "time*:times=(xs:time('10:00:00Z'),xs:time('18:00:00Z'))", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count($times)) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let time*:$times = (10:00:00Z, 18:00:00Z);", + "display count: ${count($times)};"))); + } + + @Test + void testDisplayTemplate_WithDurationSequenceVariable() { + assertEquals( + lines( + "GLOBALS:", + "duration*:durs=(xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M'))", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count($durs)) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let measure*:$durs = (P1Y, P2M);", + "display count: ${count($durs)};"))); + } + + // #endregion Sequence variable declarations --------------------------------- + + // #region Sequence function declarations ------------------------------------ + + @Test + void testDisplayTemplate_WithTextSequenceFunction() { + assertEquals( + lines( + "GLOBALS:", + "string*:getItems() -> { ('a','b','c') }", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count(udf:getItems())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let text*:?getItems() = ('a', 'b', 'c');", + "display count: ${count(?getItems())};"))); + } + + @Test + void testDisplayTemplate_WithNumericSequenceFunction() { + assertEquals( + lines( + "GLOBALS:", + "decimal*:getNumbers() -> { (1,2,3) }", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count(udf:getNumbers())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let number*:?getNumbers() = (1, 2, 3);", + "display count: ${count(?getNumbers())};"))); + } + + @Test + void testDisplayTemplate_WithBooleanSequenceFunction() { + assertEquals( + lines( + "GLOBALS:", + "boolean*:getFlags() -> { (true(),false()) }", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count(udf:getFlags())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let indicator*:?getFlags() = (TRUE, FALSE);", + "display count: ${count(?getFlags())};"))); + } + + @Test + void testDisplayTemplate_WithDateSequenceFunction() { + assertEquals( + lines( + "GLOBALS:", + "date*:getDates() -> { (xs:date('2024-01-01Z'),xs:date('2024-12-31Z')) }", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count(udf:getDates())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let date*:?getDates() = (2024-01-01Z, 2024-12-31Z);", + "display count: ${count(?getDates())};"))); + } + + @Test + void testDisplayTemplate_WithTimeSequenceFunction() { + assertEquals( + lines( + "GLOBALS:", + "time*:getTimes() -> { (xs:time('10:00:00Z'),xs:time('18:00:00Z')) }", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count(udf:getTimes())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let time*:?getTimes() = (10:00:00Z, 18:00:00Z);", + "display count: ${count(?getTimes())};"))); + } + + @Test + void testDisplayTemplate_WithDurationSequenceFunction() { + assertEquals( + lines( + "GLOBALS:", + "duration*:getDurations() -> { (xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2M')) }", + "TEMPLATES:", + "let body01() -> { text('count: ')eval(count(udf:getDurations())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let measure*:?getDurations() = (P1Y, P2M);", + "display count: ${count(?getDurations())};"))); + } + + // #endregion Sequence function declarations --------------------------------- + + // #region Sequence parameter declarations ----------------------------------- + + @Test + void testDisplayTemplate_WithTextSequenceParameter() { + assertEquals( + lines( + "GLOBALS:", + "string*:processItems(string*:items) -> { $items }", + "TEMPLATES:", + "let body01() -> { text('done') }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let text*:?processItems(text*:$items) = $items;", + "display done;"))); + } + + @Test + void testDisplayTemplate_WithNumericSequenceParameter() { + assertEquals( + lines( + "GLOBALS:", + "decimal*:processNumbers(decimal*:nums) -> { $nums }", + "TEMPLATES:", + "let body01() -> { text('done') }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let number*:?processNumbers(number*:$nums) = $nums;", + "display done;"))); + } + + @Test + void testDisplayTemplate_WithBooleanSequenceParameter() { + assertEquals( + lines( + "GLOBALS:", + "boolean*:processFlags(boolean*:flags) -> { $flags }", + "TEMPLATES:", + "let body01() -> { text('done') }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let indicator*:?processFlags(indicator*:$flags) = $flags;", + "display done;"))); + } + + @Test + void testDisplayTemplate_WithDateSequenceParameter() { + assertEquals( + lines( + "GLOBALS:", + "date*:processDates(date*:dates) -> { $dates }", + "TEMPLATES:", + "let body01() -> { text('done') }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let date*:?processDates(date*:$dates) = $dates;", + "display done;"))); + } + + @Test + void testDisplayTemplate_WithTimeSequenceParameter() { + assertEquals( + lines( + "GLOBALS:", + "time*:processTimes(time*:times) -> { $times }", + "TEMPLATES:", + "let body01() -> { text('done') }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let time*:?processTimes(time*:$times) = $times;", + "display done;"))); + } + + @Test + void testDisplayTemplate_WithDurationSequenceParameter() { + assertEquals( + lines( + "GLOBALS:", + "duration*:processDurations(duration*:durs) -> { $durs }", + "TEMPLATES:", + "let body01() -> { text('done') }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate(lines( + "let measure*:?processDurations(measure*:$durs) = $durs;", + "display done;"))); + } + + // #endregion Sequence parameter declarations -------------------------------- + + // #region templateLine ------------------------------------------------------ @Test void testTemplateLine_NoIndentation() { @@ -791,10 +1088,10 @@ void testTemplateLine_Indentation_DeepNesting() { " {BT-00-Text} Level 3"))); } - // #endregion templateLine ------------------------------------------------- + // #endregion templateLine --------------------------------------------------- - // #region otherSections ---------------------------------------------------- + // #region otherSections ----------------------------------------------------- @Test void testOtherSections_SummarySection() { @@ -827,9 +1124,9 @@ void testOtherSections_SummarySection() { "{BT-00-Text} Summary: ${BT-00-Text}"))); } - // #endregion otherSections ------------------------------------------------- + // #endregion otherSections -------------------------------------------------- - // #region Labels ----------------------------------------------------------- + // #region Labels ------------------------------------------------------------ @Test void testLabelBlock_StandardLabelReference() { @@ -1098,9 +1395,9 @@ void testLabelBlock_Expression_NestedExpressions() { "{/, text:$assetType='field', text:$labelType='name', text:$assetId='BT-00-Text'} #{${$assetType}|${$labelType}|${$assetId}}")); } - // #endregion Labels -------------------------------------------------------- + // #endregion Labels --------------------------------------------------------- - // #region Expression block ------------------------------------------------- + // #region Expression block -------------------------------------------------- @Test void testExpressionBlock_ShorthandFieldValueReferenceFromContextField() { @@ -1129,9 +1426,9 @@ void testExpressionBlock_ShorthandFieldValueReferenceFromContextField_WithNodeCo assertThrows(ParseCancellationException.class, () -> translateTemplate("{ND-Root} $value")); } - // #endregion Expression block ---------------------------------------------- + // #endregion Expression block ----------------------------------------------- - // #region contextDeclarationBlock ------------------------------------------ + // #region contextDeclarationBlock ------------------------------------------- @Test void testContextDeclarationBlock_ContextFieldVariable() { @@ -1183,9 +1480,31 @@ void testContextDeclarationBlock_OnlyVariables() { "{text:$var1='test', number:$var2=123, indicator:$var3=TRUE} Only vars: ${$var1}, ${$var2}, ${$var3}")); } - // #endregion contextDeclarationBlock --------------------------------------- + /** + * Context variables are always scalar even when the context field is repeatable, + * because the template iterates over values and the variable holds each iteration's value. + */ + @Test + void testContextDeclarationBlock_RepeatableFieldContextVariable_UsedAsScalar() { + String result = translateTemplate("{context:$ctx = BT-13-Number} Value plus one: ${$ctx + 1}"); + + assertTrue(result.contains("decimal:ctx"), + "Context variable should be scalar (decimal:ctx), not sequence. Actual: " + result); + assertFalse(result.contains("decimal*:ctx"), + "Context variable should NOT be typed as sequence. Actual: " + result); + + assertEquals( + lines( + "TEMPLATES:", + "let body01(decimal:ctx) -> { text('Value plus one: ')eval($ctx + 1) }", + "MAIN:", + "for-each(/*/SubNode/RepeatableInSubNode/Number).call(body01(decimal:ctx=.))"), + result); + } - // #region chooseTemplate ------------------------------------ + // #endregion contextDeclarationBlock ---------------------------------------- + + // #region chooseTemplate ---------------------------------------------------- @Test void testChooseTemplate_WhenBlock_MultipleConditions() { @@ -1296,9 +1615,9 @@ void testChooseTemplate_WhenNoOtherwiseNoContext() { "when TRUE display this is a ${$t};"))); } - // #endregion chooseTemplate --------------------------------- + // #endregion chooseTemplate ------------------------------------------------- - // #region variableList --------------------------------------------- + // #region variableList ------------------------------------------------------ @Test void testVariableList_WithAllDataTypes() { @@ -1323,9 +1642,9 @@ void testVariableList_ExpressionInitializers() { translateTemplate("{BT-00-Text, text:$computed=concat('prefix-', BT-00-Text)} Computed: ${$computed}")); } - // #endregion variableList ------------------------------------------ + // #endregion variableList --------------------------------------------------- - // #region templateLine edge cases ------------------------------------------ + // #region templateLine edge cases ------------------------------------------- @Test void testTemplateLine_OutlineNumber_Only() { @@ -1349,9 +1668,9 @@ void testTemplateLine_OutlineNumber_WithContext() { translateTemplate("1 {BT-00-Text} Value: ${BT-00-Text}")); } - // #endregion templateLine edge cases --------------------------------------- + // #endregion templateLine edge cases ---------------------------------------- - // #region InvalidIndentationException -------------------------------------- + // #region InvalidIndentationException --------------------------------------- @Test void testTemplateLine_InvalidIndentation_FirstIndentation() { @@ -1403,9 +1722,9 @@ void testTemplateDeclaration_InvalidNestedStructure() { assertThrows(InvalidIndentationException.class, () -> translateTemplate(template)); } - // #endregion InvalidIndentationException ----------------------------------- + // #endregion InvalidIndentationException ------------------------------------ - // #region TypeMismatchException -------------------------------------------- + // #region TypeMismatchException --------------------------------------------- @Test void testTemplateDefinition_InvalidParameters() { @@ -1415,9 +1734,79 @@ void testTemplateDefinition_InvalidParameters() { assertThrows(InvalidArgumentException.class, () -> translateTemplate(template)); } - // #endregion TypeMismatchException ----------------------------------------- + // #endregion TypeMismatchException ------------------------------------------ + + // #region Repeatable Fields in Expression Blocks ---------------------------- + + @Test + void testExpressionBlock_RepeatableFieldDirect() { + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { text('Value: ')eval(PathNode/RepeatableTextField/normalize-space(text())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate("{ND-Root} Value: ${BT-00-Repeatable-Text}")); + } + + @Test + void testExpressionBlock_RepeatableFieldWithForLoop() { + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { text('Value: ')eval(for $x in PathNode/RepeatableTextField/normalize-space(text()) return $x) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate("{ND-Root} Value: ${for text:$x in BT-00-Repeatable-Text return $x}")); + } + + @Test + void testExpressionBlock_RepeatableFieldWithExplicitCast() { + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { text('Value: ')eval(PathNode/RepeatableTextField/normalize-space(text())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate("{ND-Root} Value: ${(text*)BT-00-Repeatable-Text}")); + } + + @Test + void testExpressionBlock_ScalarField() { + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { text('Value: ')eval(PathNode/TextField/normalize-space(text())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate("{ND-Root} Value: ${BT-00-Text}")); + } + + @Test + void testExpressionBlock_LiteralNumericSequence() { + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { text('Values: ')eval((1,2,3)) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate("{ND-Root} Values: ${(1,2,3)}")); + } + + @Test + void testExpressionBlock_MixedSequenceWithField() { + assertEquals( + lines( + "TEMPLATES:", + "let body01() -> { text('Values: ')eval((1,2,PathNode/NumberField/number())) }", + "MAIN:", + "for-each(/*).call(body01())"), + translateTemplate("{ND-Root} Values: ${(1,2,BT-00-Number)}")); + } + + // #endregion Repeatable Fields in Expression Blocks ------------------------- - // #region IllegalArgumentException ----------------------------------------- + // #region IllegalArgumentException ------------------------------------------ @Test void testTemplateDefinition_InvalidTooManyParameters() { @@ -1435,7 +1824,7 @@ void testTemplateDefinition_InvalidTooFewParameters() { assertThrows(InvalidArgumentException.class, () -> translateTemplate(template)); } - // #endregion IllegalArgumentException -------------------------------------- + // #endregion IllegalArgumentException --------------------------------------- @Test void testContextualizer_WithPredicate() { diff --git a/src/test/java/eu/europa/ted/efx/sdk2/SdkSymbolResolverTest.java b/src/test/java/eu/europa/ted/efx/sdk2/SdkSymbolResolverTest.java new file mode 100644 index 0000000..70cbbb1 --- /dev/null +++ b/src/test/java/eu/europa/ted/efx/sdk2/SdkSymbolResolverTest.java @@ -0,0 +1,650 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.efx.sdk2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import eu.europa.ted.efx.exceptions.SymbolResolutionException; +import eu.europa.ted.efx.exceptions.SymbolResolutionException.ErrorCode; +import eu.europa.ted.efx.interfaces.SymbolResolver; +import eu.europa.ted.efx.mock.sdk2.SymbolResolverMockV2; +import eu.europa.ted.efx.model.expressions.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.BooleanPath; +import eu.europa.ted.efx.model.expressions.scalar.DatePath; +import eu.europa.ted.efx.model.expressions.scalar.DurationPath; +import eu.europa.ted.efx.model.expressions.scalar.MultilingualStringPath; +import eu.europa.ted.efx.model.expressions.scalar.NodePath; +import eu.europa.ted.efx.model.expressions.scalar.NumericPath; +import eu.europa.ted.efx.model.expressions.scalar.ScalarPath; +import eu.europa.ted.efx.model.expressions.scalar.StringPath; +import eu.europa.ted.efx.model.expressions.scalar.TimePath; +import eu.europa.ted.efx.model.expressions.sequence.NodeSequencePath; +import eu.europa.ted.efx.model.expressions.sequence.SequencePath; + +/** + * Comprehensive tests for SdkSymbolResolver covering: + * 1. Exception handling for bad inputs + * 2. Repeatability logic from different contexts + * 3. PathExpression shape (ScalarPath vs SequencePath) + * 4. PathExpression data types + * 5. Context-aware relative paths + * 6. Basic lookups + * 7. Alias resolution + * 8. Root and notice type methods + */ +class SdkSymbolResolverTest { + + private static SymbolResolver resolver; + + @BeforeAll + static void setup() throws Exception { + resolver = new SymbolResolverMockV2(); + } + + // ========================================================================= + // 1. Exception Handling + // ========================================================================= + + @Nested + @DisplayName("1. Exception Handling") + class ExceptionTests { + + @Test + @DisplayName("getAbsolutePathOfField throws UNKNOWN_SYMBOL for nonexistent field") + void getAbsolutePathOfField_unknownField_throwsException() { + SymbolResolutionException ex = assertThrows( + SymbolResolutionException.class, + () -> resolver.getAbsolutePathOfField("BT-NONEXISTENT")); + + assertEquals(ErrorCode.UNKNOWN_SYMBOL, ex.getErrorCode()); + } + + @Test + @DisplayName("getAbsolutePathOfNode throws UNKNOWN_SYMBOL for nonexistent node") + void getAbsolutePathOfNode_unknownNode_throwsException() { + SymbolResolutionException ex = assertThrows( + SymbolResolutionException.class, + () -> resolver.getAbsolutePathOfNode("ND-NONEXISTENT")); + + assertEquals(ErrorCode.UNKNOWN_SYMBOL, ex.getErrorCode()); + } + + @Test + @DisplayName("expandCodelist throws UNKNOWN_CODELIST for nonexistent codelist") + void expandCodelist_unknownCodelist_throwsException() { + SymbolResolutionException ex = assertThrows( + SymbolResolutionException.class, + () -> resolver.expandCodelist("nonexistent-codelist")); + + assertEquals(ErrorCode.UNKNOWN_CODELIST, ex.getErrorCode()); + } + + @Test + @DisplayName("getRootCodelistOfField throws NO_CODELIST_FOR_FIELD for non-code field") + void getRootCodelistOfField_nonCodeField_throwsException() { + SymbolResolutionException ex = assertThrows( + SymbolResolutionException.class, + () -> resolver.getRootCodelistOfField("BT-12-Text")); + + assertEquals(ErrorCode.NO_CODELIST_FOR_FIELD, ex.getErrorCode()); + } + } + + // ========================================================================= + // 2. Repeatability Logic + // ========================================================================= + + @Nested + @DisplayName("2. Repeatability Logic") + class RepeatabilityTests { + + @Test + @DisplayName("Scalar field from root context returns false") + void isFieldRepeatableFromContext_scalarFieldFromRoot_returnsFalse() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-12-Text", null), + "Field with no repeatable ancestors should return false from root"); + } + + @Test + @DisplayName("Field in self-repeatable node from root returns true") + void isFieldRepeatableFromContext_fieldInRepNode_fromRoot_returnsTrue() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-13-Text", null), + "Field in repeatable node should return true from root"); + } + + @Test + @DisplayName("Field under repeatable ancestor from root returns true") + void isFieldRepeatableFromContext_fieldUnderRepAncestor_fromRoot_returnsTrue() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-21-Number", null), + "Field under repeatable ancestor should return true from root"); + } + + @Test + @DisplayName("Field under repeatable ancestor from that ancestor returns false") + void isFieldRepeatableFromContext_fieldUnderRepAncestor_fromRepAncestor_returnsFalse() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-21-Number", "ND-RepeatableNode"), + "Walk should stop at context node, returning false"); + } + + @Test + @DisplayName("Deeply nested field with inner repeatable node from outer context returns true") + void isFieldRepeatableFromContext_deeplyNested_fromOuterRepContext_returnsTrue() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-25-Date", "ND-RepeatableNode"), + "Should still return true because inner repeatable node is crossed"); + } + + @Test + @DisplayName("Scalar field from cross-branch repeatable context stays scalar") + void isFieldRepeatableFromContext_scalarField_fromCrossBranchRepContext_returnsFalse() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-12-Text", "ND-RepeatableNode"), + "Scalar field should remain scalar even from rep context on different branch"); + } + + @Test + @DisplayName("Sequence field from cross-branch non-repeatable context stays sequence") + void isFieldRepeatableFromContext_sequenceField_fromCrossBranchNonRepContext_returnsTrue() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-21-Number", "ND-SubNode"), + "Sequence field should remain sequence from non-rep context on different branch"); + } + + @Test + @DisplayName("Attribute field in repeatable node from root returns true") + void isFieldRepeatableFromContext_attributeInRepNode_fromRoot_returnsTrue() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-00-Attribute-In-Repeatable-Node", null), + "Attribute field in repeatable node should return true from root"); + } + + @Test + @DisplayName("Attribute field in repeatable node from that node returns false") + void isFieldRepeatableFromContext_attributeInRepNode_fromRepNode_returnsFalse() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-00-Attribute-In-Repeatable-Node", "ND-RepeatableNode"), + "Attribute field should not be repeatable when context is its parent repeatable node"); + } + } + + // ========================================================================= + // 3. PathExpression Shape Tests + // ========================================================================= + + @Nested + @DisplayName("3. PathExpression Shape Tests") + class PathExpressionShapeTests { + + @Test + @DisplayName("Non-repeatable field returns ScalarPath") + void nonRepeatableField_shouldReturnScalarPath() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-00-Text", null), + "Precondition: field should NOT be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Text"); + + assertTrue(path instanceof ScalarPath, + "Non-repeatable field should return ScalarPath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Repeatable field returns SequencePath") + void repeatableField_shouldReturnSequencePath() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-00-Repeatable-Text", null), + "Precondition: field should be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Repeatable-Text"); + + assertTrue(path instanceof SequencePath, + "Repeatable field should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Field in repeatable node returns SequencePath") + void fieldInRepeatableNode_shouldReturnSequencePath() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-00-Text-In-Repeatable-Node", null), + "Precondition: field in repeatable node should be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Text-In-Repeatable-Node"); + + assertTrue(path instanceof SequencePath, + "Field in repeatable node should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Field in non-repeatable subnode of repeatable node returns SequencePath") + void fieldInNonRepeatableSubNode_shouldReturnSequencePath() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-00-Text-In-NonRepeatableSubNode", null), + "Precondition: field under repeatable ancestor should be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Text-In-NonRepeatableSubNode"); + + assertTrue(path instanceof SequencePath, + "Field under repeatable ancestor should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Field in double-repeatable context returns SequencePath") + void fieldInDoubleRepeatableContext_shouldReturnSequencePath() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-00-Text-In-RepeatableSubSubNode", null), + "Precondition: field in double-repeatable context should be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Text-In-RepeatableSubSubNode"); + + assertTrue(path instanceof SequencePath, + "Field in double-repeatable context should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Non-repeatable root node returns NodePath (scalar)") + void nonRepeatableRootNode_shouldReturnScalarPath() { + PathExpression path = resolver.getAbsolutePathOfNode("ND-Root"); + + assertTrue(path instanceof ScalarPath, + "Non-repeatable node should return ScalarPath, got: " + path.getClass().getSimpleName()); + assertEquals(NodePath.class, path.getClass(), + "Non-repeatable node should return NodePath"); + } + + @Test + @DisplayName("Repeatable node returns SequencePath") + void repeatableNode_shouldReturnSequencePath() { + PathExpression path = resolver.getAbsolutePathOfNode("ND-RepeatableNode"); + + assertTrue(path instanceof SequencePath, + "Repeatable node should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Non-repeatable subnode under repeatable parent returns SequencePath") + void nonRepeatableSubNode_underRepeatableParent_shouldReturnSequencePath() { + assertTrue(resolver.isNodeRepeatableFromContext("ND-NonRepeatableSubNode", null), + "Precondition: node under repeatable ancestor should be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfNode("ND-NonRepeatableSubNode"); + + assertTrue(path instanceof SequencePath, + "Node under repeatable ancestor should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Repeatable subsubnode returns SequencePath") + void repeatableSubSubNode_shouldReturnSequencePath() { + PathExpression path = resolver.getAbsolutePathOfNode("ND-RepeatableSubSubNode"); + + assertTrue(path instanceof SequencePath, + "Repeatable subsubnode should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Non-repeatable attribute field without attribute returns NodePath") + void nonRepeatableAttributeField_withoutAttribute_shouldReturnNodePath() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-00-Attribute", null), + "Precondition: attribute field should NOT be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfFieldWithoutTheAttribute("BT-00-Attribute"); + + assertTrue(path instanceof NodePath, + "Non-repeatable attribute field without @attribute should return NodePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Attribute field in repeatable node without attribute returns NodeSequencePath") + void attributeInRepeatableNode_withoutAttribute_shouldReturnNodeSequencePath() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-00-Attribute-In-Repeatable-Node", null), + "Precondition: attribute field in repeatable node should be repeatable from root"); + + PathExpression path = resolver.getAbsolutePathOfFieldWithoutTheAttribute("BT-00-Attribute-In-Repeatable-Node"); + + assertTrue(path instanceof NodeSequencePath, + "Attribute field in repeatable node without @attribute should return NodeSequencePath, got: " + path.getClass().getSimpleName()); + } + } + + // ========================================================================= + // 4. PathExpression Data Type Tests + // ========================================================================= + + @Nested + @DisplayName("4. PathExpression Data Type Tests") + class PathExpressionDataTypeTests { + + @Test + @DisplayName("Text field returns StringPath") + void textField_shouldReturnStringPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Text"); + assertEquals(StringPath.class, path.getClass(), + "Text field should return StringPath"); + } + + @Test + @DisplayName("Indicator field returns BooleanPath") + void indicatorField_shouldReturnBooleanPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Indicator"); + assertEquals(BooleanPath.class, path.getClass(), + "Indicator field should return BooleanPath"); + } + + @Test + @DisplayName("Code field returns StringPath") + void codeField_shouldReturnStringPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Code"); + assertEquals(StringPath.class, path.getClass(), + "Code field should return StringPath"); + } + + @Test + @DisplayName("Multilingual field returns MultilingualStringPath") + void multilingualField_shouldReturnMultilingualStringPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Text-Multilingual"); + assertEquals(MultilingualStringPath.class, path.getClass(), + "Multilingual field should return MultilingualStringPath"); + } + + @Test + @DisplayName("Date field returns DatePath") + void dateField_shouldReturnDatePath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-StartDate"); + assertEquals(DatePath.class, path.getClass(), + "Date field should return DatePath"); + } + + @Test + @DisplayName("Time field returns TimePath") + void timeField_shouldReturnTimePath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-StartTime"); + assertEquals(TimePath.class, path.getClass(), + "Time field should return TimePath"); + } + + @Test + @DisplayName("Measure field returns DurationPath") + void measureField_shouldReturnDurationPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Measure"); + assertEquals(DurationPath.class, path.getClass(), + "Measure field should return DurationPath"); + } + + @Test + @DisplayName("Integer field returns NumericPath") + void integerField_shouldReturnNumericPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Integer"); + assertEquals(NumericPath.class, path.getClass(), + "Integer field should return NumericPath"); + } + + @Test + @DisplayName("Amount field returns NumericPath") + void amountField_shouldReturnNumericPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Amount"); + assertEquals(NumericPath.class, path.getClass(), + "Amount field should return NumericPath"); + } + + @Test + @DisplayName("Number field returns NumericPath") + void numberField_shouldReturnNumericPath() { + PathExpression path = resolver.getAbsolutePathOfField("BT-00-Number"); + assertEquals(NumericPath.class, path.getClass(), + "Number field should return NumericPath"); + } + } + + // ========================================================================= + // 5. Context-Aware Relative Paths + // ========================================================================= + + @Nested + @DisplayName("5. Context-Aware Relative Paths") + class RelativePathTests { + + @Test + @DisplayName("Field in repeatable node from root returns SequencePath") + void fromRoot_repeatableField_shouldReturnSequencePath() { + assertTrue(resolver.isFieldRepeatableFromContext("BT-00-Text-In-Repeatable-Node", null), + "Precondition: field should be repeatable from root"); + + PathExpression path = resolver.getRelativePathOfField("BT-00-Text-In-Repeatable-Node", "ND-Root"); + + assertTrue(path instanceof SequencePath, + "Field in repeatable node from root should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Field in repeatable node from same node context returns ScalarPath") + void fromRepeatableNode_sameField_shouldReturnScalarPath() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-00-Text-In-Repeatable-Node", "ND-RepeatableNode"), + "Precondition: field should NOT be repeatable from its own parent node context"); + + PathExpression path = resolver.getRelativePathOfField("BT-00-Text-In-Repeatable-Node", "ND-RepeatableNode"); + + assertTrue(path instanceof ScalarPath, + "Field from its parent repeatable node context should return ScalarPath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Non-repeatable field from root returns ScalarPath") + void fromRoot_nonRepeatableField_shouldReturnScalarPath() { + assertFalse(resolver.isFieldRepeatableFromContext("BT-00-Text", null), + "Precondition: field should NOT be repeatable"); + + PathExpression path = resolver.getRelativePathOfField("BT-00-Text", "ND-Root"); + + assertTrue(path instanceof ScalarPath, + "Non-repeatable field should return ScalarPath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Repeatable node from root returns SequencePath") + void fromRoot_repeatableNode_shouldReturnSequencePath() { + PathExpression path = resolver.getRelativePathOfNode("ND-RepeatableNode", "ND-Root"); + + assertTrue(path instanceof SequencePath, + "Repeatable node from root should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Non-repeatable subnode from repeatable parent returns ScalarPath") + void fromRepeatableNode_nonRepeatableSubNode_shouldReturnScalarPath() { + PathExpression path = resolver.getRelativePathOfNode("ND-NonRepeatableSubNode", "ND-RepeatableNode"); + + assertTrue(path instanceof ScalarPath, + "Non-repeatable subnode from parent context should return ScalarPath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Field relative to another field with repeatable ancestor returns SequencePath") + void fieldToField_withRepeatableAncestor_shouldReturnSequencePath() { + // BT-21-Number is in ND-NonRepeatableSubNode, whose parent ND-RepeatableNode is repeatable + // BT-00-Text is in ND-Root + // Walking up from field: ND-NonRepeatableSubNode (not rep) → ND-RepeatableNode (rep!) → sequence + PathExpression path = resolver.getRelativePathOfField("BT-21-Number", "BT-00-Text"); + + assertTrue(path instanceof SequencePath, + "Field with repeatable ancestor relative to field in root should return SequencePath, got: " + path.getClass().getSimpleName()); + } + + @Test + @DisplayName("Field relative to sibling field in same node returns ScalarPath") + void fieldToField_siblingInSameNode_shouldReturnScalarPath() { + // Both BT-21-Number and BT-21-TextMultilingual are in ND-NonRepeatableSubNode + // Context ancestry includes ND-NonRepeatableSubNode, so walk stops immediately + PathExpression path = resolver.getRelativePathOfField("BT-21-Number", "BT-21-TextMultilingual"); + + assertTrue(path instanceof ScalarPath, + "Field relative to sibling field in same node should return ScalarPath, got: " + path.getClass().getSimpleName()); + } + } + + // ========================================================================= + // 6. Basic Lookups + // ========================================================================= + + @Nested + @DisplayName("6. Basic Lookups") + class BasicLookupTests { + + @Test + @DisplayName("getTypeOfField returns correct type for text field") + void getTypeOfField_textField_returnsText() { + assertEquals("text", resolver.getTypeOfField("BT-12-Text")); + } + + @Test + @DisplayName("getTypeOfField returns correct type for number field") + void getTypeOfField_numberField_returnsNumber() { + assertEquals("number", resolver.getTypeOfField("BT-21-Number")); + } + + @Test + @DisplayName("getParentNodeOfField returns correct parent node") + void getParentNodeOfField_returnsCorrectParent() { + assertEquals("ND-SubSubNode2", resolver.getParentNodeOfField("BT-12-Text")); + } + + @Test + @DisplayName("isAttributeField returns true for attribute field") + void isAttributeField_attributeField_returnsTrue() { + assertTrue(resolver.isAttributeField("BT-00-Attribute"), + "Attribute field should return true"); + } + + @Test + @DisplayName("isAttributeField returns false for element field") + void isAttributeField_elementField_returnsFalse() { + assertFalse(resolver.isAttributeField("BT-12-Text"), + "Element field should return false"); + } + + @Test + @DisplayName("getAttributeNameFromAttributeField returns attribute name") + void getAttributeNameFromAttributeField_returnsAttributeName() { + String attrName = resolver.getAttributeNameFromAttributeField("BT-00-Attribute"); + assertNotNull(attrName, "Should return attribute name"); + assertFalse(attrName.isEmpty(), "Attribute name should not be empty"); + } + + @Test + @DisplayName("expandCodelist returns non-empty list for valid codelist") + void expandCodelist_validCodelist_returnsNonEmptyList() { + var codes = resolver.expandCodelist("accessibility"); + assertFalse(codes.isEmpty(), "Codelist should have at least one code"); + } + + @Test + @DisplayName("getRootCodelistOfField returns root codelist for code field") + void getRootCodelistOfField_codeField_returnsRootCodelist() { + String rootCodelist = resolver.getRootCodelistOfField("BT-00-Code"); + assertNotNull(rootCodelist, "Should return root codelist"); + assertFalse(rootCodelist.isEmpty(), "Root codelist should not be empty"); + } + + @Test + @DisplayName("Attribute field without attribute returns NodePath") + void getAbsolutePathOfFieldWithoutTheAttribute_returnsNodePath() { + PathExpression path = resolver.getAbsolutePathOfFieldWithoutTheAttribute("BT-00-Attribute"); + + assertEquals(NodePath.class, path.getClass(), + "Attribute field without attribute should return NodePath"); + } + + @Test + @DisplayName("Code attribute field without attribute returns NodePath") + void getAbsolutePathOfFieldWithoutTheAttribute_codeAttribute_returnsNodePath() { + PathExpression path = resolver.getAbsolutePathOfFieldWithoutTheAttribute("BT-00-CodeAttribute"); + + assertEquals(NodePath.class, path.getClass(), + "Code attribute field without attribute should return NodePath"); + } + } + + // ========================================================================= + // 7. Alias Resolution + // ========================================================================= + + @Nested + @DisplayName("7. Alias Resolution") + class AliasResolutionTests { + + @Test + @DisplayName("getFieldIdFromAlias returns field ID for valid alias") + void getFieldIdFromAlias_validAlias_returnsFieldId() { + String fieldId = resolver.getFieldIdFromAlias("textField"); + assertEquals("BT-00-Text", fieldId, "Should return field ID for alias 'textField'"); + } + + @Test + @DisplayName("getNodeIdFromAlias returns node ID for valid alias") + void getNodeIdFromAlias_validAlias_returnsNodeId() { + String nodeId = resolver.getNodeIdFromAlias("Root"); + assertEquals("ND-Root", nodeId, "Should return node ID for alias 'Root'"); + } + + @Test + @DisplayName("getNodeIdFromAlias returns null for unknown alias") + void getNodeIdFromAlias_unknownAlias_returnsNull() { + assertNull(resolver.getNodeIdFromAlias("nonexistent-alias"), + "Unknown alias should return null"); + } + + @Test + @DisplayName("getFieldIdFromAlias returns null for unknown alias") + void getFieldIdFromAlias_unknownAlias_returnsNull() { + assertNull(resolver.getFieldIdFromAlias("nonexistent-alias"), + "Unknown alias should return null to enable fallback"); + } + } + + // ========================================================================= + // 8. Root and Notice Type Methods + // ========================================================================= + + @Nested + @DisplayName("8. Root and Notice Type Methods") + class RootAndNoticeTypeTests { + + @Test + @DisplayName("getRootNodeId returns root node ID") + void getRootNodeId_returnsRootNodeId() { + String rootNodeId = resolver.getRootNodeId(); + assertEquals("ND-Root", rootNodeId, "Should return ND-Root"); + } + + @Test + @DisplayName("getRootPath returns NodePath to root") + void getRootPath_returnsNodePathToRoot() { + PathExpression rootPath = resolver.getRootPath(); + + assertNotNull(rootPath, "Should return root path"); + assertEquals(NodePath.class, rootPath.getClass(), + "Root path should be NodePath"); + } + + @Test + @DisplayName("getAllNoticeSubtypeIds returns list of notice types") + void getAllNoticeSubtypeIds_returnsNoticeTypes() { + List noticeTypes = resolver.getAllNoticeSubtypeIds(); + + assertNotNull(noticeTypes, "Should return list of notice types"); + assertFalse(noticeTypes.isEmpty(), "Notice types list should not be empty"); + } + } +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/complete-validation.sch new file mode 100644 index 0000000..e77e22f --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/complete-validation.sch @@ -0,0 +1,30 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..f5d7194 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..e9c2ac2 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/input.efx new file mode 100644 index 0000000..06710d1 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/input.efx @@ -0,0 +1,6 @@ +---- STAGE 1a ---- + +WITH context : $ctx = BT-13-Number +ASSERT $ctx + 1 > 0 +AS ERROR R-CTX-001 +FOR BT-13-Number IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/complete-validation.sch new file mode 100644 index 0000000..e77e22f --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/complete-validation.sch @@ -0,0 +1,30 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..f5d7194 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..e9c2ac2 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/complete-validation.sch new file mode 100644 index 0000000..4be5377 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..c20b02d --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-003 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..f22f3a5 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-003 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/input.efx new file mode 100644 index 0000000..677d8d6 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/input.efx @@ -0,0 +1,8 @@ +LET indicator* : $flags = (TRUE, FALSE, TRUE); + +---- STAGE 1a ---- + +WITH BT-00-Text +ASSERT count($flags) > 0 +AS ERROR R-SEQ-003 +FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/complete-validation.sch new file mode 100644 index 0000000..4be5377 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..c20b02d --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-003 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..f22f3a5 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-003 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/complete-validation.sch new file mode 100644 index 0000000..a8589a0 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..ee4bd72 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-004 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..5749e52 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-004 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/input.efx new file mode 100644 index 0000000..d2382c4 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/input.efx @@ -0,0 +1,8 @@ +LET date* : $dates = (2024-01-01Z, 2024-06-01Z); + +---- STAGE 1a ---- + +WITH BT-00-Text +ASSERT count($dates) > 0 +AS ERROR R-SEQ-004 +FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/complete-validation.sch new file mode 100644 index 0000000..a8589a0 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..ee4bd72 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-004 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..5749e52 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-004 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/complete-validation.sch new file mode 100644 index 0000000..9911825 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..1580f9e --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-006 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..9d0f4de --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-006 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/input.efx new file mode 100644 index 0000000..6d911e9 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/input.efx @@ -0,0 +1,8 @@ +LET measure* : $durations = (P1D, P2D); + +---- STAGE 1a ---- + +WITH BT-00-Text +ASSERT count($durations) > 0 +AS ERROR R-SEQ-006 +FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/complete-validation.sch new file mode 100644 index 0000000..9911825 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..1580f9e --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-006 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..9d0f4de --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-006 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/complete-validation.sch new file mode 100644 index 0000000..d1b9c78 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..4f670f3 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-002 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..fa1b7dc --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-002 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/input.efx new file mode 100644 index 0000000..c29f5f5 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/input.efx @@ -0,0 +1,8 @@ +LET number* : $numbers = (1, 2, 3); + +---- STAGE 1a ---- + +WITH BT-00-Text +ASSERT count($numbers) > 0 +AS ERROR R-SEQ-002 +FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/complete-validation.sch new file mode 100644 index 0000000..d1b9c78 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..4f670f3 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-002 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..fa1b7dc --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-002 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/complete-validation.sch new file mode 100644 index 0000000..5763fec --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..48e0eaa --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..9dfe996 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/input.efx new file mode 100644 index 0000000..bea6cfa --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/input.efx @@ -0,0 +1,8 @@ +LET text* : $languages = ('EN', 'FR', 'DE'); + +---- STAGE 1a ---- + +WITH BT-00-Text +ASSERT count($languages) > 0 +AS ERROR R-SEQ-001 +FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/complete-validation.sch new file mode 100644 index 0000000..5763fec --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..48e0eaa --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..9dfe996 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/complete-validation.sch new file mode 100644 index 0000000..c3aea28 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..08c1cab --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-005 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..eee6107 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-005 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/input.efx new file mode 100644 index 0000000..97a7796 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/input.efx @@ -0,0 +1,8 @@ +LET time* : $times = (09:00:00Z, 17:00:00Z); + +---- STAGE 1a ---- + +WITH BT-00-Text +ASSERT count($times) > 0 +AS ERROR R-SEQ-005 +FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/complete-validation.sch new file mode 100644 index 0000000..c3aea28 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/complete-validation.sch @@ -0,0 +1,31 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..08c1cab --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-005 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..eee6107 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -0,0 +1,6 @@ + + + + rule|text|R-SEQ-005 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/complete-validation.sch new file mode 100644 index 0000000..ed3741d --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/complete-validation.sch @@ -0,0 +1,34 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + PathNode/TextField + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch new file mode 100644 index 0000000..25c3634 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch new file mode 100644 index 0000000..8e0e369 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx new file mode 100644 index 0000000..92f1ca0 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx @@ -0,0 +1,10 @@ +// Test: Context variable override syntax ($ctx::FieldRef) +// Verifies that context variables can be used to override context when accessing fields +// This exercises the preprocessor's context variable lookup functionality + +---- STAGE 1a ---- + +WITH context : $ctx = ND-Root + ASSERT $ctx::BT-00-Text is not empty + AS ERROR R-CTX-001 + FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/schematrons.json b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/schematrons.json new file mode 100644 index 0000000..2bd8409 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/schematrons.json @@ -0,0 +1,31 @@ +{ + "schematrons" : [ { + "name" : "complete-validation", + "type" : "dynamic", + "filename" : "dynamic/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "dynamic", + "stage" : "1a", + "filename" : "dynamic/validation-stage-1a-2.sch" + }, { + "name" : "complete-validation", + "type" : "static", + "filename" : "static/complete-validation.sch" + }, { + "name" : "validation-stage-1a-1", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-1.sch" + }, { + "name" : "validation-stage-1a-2", + "type" : "static", + "stage" : "1a", + "filename" : "static/validation-stage-1a-2.sch" + } ] +} diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/complete-validation.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/complete-validation.sch new file mode 100644 index 0000000..ed3741d --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/complete-validation.sch @@ -0,0 +1,34 @@ + + + + eForms schematron rules + + + + + + + + + + + + + + + + + + + + + + + + PathNode/TextField + + + + + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch new file mode 100644 index 0000000..25c3634 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch new file mode 100644 index 0000000..8e0e369 --- /dev/null +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch @@ -0,0 +1,7 @@ + + + + + rule|text|R-CTX-001 + + diff --git a/src/test/resources/json/README.md b/src/test/resources/json/README.md new file mode 100644 index 0000000..53727b5 --- /dev/null +++ b/src/test/resources/json/README.md @@ -0,0 +1,67 @@ +# SDK 2 Test Data + +This folder contains mock data for testing EFX SDK version 2 functionality. + +## Node Hierarchy + +``` +ND-Root (non-rep) +| ++-- ND-SubNode (non-rep) +| +-- ND-SubSubNode (non-rep) <- non-rep in non-rep +| +-- ND-SubSubNode2 (non-rep) <- non-rep in non-rep (sibling) +| +-- ND-RepeatableInSubNode (REP) <- rep in non-rep +| +-- ND-RepeatableInSubNode2 (REP) <- rep in non-rep (sibling) +| ++-- ND-RepeatableNode (REP) + +-- ND-NonRepeatableSubNode (non-rep) <- non-rep in rep + | +-- ND-RepeatableSubSubNode (REP) <- rep in non-rep in rep + +-- ND-NonRepeatableSubNode2 (non-rep) <- non-rep in rep (sibling) + +-- ND-RepeatableInRepeatableNode (REP) <- rep in rep + +-- ND-RepeatableInRepeatableNode2 (REP) <- rep in rep (sibling) +``` + +## Repeatability Coverage + +The structure covers all 4 parent/child repeatability combinations: + +| Parent Type | Child Type | Nodes | +|-------------|------------|-------| +| Non-rep | Non-rep | ND-SubSubNode, ND-SubSubNode2 | +| Non-rep | Rep | ND-RepeatableInSubNode, ND-RepeatableInSubNode2 | +| Rep | Non-rep | ND-NonRepeatableSubNode, ND-NonRepeatableSubNode2 | +| Rep | Rep | ND-RepeatableInRepeatableNode, ND-RepeatableInRepeatableNode2 | + +Sibling pairs enable cross-branch backtracking tests. + +## Field Naming Convention + +Fields use the pattern `BT-XY-Type` where: +- **X** = Branch (1 = ND-SubNode branch, 2 = ND-RepeatableNode branch) +- **Y** = Node within branch (1-4 for children of each branch, 5 for deeper nesting) +- **Type** = Data type (Text, TextMultilingual, Indicator, Number, Date, Time, Measure) + +| Prefix | Parent Node | Repeatability from Root | +|--------|-------------|------------------------| +| BT-00-* | ND-Root | scalar | +| BT-11-* | ND-SubSubNode | scalar | +| BT-12-* | ND-SubSubNode2 | scalar | +| BT-13-* | ND-RepeatableInSubNode | SEQUENCE (self-rep) | +| BT-14-* | ND-RepeatableInSubNode2 | SEQUENCE (self-rep) | +| BT-21-* | ND-NonRepeatableSubNode | SEQUENCE (ancestor-rep) | +| BT-22-* | ND-NonRepeatableSubNode2 | SEQUENCE (ancestor-rep) | +| BT-23-* | ND-RepeatableInRepeatableNode | SEQUENCE (self + ancestor) | +| BT-24-* | ND-RepeatableInRepeatableNode2 | SEQUENCE (self + ancestor) | +| BT-25-* | ND-RepeatableSubSubNode | SEQUENCE (self + ancestor) | + +## Files + +- `fields-sdk2.json` - Field definitions with XPath expressions +- `nodes-sdk2.json` - Node definitions with hierarchy and repeatability + +## Test Scenarios Enabled + +1. **Scalar vs SEQUENCE resolution** - Fields in non-rep vs rep contexts +2. **Ancestor repeatability** - Fields under repeatable ancestors +3. **Cross-branch navigation** - Paths that backtrack through parent nodes +4. **Multiple data types** - Each node has fields of 7 different types diff --git a/src/test/resources/json/fields-sdk1.json b/src/test/resources/json/fields-sdk1.json deleted file mode 100644 index 2edcc39..0000000 --- a/src/test/resources/json/fields-sdk1.json +++ /dev/null @@ -1,184 +0,0 @@ -[ - { - "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 deleted file mode 100644 index 85338bc..0000000 --- a/src/test/resources/json/fields-sdk2.json +++ /dev/null @@ -1,230 +0,0 @@ -[ - { - "id": "BT-00-Text", - "alias": "textField", - "type": "text", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/TextField", - "xpathRelative": "PathNode/TextField" - }, - { - "id": "BT-00-Attribute", - "alias": "attributeField", - "type": "text", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/TextField/@Attribute", - "xpathRelative": "PathNode/TextField/@Attribute" - }, - { - "id": "BT-00-Indicator", - "alias": "indicatorField", - "type": "indicator", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/IndicatorField", - "xpathRelative": "PathNode/IndicatorField" - }, - { - "id": "BT-00-Code", - "alias": "codeField", - "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": "internalCodeField", - "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": "codeAttributeField", - "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": "textMultilingualField", - "type": "text-multilingual", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/TextMultilingualField", - "xpathRelative": "PathNode/TextMultilingualField" - }, - { - "id": "BT-00-StartDate", - "alias": "startDateField", - "type": "date", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/StartDateField", - "xpathRelative": "PathNode/StartDateField" - }, - { - "id": "BT-00-EndDate", - "alias": "endDateField", - "type": "date", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/EndDateField", - "xpathRelative": "PathNode/EndDateField" - }, - { - "id": "BT-00-StartTime", - "alias": "startTimeField", - "type": "time", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/StartTimeField", - "xpathRelative": "PathNode/StartTimeField" - }, - { - "id": "BT-00-EndTime", - "alias": "endTimeField", - "type": "time", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/EndTimeField", - "xpathRelative": "PathNode/EndTimeField" - }, - { - "id": "BT-00-Measure", - "alias": "measureField", - "type": "measure", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/MeasureField", - "xpathRelative": "PathNode/MeasureField" - }, - { - "id": "BT-00-Integer", - "alias": "integerField", - "type": "integer", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/IntegerField", - "xpathRelative": "PathNode/IntegerField" - }, - { - "id": "BT-00-Amount", - "alias": "amountField", - "type": "amount", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/AmountField", - "xpathRelative": "PathNode/AmountField" - }, - { - "id": "BT-00-Url", - "alias": "urlField", - "type": "url", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/UrlField", - "xpathRelative": "PathNode/UrlField" - }, - { - "id": "BT-00-Zoned-Date", - "alias": "zonedDateField", - "type": "zoned-date", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/ZonedDateField", - "xpathRelative": "PathNode/ZonedDateField" - }, - { - "id": "BT-00-Zoned-Time", - "alias": "zonedTimeField", - "type": "zoned-time", - "parentNodeId": "ND-Root", - "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", - "type": "id-ref", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/IdRefField", - "xpathRelative": "PathNode/IdRefField" - }, - { - "id": "BT-00-Number", - "alias": "numberField", - "type": "number", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/NumberField", - "xpathRelative": "PathNode/NumberField" - }, - { - "id": "BT-00-Phone", - "alias": "phoneField", - "type": "phone", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/PhoneField", - "xpathRelative": "PathNode/PhoneField" - }, - { - "id": "BT-00-Email", - "alias": "emailField", - "type": "email", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/EmailField", - "xpathRelative": "PathNode/EmailField" - }, - { - "id": "BT-01-SubLevel-Text", - "alias": "subLevel_textField", - "type": "text", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/ChildNode/SubLevelTextField", - "xpathRelative": "PathNode/ChildNode/SubLevelTextField" - }, - { - "id": "BT-01-SubNode-Text", - "alias": "subNode_textField", - "type": "text", - "parentNodeId": "ND-SubNode", - "xpathAbsolute": "/*/SubNode/SubTextField", - "xpathRelative": "SubTextField" - }, - { - "id": "BT-01-SubSubNode-Text", - "alias": "subSubNode_textField", - "type": "text", - "parentNodeId": "ND-SubSubNode", - "xpathAbsolute": "/*/SubNode/SubSubNode/SubTextField[0 = 0]", - "xpathRelative": "SubTextField" - }, - { - "id": "BT-00(a)-Text", - "alias": "textFieldWithParens", - "type": "text", - "parentNodeId": "ND-Root", - "xpathAbsolute": "/*/PathNode/TextFieldA", - "xpathRelative": "PathNode/TextFieldA" - }] diff --git a/src/test/resources/json/sdk1-fields.json b/src/test/resources/json/sdk1-fields.json new file mode 100644 index 0000000..f2f3870 --- /dev/null +++ b/src/test/resources/json/sdk1-fields.json @@ -0,0 +1,202 @@ +{ + "xmlStructure": [ + { + "id": "ND-Root", + "parentId": null, + "xpathAbsolute": "/*", + "xpathRelative": "/*", + "repeatable": false + }, + { + "id": "ND-SubNode", + "parentId": "ND-Root", + "xpathAbsolute": "/*/SubNode", + "xpathRelative": "SubNode", + "repeatable": false + } + ], + "fields": [ + { + "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/sdk2-fields.json b/src/test/resources/json/sdk2-fields.json new file mode 100644 index 0000000..2dd5990 --- /dev/null +++ b/src/test/resources/json/sdk2-fields.json @@ -0,0 +1,854 @@ +{ + "xmlStructure": [ + { + "id": "ND-Root", + "parentId": null, + "xpathAbsolute": "/*", + "xpathRelative": "/*", + "repeatable": false, + "alias": "Root" + }, + { + "id": "ND-SubNode", + "parentId": "ND-Root", + "xpathAbsolute": "/*/SubNode", + "xpathRelative": "SubNode", + "repeatable": false, + "alias": "SubNode" + }, + { + "id": "ND-SubSubNode", + "parentId": "ND-SubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode", + "xpathRelative": "SubSubNode", + "repeatable": false, + "alias": "SubSubNode" + }, + { + "id": "ND-SubSubNode2", + "parentId": "ND-SubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode2", + "xpathRelative": "SubSubNode2", + "repeatable": false, + "alias": "SubSubNode2" + }, + { + "id": "ND-RepeatableInSubNode", + "parentId": "ND-SubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode", + "xpathRelative": "RepeatableInSubNode", + "repeatable": true, + "alias": "RepeatableInSubNode" + }, + { + "id": "ND-RepeatableInSubNode2", + "parentId": "ND-SubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2", + "xpathRelative": "RepeatableInSubNode2", + "repeatable": true, + "alias": "RepeatableInSubNode2" + }, + { + "id": "ND-RepeatableNode", + "parentId": "ND-Root", + "xpathAbsolute": "/*/RepeatableNode", + "xpathRelative": "RepeatableNode", + "repeatable": true, + "alias": "RepeatableNode" + }, + { + "id": "ND-NonRepeatableSubNode", + "parentId": "ND-RepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode", + "xpathRelative": "NonRepeatableSubNode", + "repeatable": false, + "alias": "NonRepeatableSubNode" + }, + { + "id": "ND-NonRepeatableSubNode2", + "parentId": "ND-RepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2", + "xpathRelative": "NonRepeatableSubNode2", + "repeatable": false, + "alias": "NonRepeatableSubNode2" + }, + { + "id": "ND-RepeatableSubSubNode", + "parentId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode", + "xpathRelative": "RepeatableSubSubNode", + "repeatable": true, + "alias": "RepeatableSubSubNode" + }, + { + "id": "ND-RepeatableInRepeatableNode", + "parentId": "ND-RepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode", + "xpathRelative": "RepeatableInRepeatableNode", + "repeatable": true, + "alias": "RepeatableInRepeatableNode" + }, + { + "id": "ND-RepeatableInRepeatableNode2", + "parentId": "ND-RepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2", + "xpathRelative": "RepeatableInRepeatableNode2", + "repeatable": true, + "alias": "RepeatableInRepeatableNode2" + } + ], + "fields": [ + { + "id": "BT-00-Text", + "alias": "textField", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextField", + "xpathRelative": "PathNode/TextField" + }, + { + "id": "BT-00-Attribute", + "alias": "attributeField", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextField/@Attribute", + "xpathRelative": "PathNode/TextField/@Attribute" + }, + { + "id": "BT-00-Indicator", + "alias": "indicatorField", + "type": "indicator", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IndicatorField", + "xpathRelative": "PathNode/IndicatorField" + }, + { + "id": "BT-00-Code", + "alias": "codeField", + "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": "internalCodeField", + "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": "codeAttributeField", + "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": "textMultilingualField", + "type": "text-multilingual", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextMultilingualField", + "xpathRelative": "PathNode/TextMultilingualField" + }, + { + "id": "BT-00-StartDate", + "alias": "startDateField", + "type": "date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/StartDateField", + "xpathRelative": "PathNode/StartDateField" + }, + { + "id": "BT-00-EndDate", + "alias": "endDateField", + "type": "date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EndDateField", + "xpathRelative": "PathNode/EndDateField" + }, + { + "id": "BT-00-StartTime", + "alias": "startTimeField", + "type": "time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/StartTimeField", + "xpathRelative": "PathNode/StartTimeField" + }, + { + "id": "BT-00-EndTime", + "alias": "endTimeField", + "type": "time", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EndTimeField", + "xpathRelative": "PathNode/EndTimeField" + }, + { + "id": "BT-00-Measure", + "alias": "measureField", + "type": "measure", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/MeasureField", + "xpathRelative": "PathNode/MeasureField" + }, + { + "id": "BT-00-Integer", + "alias": "integerField", + "type": "integer", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IntegerField", + "xpathRelative": "PathNode/IntegerField" + }, + { + "id": "BT-00-Amount", + "alias": "amountField", + "type": "amount", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/AmountField", + "xpathRelative": "PathNode/AmountField" + }, + { + "id": "BT-00-Url", + "alias": "urlField", + "type": "url", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/UrlField", + "xpathRelative": "PathNode/UrlField" + }, + { + "id": "BT-00-Zoned-Date", + "alias": "zonedDateField", + "type": "zoned-date", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ZonedDateField", + "xpathRelative": "PathNode/ZonedDateField" + }, + { + "id": "BT-00-Zoned-Time", + "alias": "zonedTimeField", + "type": "zoned-time", + "parentNodeId": "ND-Root", + "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", + "type": "id-ref", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/IdRefField", + "xpathRelative": "PathNode/IdRefField" + }, + { + "id": "BT-00-Number", + "alias": "numberField", + "type": "number", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/NumberField", + "xpathRelative": "PathNode/NumberField" + }, + { + "id": "BT-00-Phone", + "alias": "phoneField", + "type": "phone", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/PhoneField", + "xpathRelative": "PathNode/PhoneField" + }, + { + "id": "BT-00-Email", + "alias": "emailField", + "type": "email", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/EmailField", + "xpathRelative": "PathNode/EmailField" + }, + { + "id": "BT-01-SubLevel-Text", + "alias": "subLevel_textField", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/ChildNode/SubLevelTextField", + "xpathRelative": "PathNode/ChildNode/SubLevelTextField" + }, + { + "id": "BT-01-SubNode-Text", + "alias": "subNode_textField", + "type": "text", + "parentNodeId": "ND-SubNode", + "xpathAbsolute": "/*/SubNode/SubTextField", + "xpathRelative": "SubTextField" + }, + { + "id": "BT-01-SubSubNode-Text", + "alias": "subSubNode_textField", + "type": "text", + "parentNodeId": "ND-SubSubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode/SubTextField[0 = 0]", + "xpathRelative": "SubTextField" + }, + { + "id": "BT-00(a)-Text", + "alias": "textFieldWithParens", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/TextFieldA", + "xpathRelative": "PathNode/TextFieldA" + }, + { + "id": "BT-00-Repeatable-Text", + "alias": "repeatableTextField", + "type": "text", + "parentNodeId": "ND-Root", + "xpathAbsolute": "/*/PathNode/RepeatableTextField", + "xpathRelative": "PathNode/RepeatableTextField", + "repeatable": { + "value": true + } + }, + { + "id": "BT-00-Text-In-Repeatable-Node", + "alias": "textInRepeatableNode", + "type": "text", + "parentNodeId": "ND-RepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/TextField", + "xpathRelative": "TextField" + }, + { + "id": "BT-00-Attribute-In-Repeatable-Node", + "alias": "attributeInRepeatableNode", + "type": "text", + "parentNodeId": "ND-RepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/TextField/@attribute", + "xpathRelative": "TextField/@attribute" + }, + { + "id": "BT-00-Text-In-NonRepeatableSubNode", + "alias": "textInNonRepeatableSubNode", + "type": "text", + "parentNodeId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/TextField", + "xpathRelative": "TextField" + }, + { + "id": "BT-00-Text-In-RepeatableSubSubNode", + "alias": "textInRepeatableSubSubNode", + "type": "text", + "parentNodeId": "ND-RepeatableSubSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode/TextField", + "xpathRelative": "TextField" + }, + { + "id": "BT-11-TextMultilingual", + "alias": "bt11TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-SubSubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-11-Indicator", + "alias": "bt11Indicator", + "type": "indicator", + "parentNodeId": "ND-SubSubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-11-Number", + "alias": "bt11Number", + "type": "number", + "parentNodeId": "ND-SubSubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-11-Date", + "alias": "bt11Date", + "type": "date", + "parentNodeId": "ND-SubSubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-11-Time", + "alias": "bt11Time", + "type": "time", + "parentNodeId": "ND-SubSubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-11-Measure", + "alias": "bt11Measure", + "type": "measure", + "parentNodeId": "ND-SubSubNode", + "xpathAbsolute": "/*/SubNode/SubSubNode/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-12-Text", + "alias": "bt12Text", + "type": "text", + "parentNodeId": "ND-SubSubNode2", + "xpathAbsolute": "/*/SubNode/SubSubNode2/Text", + "xpathRelative": "Text" + }, + { + "id": "BT-12-TextMultilingual", + "alias": "bt12TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-SubSubNode2", + "xpathAbsolute": "/*/SubNode/SubSubNode2/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-12-Indicator", + "alias": "bt12Indicator", + "type": "indicator", + "parentNodeId": "ND-SubSubNode2", + "xpathAbsolute": "/*/SubNode/SubSubNode2/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-12-Number", + "alias": "bt12Number", + "type": "number", + "parentNodeId": "ND-SubSubNode2", + "xpathAbsolute": "/*/SubNode/SubSubNode2/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-12-Date", + "alias": "bt12Date", + "type": "date", + "parentNodeId": "ND-SubSubNode2", + "xpathAbsolute": "/*/SubNode/SubSubNode2/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-12-Time", + "alias": "bt12Time", + "type": "time", + "parentNodeId": "ND-SubSubNode2", + "xpathAbsolute": "/*/SubNode/SubSubNode2/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-12-Measure", + "alias": "bt12Measure", + "type": "measure", + "parentNodeId": "ND-SubSubNode2", + "xpathAbsolute": "/*/SubNode/SubSubNode2/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-13-Text", + "alias": "bt13Text", + "type": "text", + "parentNodeId": "ND-RepeatableInSubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode/Text", + "xpathRelative": "Text" + }, + { + "id": "BT-13-TextMultilingual", + "alias": "bt13TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-RepeatableInSubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-13-Indicator", + "alias": "bt13Indicator", + "type": "indicator", + "parentNodeId": "ND-RepeatableInSubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-13-Number", + "alias": "bt13Number", + "type": "number", + "parentNodeId": "ND-RepeatableInSubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-13-Date", + "alias": "bt13Date", + "type": "date", + "parentNodeId": "ND-RepeatableInSubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-13-Time", + "alias": "bt13Time", + "type": "time", + "parentNodeId": "ND-RepeatableInSubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-13-Measure", + "alias": "bt13Measure", + "type": "measure", + "parentNodeId": "ND-RepeatableInSubNode", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-14-Text", + "alias": "bt14Text", + "type": "text", + "parentNodeId": "ND-RepeatableInSubNode2", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2/Text", + "xpathRelative": "Text" + }, + { + "id": "BT-14-TextMultilingual", + "alias": "bt14TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-RepeatableInSubNode2", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-14-Indicator", + "alias": "bt14Indicator", + "type": "indicator", + "parentNodeId": "ND-RepeatableInSubNode2", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-14-Number", + "alias": "bt14Number", + "type": "number", + "parentNodeId": "ND-RepeatableInSubNode2", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-14-Date", + "alias": "bt14Date", + "type": "date", + "parentNodeId": "ND-RepeatableInSubNode2", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-14-Time", + "alias": "bt14Time", + "type": "time", + "parentNodeId": "ND-RepeatableInSubNode2", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-14-Measure", + "alias": "bt14Measure", + "type": "measure", + "parentNodeId": "ND-RepeatableInSubNode2", + "xpathAbsolute": "/*/SubNode/RepeatableInSubNode2/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-21-TextMultilingual", + "alias": "bt21TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-21-Indicator", + "alias": "bt21Indicator", + "type": "indicator", + "parentNodeId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-21-Number", + "alias": "bt21Number", + "type": "number", + "parentNodeId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-21-Date", + "alias": "bt21Date", + "type": "date", + "parentNodeId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-21-Time", + "alias": "bt21Time", + "type": "time", + "parentNodeId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-21-Measure", + "alias": "bt21Measure", + "type": "measure", + "parentNodeId": "ND-NonRepeatableSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-22-Text", + "alias": "bt22Text", + "type": "text", + "parentNodeId": "ND-NonRepeatableSubNode2", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2/Text", + "xpathRelative": "Text" + }, + { + "id": "BT-22-TextMultilingual", + "alias": "bt22TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-NonRepeatableSubNode2", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-22-Indicator", + "alias": "bt22Indicator", + "type": "indicator", + "parentNodeId": "ND-NonRepeatableSubNode2", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-22-Number", + "alias": "bt22Number", + "type": "number", + "parentNodeId": "ND-NonRepeatableSubNode2", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-22-Date", + "alias": "bt22Date", + "type": "date", + "parentNodeId": "ND-NonRepeatableSubNode2", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-22-Time", + "alias": "bt22Time", + "type": "time", + "parentNodeId": "ND-NonRepeatableSubNode2", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-22-Measure", + "alias": "bt22Measure", + "type": "measure", + "parentNodeId": "ND-NonRepeatableSubNode2", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode2/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-23-Text", + "alias": "bt23Text", + "type": "text", + "parentNodeId": "ND-RepeatableInRepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode/Text", + "xpathRelative": "Text" + }, + { + "id": "BT-23-TextMultilingual", + "alias": "bt23TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-RepeatableInRepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-23-Indicator", + "alias": "bt23Indicator", + "type": "indicator", + "parentNodeId": "ND-RepeatableInRepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-23-Number", + "alias": "bt23Number", + "type": "number", + "parentNodeId": "ND-RepeatableInRepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-23-Date", + "alias": "bt23Date", + "type": "date", + "parentNodeId": "ND-RepeatableInRepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-23-Time", + "alias": "bt23Time", + "type": "time", + "parentNodeId": "ND-RepeatableInRepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-23-Measure", + "alias": "bt23Measure", + "type": "measure", + "parentNodeId": "ND-RepeatableInRepeatableNode", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-24-Text", + "alias": "bt24Text", + "type": "text", + "parentNodeId": "ND-RepeatableInRepeatableNode2", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2/Text", + "xpathRelative": "Text" + }, + { + "id": "BT-24-TextMultilingual", + "alias": "bt24TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-RepeatableInRepeatableNode2", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-24-Indicator", + "alias": "bt24Indicator", + "type": "indicator", + "parentNodeId": "ND-RepeatableInRepeatableNode2", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-24-Number", + "alias": "bt24Number", + "type": "number", + "parentNodeId": "ND-RepeatableInRepeatableNode2", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-24-Date", + "alias": "bt24Date", + "type": "date", + "parentNodeId": "ND-RepeatableInRepeatableNode2", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-24-Time", + "alias": "bt24Time", + "type": "time", + "parentNodeId": "ND-RepeatableInRepeatableNode2", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-24-Measure", + "alias": "bt24Measure", + "type": "measure", + "parentNodeId": "ND-RepeatableInRepeatableNode2", + "xpathAbsolute": "/*/RepeatableNode/RepeatableInRepeatableNode2/Measure", + "xpathRelative": "Measure" + }, + { + "id": "BT-25-TextMultilingual", + "alias": "bt25TextMultilingual", + "type": "text-multilingual", + "parentNodeId": "ND-RepeatableSubSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode/TextMultilingual", + "xpathRelative": "TextMultilingual" + }, + { + "id": "BT-25-Indicator", + "alias": "bt25Indicator", + "type": "indicator", + "parentNodeId": "ND-RepeatableSubSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode/Indicator", + "xpathRelative": "Indicator" + }, + { + "id": "BT-25-Number", + "alias": "bt25Number", + "type": "number", + "parentNodeId": "ND-RepeatableSubSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode/Number", + "xpathRelative": "Number" + }, + { + "id": "BT-25-Date", + "alias": "bt25Date", + "type": "date", + "parentNodeId": "ND-RepeatableSubSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode/Date", + "xpathRelative": "Date" + }, + { + "id": "BT-25-Time", + "alias": "bt25Time", + "type": "time", + "parentNodeId": "ND-RepeatableSubSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode/Time", + "xpathRelative": "Time" + }, + { + "id": "BT-25-Measure", + "alias": "bt25Measure", + "type": "measure", + "parentNodeId": "ND-RepeatableSubSubNode", + "xpathAbsolute": "/*/RepeatableNode/NonRepeatableSubNode/RepeatableSubSubNode/Measure", + "xpathRelative": "Measure" + } + ] +} From 99dde787876f090dab4365398cd78e4aecf6d09d Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Mon, 2 Feb 2026 05:02:12 +0100 Subject: [PATCH 3/6] Fix role attribute case in test expected output files - use uppercase --- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch index f5d7194..bcc45f2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch index e9c2ac2..0595d7d 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/dynamic/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch index f5d7194..bcc45f2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch index e9c2ac2..0595d7d 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testContextVariable_RepeatableField_UsedAsScalar/static/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch index c20b02d..77686d2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-003 + rule|text|R-SEQ-003 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch index f22f3a5..10b4476 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-003 + rule|text|R-SEQ-003 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch index c20b02d..77686d2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-003 + rule|text|R-SEQ-003 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch index f22f3a5..10b4476 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_BooleanSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-003 + rule|text|R-SEQ-003 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch index ee4bd72..4b264b2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-004 + rule|text|R-SEQ-004 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch index 5749e52..8a5a730 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-004 + rule|text|R-SEQ-004 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch index ee4bd72..4b264b2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-004 + rule|text|R-SEQ-004 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch index 5749e52..8a5a730 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DateSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-004 + rule|text|R-SEQ-004 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch index 1580f9e..ea49c8b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-006 + rule|text|R-SEQ-006 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch index 9d0f4de..22d604f 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-006 + rule|text|R-SEQ-006 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch index 1580f9e..ea49c8b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-006 + rule|text|R-SEQ-006 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch index 9d0f4de..22d604f 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_DurationSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-006 + rule|text|R-SEQ-006 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch index 4f670f3..d492aee 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-002 + rule|text|R-SEQ-002 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch index fa1b7dc..789b403 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-002 + rule|text|R-SEQ-002 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch index 4f670f3..d492aee 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-002 + rule|text|R-SEQ-002 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch index fa1b7dc..789b403 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_NumericSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-002 + rule|text|R-SEQ-002 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch index 48e0eaa..85a74e9 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-001 + rule|text|R-SEQ-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch index 9dfe996..e01442f 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-001 + rule|text|R-SEQ-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch index 48e0eaa..85a74e9 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-001 + rule|text|R-SEQ-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch index 9dfe996..e01442f 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TextSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-001 + rule|text|R-SEQ-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch index 08c1cab..bf82227 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-005 + rule|text|R-SEQ-005 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch index eee6107..8b32b4b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/dynamic/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-005 + rule|text|R-SEQ-005 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch index 08c1cab..bf82227 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-005 + rule|text|R-SEQ-005 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch index eee6107..8b32b4b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_TimeSequence_GlobalLevel/static/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-SEQ-005 + rule|text|R-SEQ-005 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch index 25c3634..945ab7d 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch index 8e0e369..2190852 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch index 25c3634..945ab7d 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch index 8e0e369..2190852 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 From 30c386c8e67418a35fb25f1bc6cb5bdc698116e7 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Mon, 2 Feb 2026 10:29:57 +0100 Subject: [PATCH 4/6] Replace Stream.toList() with Collectors.toList() for JDK 11 compatibility --- src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java | 2 +- .../europa/ted/eforms/sdk/schematron/SchematronGenerator.java | 3 ++- 2 files changed, 3 insertions(+), 2 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 f986a4b..570d49f 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -372,7 +372,7 @@ public String getNodeIdFromAlias(String alias) { @Override public List getAllNoticeSubtypeIds() { - return noticeTypesById.keySet().stream().map(String::toUpperCase).sorted().toList(); + return noticeTypesById.keySet().stream().map(String::toUpperCase).sorted().collect(Collectors.toList()); } protected HashMap indexFieldsByAlias() { diff --git a/src/main/java/eu/europa/ted/eforms/sdk/schematron/SchematronGenerator.java b/src/main/java/eu/europa/ted/eforms/sdk/schematron/SchematronGenerator.java index 7daf2ac..9ba4282 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/schematron/SchematronGenerator.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/schematron/SchematronGenerator.java @@ -20,6 +20,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -138,7 +139,7 @@ public String generatePattern(SchematronPattern pattern, SchematronOutputConfig model.put("rules", pattern.getRules()); model.put("tags", config.ruleNatures().stream() .map(Enum::name) - .toList()); + .collect(Collectors.toList())); template.process(model, writer); return writer.toString(); From f72320c1921b433ec245304b41bc999b233bde1e Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Tue, 3 Feb 2026 15:18:57 +0100 Subject: [PATCH 5/6] Fix broken documentation reference in test comment --- .../europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 00d5d4c..3f1f83b 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1983,9 +1983,9 @@ void testPredicateComparison_RepeatableFieldWithSomeSatisfies_Works() { // These tests verify that parentheses around field references in some...in // expressions are correctly parsed as sequences, not as single-element lists. // - // The issue: (FIELD) in "some text:$x in (FIELD) satisfies ..." is parsed - // as a scalar list instead of a parenthesized sequence reference. - // See: EFX-TYPE-SAFETY-FIXES.md Category B1 + // The issue: (FIELD) in "some text:$x in (FIELD) satisfies ..." was previously + // parsed as a scalar list instead of a parenthesized sequence reference. + // This has been fixed by the scalar/sequence grammar separation (TEDEFO-4808). @Test void testSomeSatisfies_ParenthesizedFieldReference_String() { From f96fe2cf99e45e146713fa8cd9f8939a4aa5050e Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Tue, 3 Feb 2026 15:38:08 +0100 Subject: [PATCH 6/6] Update SdkSymbolResolver to use List instead of Set for ancestry --- .../eu/europa/ted/eforms/sdk/SdkSymbolResolver.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 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 570d49f..faefcaf 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -421,9 +420,9 @@ private boolean isFieldRepeatableFromContext(final SdkField sdkField, final SdkF } // Use cached ancestry from node - Set contextAncestry = context != null + List contextAncestry = context != null ? context.getParentNode().getAncestry() - : Collections.emptySet(); + : Collections.emptyList(); // Walk up from the field's parent node toward root, looking for a repeatable // node @@ -459,9 +458,9 @@ private boolean isFieldRepeatableFromContext(final SdkField sdkField, final SdkN } // Use cached ancestry from node - Set contextAncestry = context != null + List contextAncestry = context != null ? context.getAncestry() - : Collections.emptySet(); + : Collections.emptyList(); // Walk up from the field's parent node toward root, looking for a repeatable // node @@ -510,9 +509,9 @@ public boolean isNodeRepeatableFromContext(final String nodeId, final String con private boolean isNodeRepeatableFromContext(final SdkNode sdkNode, final SdkNode contextNode) { // Use cached ancestry from node - Set contextAncestry = contextNode != null + List contextAncestry = contextNode != null ? contextNode.getAncestry() - : Collections.emptySet(); + : Collections.emptyList(); // Walk up from the node toward root, looking for a repeatable node String currentNodeId = sdkNode.getId();