diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 098a49a0..ab48d691 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,8 +12,8 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: java-version: '11' distribution: 'adopt' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4c5c702d..9d15d2a3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,14 +19,14 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Import GPG Key - uses: crazy-max/ghaction-import-gpg@v5 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Set up Java for publishing to Maven Central Repository - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '11' distribution: 'adopt' diff --git a/CHANGELOG.md b/CHANGELOG.md index b78fa146..802455aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# EFX Toolkit 2.0.0-alpha.3 Release Notes +# EFX Toolkit 2.0.0-alpha.4 Release Notes _The EFX Toolkit for Java developers is a library that enables the transpilation of [EFX](https://docs.ted.europa.eu/eforms/latest/efx) expressions and templates to different target languages. It also includes an implementation of an EFX-to-XPath transpiler._ @@ -6,8 +6,12 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation ## In this release -This release fixes an a bug that caused an exception to be thrown by XSLT processors when trying to format sequences of dates or times. -This bug was reported by a user in [eForms Notice Viewer issue #88](https://github.com/OP-TED/eforms-notice-viewer/issues/88). +This release fixes an a bug that caused variables and parameters to be in the wrong order in the generated XSL. + +A "qualifier" parameter was added in various methods, to allow the use of the corresponding new feature in the eForms Core Library 1.4.0. + +The dependency on ANTLR was updated to version 4.13.1. + ## EFX-1 Support @@ -22,7 +26,7 @@ The new version of EFX is still under development and will be released with SDK ## Breaking changes -For users of the Toolkit that have implemented custom transpilers, this release contains a few breaking changes. +For users of the Toolkit that have implemented custom transpilers, this release contains a few breaking changes from 1.3.0. More specifically: - Some additional methods have been added to the SymbolResolver, ScriptGenerator and MarkupGenerator API. As a guide for your implementations please look a the implementations included in the EFX Toolkit for use by the EFX-to-XPath transpilation. @@ -33,7 +37,7 @@ Users of the Toolkit that only use the included EFX-to-XPath transpiler will not ## Future development -Further alpha and beta releases of SDK 2 and EFX Toolkit 2 will be issued. While in "alpha" development stage, further braking changes may be introduced. SDK 2 and EFX 2 are expected to continue to be under development through the first quarter of 2024. +Further alpha and beta releases of SDK 2 and EFX Toolkit 2 will be issued. While in "alpha" development stage, further breaking changes may be introduced. --- @@ -49,4 +53,4 @@ This version of the EFX Toolkit has a compile-time dependency on the following v - eForms SDK 1.x.x - eForms SDK 2.0.0-alpha.1 -It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.3.0. +It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.4.0. diff --git a/pom.xml b/pom.xml index d50786a3..a89daf04 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ eu.europa.ted.eforms efx-toolkit-java - 2.0.0-alpha.3 + 2.0.0-alpha.4 jar EFX Toolkit for Java @@ -49,7 +49,7 @@ UTF-8 - 2023-07-28T16:03:53Z + 2024-08-02T09:53:37Z s01.oss.sonatype.org @@ -59,10 +59,10 @@ ${project.build.directory}/eforms-sdk/antlr4 - 1.3.0 + 1.4.0 - 4.9.3 + 4.13.1 3.12.0 1.2.11 2.13.4 @@ -80,7 +80,7 @@ 1.5 1.6.7 3.2.1 - 3.0.0-M7 + 3.2.5 diff --git a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java index 65134e81..8f4c4da9 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java @@ -4,6 +4,7 @@ import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import eu.europa.ted.eforms.sdk.component.SdkComponentFactory; import eu.europa.ted.eforms.sdk.component.SdkComponentType; @@ -19,12 +20,45 @@ private ComponentFactory() { super(); } + class VersionQualifier { + private final String sdkVersion; + private final String qualifier; + + VersionQualifier(String sdkVersion, String qualifier) { + this.sdkVersion = sdkVersion; + this.qualifier = qualifier; + } + + @Override + public int hashCode() { + return Objects.hash(sdkVersion, qualifier); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + VersionQualifier other = (VersionQualifier) obj; + if (!getEnclosingInstance().equals(other.getEnclosingInstance())) + return false; + return Objects.equals(sdkVersion, other.sdkVersion) + && Objects.equals(qualifier, other.qualifier); + } + + private ComponentFactory getEnclosingInstance() { + return ComponentFactory.this; + } + } /** - * EfxToXpathSymbols is implemented as a "kind-of" singleton. One instance per version of the - * eForms SDK. + * Symbol resolver is a "kind-of" singleton. One instance per version of the + * eForms SDK and per qualifier. */ - private static final Map instances = new HashMap<>(); + private static final Map instances = new HashMap<>(); /** * Gets the single instance containing the symbols defined in the given version of the eForms SDK. @@ -37,10 +71,27 @@ private ComponentFactory() { */ public static SymbolResolver getSymbolResolver(final String sdkVersion, final Path sdkRootPath) throws InstantiationException { - return instances.computeIfAbsent(sdkVersion, k -> { + return getSymbolResolver(sdkVersion, "", sdkRootPath); + } + + /** + * Gets the single instance containing the symbols defined in the given version of the eForms SDK. + * + * @param sdkVersion Version of the SDK + * @param qualifier Qualifier to choose between several implementations + * @param sdkRootPath Path to the root of the SDK + * @return The single instance containing the symbols defined in the given version of the eForms + * SDK. + * @throws InstantiationException If the SDK version is not supported. + */ + public static SymbolResolver getSymbolResolver(final String sdkVersion, final String qualifier, + final Path sdkRootPath) throws InstantiationException { + VersionQualifier key = ComponentFactory.INSTANCE.new VersionQualifier(sdkVersion, qualifier); + + return instances.computeIfAbsent(key, k -> { try { return ComponentFactory.INSTANCE.getComponentImpl(sdkVersion, - SdkComponentType.SYMBOL_RESOLVER, SymbolResolver.class, sdkVersion, + SdkComponentType.SYMBOL_RESOLVER, qualifier, SymbolResolver.class, sdkVersion, sdkRootPath); } catch (InstantiationException e) { throw new RuntimeException(MessageFormat.format( @@ -51,13 +102,23 @@ public static SymbolResolver getSymbolResolver(final String sdkVersion, final Pa public static MarkupGenerator getMarkupGenerator(final String sdkVersion, TranslatorOptions options) throws InstantiationException { + return getMarkupGenerator(sdkVersion, "", options); + } + + public static MarkupGenerator getMarkupGenerator(final String sdkVersion, final String qualifier, + TranslatorOptions options) throws InstantiationException { return ComponentFactory.INSTANCE.getComponentImpl(sdkVersion, - SdkComponentType.MARKUP_GENERATOR, MarkupGenerator.class, options); + SdkComponentType.MARKUP_GENERATOR, qualifier, MarkupGenerator.class, options); } public static ScriptGenerator getScriptGenerator(final String sdkVersion, TranslatorOptions options) throws InstantiationException { + return getScriptGenerator(sdkVersion, "", options); + } + + public static ScriptGenerator getScriptGenerator(final String sdkVersion, final String qualifier, + TranslatorOptions options) throws InstantiationException { return ComponentFactory.INSTANCE.getComponentImpl(sdkVersion, - SdkComponentType.SCRIPT_GENERATOR, ScriptGenerator.class, options); + SdkComponentType.SCRIPT_GENERATOR, qualifier, ScriptGenerator.class, options); } } diff --git a/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java b/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java index e40ae6bb..9bc54785 100644 --- a/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java +++ b/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java @@ -4,6 +4,9 @@ import eu.europa.ted.eforms.sdk.component.SdkComponentType; import eu.europa.ted.efx.interfaces.EfxExpressionTranslator; import eu.europa.ted.efx.interfaces.EfxTemplateTranslator; +import eu.europa.ted.efx.interfaces.MarkupGenerator; +import eu.europa.ted.efx.interfaces.ScriptGenerator; +import eu.europa.ted.efx.interfaces.SymbolResolver; import eu.europa.ted.efx.interfaces.TranslatorDependencyFactory; import eu.europa.ted.efx.interfaces.TranslatorOptions; @@ -16,17 +19,36 @@ private EfxTranslatorFactory() { public static EfxExpressionTranslator getEfxExpressionTranslator(final String sdkVersion, final TranslatorDependencyFactory factory, TranslatorOptions options) throws InstantiationException { + return getEfxExpressionTranslator(sdkVersion, "", factory, options); + } + + public static EfxExpressionTranslator getEfxExpressionTranslator(final String sdkVersion, + final String qualifier, final TranslatorDependencyFactory factory, TranslatorOptions options) + throws InstantiationException { + + SymbolResolver symbolResolver = factory.createSymbolResolver(sdkVersion, qualifier); + ScriptGenerator scriptGenerator = factory.createScriptGenerator(sdkVersion, qualifier, options); + return EfxTranslatorFactory.INSTANCE.getComponentImpl(sdkVersion, - SdkComponentType.EFX_EXPRESSION_TRANSLATOR, EfxExpressionTranslator.class, - factory.createSymbolResolver(sdkVersion), factory.createScriptGenerator(sdkVersion, options), - factory.createErrorListener()); + SdkComponentType.EFX_EXPRESSION_TRANSLATOR, qualifier, EfxExpressionTranslator.class, + symbolResolver, scriptGenerator, factory.createErrorListener()); } public static EfxTemplateTranslator getEfxTemplateTranslator(final String sdkVersion, final TranslatorDependencyFactory factory, TranslatorOptions options) throws InstantiationException { + return getEfxTemplateTranslator(sdkVersion, "", factory, options); + } + + public static EfxTemplateTranslator getEfxTemplateTranslator(final String sdkVersion, + final String qualifier, final TranslatorDependencyFactory factory, TranslatorOptions options) + throws InstantiationException { + + MarkupGenerator markupGenerator = factory.createMarkupGenerator(sdkVersion, qualifier, options); + SymbolResolver symbolResolver = factory.createSymbolResolver(sdkVersion, qualifier); + ScriptGenerator scriptGenerator = factory.createScriptGenerator(sdkVersion, qualifier, options); + return EfxTranslatorFactory.INSTANCE.getComponentImpl(sdkVersion, - SdkComponentType.EFX_TEMPLATE_TRANSLATOR, EfxTemplateTranslator.class, - factory.createMarkupGenerator(sdkVersion, options), factory.createSymbolResolver(sdkVersion), - factory.createScriptGenerator(sdkVersion, options), factory.createErrorListener()); + SdkComponentType.EFX_TEMPLATE_TRANSLATOR, qualifier, EfxTemplateTranslator.class, + markupGenerator, symbolResolver, scriptGenerator, factory.createErrorListener()); } } diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java index 69320ab2..59ad652d 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java @@ -37,9 +37,10 @@ public interface TranslatorDependencyFactory { * @param sdkVersion The version of the SDK that contains the version of the EFX grammar that the * EFX translator will attempt to translate. This is important as the symbols used in the * EFX expression are defined in the specific version of the SDK. + * @param qualifier Qualifier to choose between several implementations. * @return An instance of ScriptGenerator to be used by the EFX translator. */ - public SymbolResolver createSymbolResolver(String sdkVersion); + public SymbolResolver createSymbolResolver(String sdkVersion, String qualifier); /** * Creates a ScriptGenerator instance. @@ -50,10 +51,11 @@ public interface TranslatorDependencyFactory { * @param sdkVersion The version of the SDK that contains the version of the EFX grammar that the * EFX translator will attempt to translate. This is important as it defines the EFX * language features that ScriptGenerator instance should be able to handle. + * @param qualifier Qualifier to choose between several implementations. * @param options The options to be used by the ScriptGenerator. * @return An instance of ScriptGenerator to be used by the EFX translator. */ - public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options); + public ScriptGenerator createScriptGenerator(String sdkVersion, String qualifier, TranslatorOptions options); /** * Creates a MarkupGenerator instance. @@ -64,10 +66,11 @@ public interface TranslatorDependencyFactory { * @param sdkVersion The version of the SDK that contains the version of the EFX grammar that the * EFX translator will attempt to translate. This is important as it defines the EFX * language features that MarkupGenerator instance should be able to handle. + * @param qualifier Qualifier to choose between several implementations. * @param options The options to be used by the MarkupGenerator. * @return The instance of MarkupGenerator to be used by the EFX translator. */ - public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options); + public MarkupGenerator createMarkupGenerator(String sdkVersion, String qualifier, TranslatorOptions options); /** * Creates an error listener instance. diff --git a/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java index b019ad1e..360d071a 100644 --- a/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java +++ b/src/main/java/eu/europa/ted/efx/model/templates/ContentBlock.java @@ -122,9 +122,6 @@ public Context getParentContext() { public Set getOwnVariables() { Set variables = new LinkedHashSet<>(); - if (this.context != null && this.context.variable() != null) { - variables.add(this.context.variable()); - } variables.addAll(this.variables); return variables; } @@ -140,7 +137,7 @@ public Set getAllVariables() { } public Set getTemplateParameters() { - return this.getAllVariables().stream().map(v -> v.name).collect(Collectors.toSet()); + return this.getAllVariables().stream().map(v -> v.name).collect(Collectors.toCollection(LinkedHashSet::new)); } public Markup renderContent(MarkupGenerator markupGenerator) { 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 8d87234f..510852c1 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -629,6 +629,11 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) { String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx); if (fieldId != null) { Variable contextVariable = this.getContextVariable(ctx, contextPath); + if (contextVariable != null) { + VariableList variables = this.stack.pop(VariableList.class); + variables.add(contextVariable); + this.stack.push(variables); + } this.exitFieldContextDeclaration(fieldId, contextPath, contextVariable); } else { String nodeId = getNodeIdFromChildSimpleNodeReferenceContext(ctx); @@ -694,11 +699,6 @@ private Variable getContextVariable(ContextDeclarationContext ctx, } - @Override - public void enterTemplateVariableList(TemplateVariableListContext ctx) { - this.stack.push(new VariableList()); - } - // #region Variable Initializers -------------------------------------------- @Override @@ -740,7 +740,7 @@ private void exitVariableInitializer( this.script.composeVariableDeclaration(variableName, expression.getClass()), expression, this.script.composeVariableReference(variableName, expression.getClass())); - variables.push(variable); + variables.add(variable); this.stack.push(variables); this.stack.declareIdentifier(variable); } catch (Exception e) { @@ -752,20 +752,14 @@ private void exitVariableInitializer( // #endregion New in EFX-2 -------------------------------------------------- - /** - * This method changes the current EFX context. - * - * The EFX context is always assumed to be either a Field or a Node. Any predicate included in the - * EFX context declaration is not relevant and is ignored. - */ @Override - public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) { - if (ctx.templateVariableList() == null) { - this.stack.push(new VariableList()); - } + public void enterContextDeclarationBlock(ContextDeclarationBlockContext arg0) { + this.stack.push(new VariableList()); } + + // #endregion Context Declaration Blocks {...} ------------------------------ // #region Template lines -------------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java index e05c038f..65070d76 100644 --- a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java +++ b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java @@ -26,18 +26,19 @@ private DependencyFactoryMock() {} Map markupGenerators = new HashMap<>(); @Override - public SymbolResolver createSymbolResolver(String sdkVersion) { + public SymbolResolver createSymbolResolver(String sdkVersion, String qualifier) { + // Ignore the qualifier for unit tests return SymbolResolverMockFactory.getInstance(sdkVersion); } @Override - public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options) { + public ScriptGenerator createScriptGenerator(String sdkVersion, String qualifier, TranslatorOptions options) { // Default hashCode() implementation is OK here // we just need to distinguish TranslatorOptions instances - String key = sdkVersion + options.hashCode(); + String key = sdkVersion + qualifier + options.hashCode(); if (!scriptGenerators.containsKey(key)) { try { - this.scriptGenerators.put(key, ComponentFactory.getScriptGenerator(sdkVersion, options)); + this.scriptGenerators.put(key, ComponentFactory.getScriptGenerator(sdkVersion, qualifier, options)); } catch (InstantiationException e) { throw new RuntimeException(e.getMessage(), e); } @@ -46,11 +47,9 @@ public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOption } @Override - public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options) { - if (!this.markupGenerators.containsKey(sdkVersion)) { - this.markupGenerators.put(sdkVersion, new MarkupGeneratorMock()); - } - return this.markupGenerators.get(sdkVersion); + public MarkupGenerator createMarkupGenerator(String sdkVersion, String qualifier, TranslatorOptions options) { + String key = sdkVersion + qualifier; + return this.markupGenerators.computeIfAbsent(key, k -> new MarkupGeneratorMock()); } @Override 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 127f911d..7ed9036b 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -176,15 +176,15 @@ void testTemplateLine_VariableScope() { @Test void testTemplateLine_ContextVariable() { assertEquals( - lines("let block01(t, ctx) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", // + lines("let block01(ctx, t) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", // "for-each(.).call(block0101(ctx:$ctx, t:$t, t2:'test'))", // "for-each(.).call(block0102(ctx:$ctx, t:$t, t2:'test3')) }", // - "let block0101(t, ctx, t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", // + "let block0101(ctx, t, t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", // "for-each(.).call(block010101(ctx:$ctx, t:$t, t2:$t2))", // "for-each(.).call(block010102(ctx:$ctx, t:$t, t2:$t2)) }", // - "let block010101(t, ctx, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // - "let block010102(t, ctx, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // - "let block0102(t, ctx, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t2, $ctx)) }", // + "let block010101(ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // + "let block010102(ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", // + "let block0102(ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t2, $ctx)) }", // "for-each(/*/PathNode/TextField).call(block01(ctx:., t:./normalize-space(text())))"), // translateTemplate(lines( "{context:$ctx = BT-00-Text, text:$t = BT-00-Text} ${for text:$x in BT-00-Text return concat($x, $t)}",