From 90ad1c4f0e08d24746ea649bb502f3c4bce54af5 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 5 Sep 2026 19:49:57 +0200 Subject: [PATCH 01/10] pom: Set version to 2.0.0-alpha.8 and update dependencies --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 525baf7..79fd445 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ eu.europa.ted.eforms efx-toolkit-java - 2.0.0-SNAPSHOT + 2.0.0-alpha.8 jar EFX Toolkit for Java @@ -46,9 +46,9 @@ ${project.build.directory}/eforms-sdk/antlr4 - 1.16.0-SNAPSHOT - 2.0.0-SNAPSHOT - 1.8.0-SNAPSHOT + 1.16.0-beta.2 + 2.0.0-alpha.3 + 1.8.0 4.13.1 From e20b6606a7ea8f60636bbea0be9af46fe38be751 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 5 Sep 2026 19:50:25 +0200 Subject: [PATCH 02/10] docs: Add 2.0.0-alpha.8 changelog --- CHANGELOG.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 490f72e..d2ffd16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# EFX Toolkit 2.0.0-alpha.7 Release Notes +# EFX Toolkit 2.0.0-alpha.8 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,14 +6,15 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation ## In this release -This is an incremental update over 2.0.0-alpha.6. It refines SDK data-type resolution and updates the eForms Core dependency, while retaining full support for the EFX-2 grammar released with SDK 2.0.0-alpha.2. +This is an incremental update over 2.0.0-alpha.7. It adds support for the new EFX selector construct in both versions of the language, corrects the selection of the preferred language outside view templates, and moves to the eForms SDK grammars and eForms Core library released alongside it. -### Changes since 2.0.0-alpha.6 +### Changes since 2.0.0-alpha.7 -- Field data types are now taken directly from the SDK field definitions, removing a temporary `SdkSymbolResolver` heuristic that inferred the `duration` type from `measure` fields using the `duration-unit` codelist. Added regression tests covering measure and duration fields. -- Upgraded the eForms Core Java dependency to 1.7.0. +- **Selectors**: an EFX expression can now identify the XML elements a reference points to, instead of the values held in them. Written `&{reference}` in EFX-1, and `&{reference}` or `WITH context SELECT reference` in EFX-2, it transpiles to the path of the elements rather than to their values. Everywhere else in EFX a field reference means the field's value; a selector is the one place where an application needs to be told where a value lives. The first use of it is `privacy.undisclosedFieldSelector`, which has to name the elements a notice viewer must withhold from publication. Implemented in both translators, and covered by the compute dependency extractor. +- **Preferred language selection**: in EFX-1 the language of a multilingual text field is chosen implicitly, using a variable that the notice viewer's XSL defines. That variable exists only while a view template is being translated, so the selection is now applied there and nowhere else. +- Upgraded the eForms Core Java dependency to 1.8.0, which preserves predicates when paths are joined or given an axis. -The following sections describe the features of the 2.0.0 line, unchanged since 2.0.0-alpha.6. +The following sections describe the features of the 2.0.0 line, unchanged since 2.0.0-alpha.7. ### EFX-2 language support @@ -59,7 +60,7 @@ NOTE: Transpilation of EFX-1 to XPath and XSL in this version of the EFX Toolkit ## Breaking changes -No new breaking changes are introduced in 2.0.0-alpha.7. For the breaking changes introduced earlier in the 2.0.0 line, see the [2.0.0-alpha.6 release notes](https://github.com/OP-TED/efx-toolkit-java/releases/tag/2.0.0-alpha.6). +No new breaking changes are introduced in 2.0.0-alpha.8. For the breaking changes introduced earlier in the 2.0.0 line, see the [2.0.0-alpha.6 release notes](https://github.com/OP-TED/efx-toolkit-java/releases/tag/2.0.0-alpha.6). ## Future development @@ -76,7 +77,7 @@ Documentation for the EFX Toolkit is available at: Date: Sun, 6 Sep 2026 03:10:41 +0200 Subject: [PATCH 03/10] Keep the anchor and its predicates in a context override (TEDEFO-5170) --- CHANGELOG.md | 7 +- .../ted/efx/interfaces/ScriptGenerator.java | 7 +- .../efx/sdk1/EfxExpressionTranslatorV1.java | 10 +-- .../efx/sdk2/EfxExpressionTranslatorV2.java | 5 +- .../ted/efx/xpath/XPathContextualizer.java | 11 ++- .../ted/efx/xpath/XPathScriptGenerator.java | 8 +- .../sdk1/EfxExpressionTranslatorV1Test.java | 14 +++- .../sdk2/EfxExpressionTranslatorV2Test.java | 76 +++++++++++++------ 8 files changed, 94 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2ffd16..102bd1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,14 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation ## In this release -This is an incremental update over 2.0.0-alpha.7. It adds support for the new EFX selector construct in both versions of the language, corrects the selection of the preferred language outside view templates, and moves to the eForms SDK grammars and eForms Core library released alongside it. +This is an incremental update over 2.0.0-alpha.7. It adds support for the new EFX selector construct in both versions of the language, corrects the selection of the preferred language outside view templates and the transpilation of context overrides, and moves to the eForms SDK grammars and eForms Core library released alongside it. ### Changes since 2.0.0-alpha.7 - **Selectors**: an EFX expression can now identify the XML elements a reference points to, instead of the values held in them. Written `&{reference}` in EFX-1, and `&{reference}` or `WITH context SELECT reference` in EFX-2, it transpiles to the path of the elements rather than to their values. Everywhere else in EFX a field reference means the field's value; a selector is the one place where an application needs to be told where a value lives. The first use of it is `privacy.undisclosedFieldSelector`, which has to name the elements a notice viewer must withhold from publication. Implemented in both translators, and covered by the compute dependency extractor. - **Preferred language selection**: in EFX-1 the language of a multilingual text field is chosen implicitly, using a variable that the notice viewer's XSL defines. That variable exists only while a view template is being translated, so the selection is now applied there and nowhere else. -- Upgraded the eForms Core Java dependency to 1.8.0, which preserves predicates when paths are joined or given an axis. +- **Context overrides**: `context::field` now transpiles to a path that keeps the walk to the context. The override means the value of the field by a path that starts at the context, so where the context is not present in the notice there is no starting point and nothing is selected. Until now that walk was removed as redundant, which made `${field}` and `${context::field}` transpile to the same thing and left the override with no effect. A predicate written on the context, as in `ND-Lot[cbc:ID = 'LOT-0001']::BT-137`, was discarded as well, so the condition the author wrote had no effect either. Expressions using a context override therefore produce a longer path than before, one that yields nothing where the context is absent, and one that applies any predicate written on the context. No SDK content is affected: context overrides are not used in any released SDK. +- Upgraded the eForms Core Java dependency to 1.9.0, which preserves predicates when paths are joined or given an axis, and lets the caller decide how far a joined path is shortened. The following sections describe the features of the 2.0.0 line, unchanged since 2.0.0-alpha.7. @@ -80,4 +81,4 @@ This version of the EFX Toolkit has a compile-time dependency on the following e - eForms SDK 1.x.x (SDK 1.16.0-beta.2 grammar) - eForms SDK 2.0.0-alpha.3 -It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.8.0. +It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.9.0. diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 8b29079..63acfbc 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -328,7 +328,12 @@ default PathExpression composeFieldInExternalReference(final PathExpression exte /** * Joins two given path expressions into one by placing the second after the first and using the * proper delimiter. - * + * + *

+ * This is how the path of a context override is built: the second path is relative to the first, + * which is the context the override names. What that implies for the joined path is for the + * implementation to decide, in the terms of the language it targets. + * * @param first The part of the path that goes before the delimiter. * @param second The part of the path that goes after the delimiter. * @return The joined path expression. 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 4f8d9c5..fdac204 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -1134,12 +1134,11 @@ public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceCon */ @Override public void exitContextFieldSpecifier(ContextFieldSpecifierContext ctx) { - this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for - // the context field. + final PathExpression contextFieldPath = this.stack.pop(PathExpression.class); final String contextFieldId = getFieldId(ctx.fieldContext()); this.efxContext .push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId), - this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol()))); + contextFieldPath)); } @@ -1163,12 +1162,11 @@ public void exitFieldReferenceWithFieldContextOverride( */ @Override public void exitContextNodeSpecifier(ContextNodeSpecifierContext ctx) { - this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for - // the context node. + final PathExpression contextNodePath = this.stack.pop(PathExpression.class); final String contextNodeId = getNodeId(ctx.node); this.efxContext .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.symbol()))); + contextNodePath)); } /** 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 7180787..e4907b0 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2074,12 +2074,11 @@ public void exitFieldReferenceWithFieldContextOverride( */ @Override public void exitContextNodeSpecifier(ContextNodeSpecifierContext ctx) { - this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for - // the context node. + final PathExpression contextNodePath = this.stack.pop(PathExpression.class); final String contextNodeId = getNodeId(ctx.node); this.efxContext .push(new NodeContext(contextNodeId, this.symbols.getAbsolutePathOfNode(contextNodeId), - this.symbols.getRelativePathOfNode(contextNodeId, this.efxContext.symbol()))); + contextNodePath)); } /** 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 cfd0eb5..7d2c2bc 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -13,6 +13,7 @@ */ package eu.europa.ted.efx.xpath; +import eu.europa.ted.eforms.xpath.Simplification; import eu.europa.ted.eforms.xpath.XPathProcessor; import eu.europa.ted.efx.model.expressions.Expression; import eu.europa.ted.efx.model.expressions.PathExpression; @@ -39,9 +40,15 @@ public static PathExpression contextualize(final PathExpression contextXpath, return Expression.instantiate(result, xpath.getClass()); } - public static PathExpression join(final PathExpression first, final PathExpression second) { + /** + * Joins the path of a context to a path that is relative to it, shortening the result as far as + * the caller asks for. + */ + public static PathExpression join(final PathExpression first, final PathExpression second, + final Simplification simplification) { - String joinedXPath = XPathProcessor.join(first.getScript(), second.getScript()); + String joinedXPath = + XPathProcessor.join(first.getScript(), second.getScript(), simplification); 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 83c90a1..43d037e 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -27,6 +27,7 @@ import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.xpath.Simplification; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.TranslatorOptions; import eu.europa.ted.efx.model.expressions.Expression; @@ -298,7 +299,12 @@ public T composeParenthesizedExpression(T expression, Cla @Override public PathExpression joinPaths(final PathExpression first, final PathExpression second) { - return XPathContextualizer.join(first, second); + // Every step of both paths is kept. A context override means the value of the field by a path + // that starts at the context, so where the context is not present in the notice there is no + // starting point and nothing is selected. Shortening the path would remove the walk to the + // context and with it that condition, leaving an override indistinguishable from a plain + // reference. + return XPathContextualizer.join(first, second, Simplification.NONE); } @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java index dc0586b..8d1206b 100644 --- a/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1Test.java @@ -1089,13 +1089,21 @@ void testFieldReferenceInOtherNotice() { @Test void testFieldReferenceWithFieldContextOverride() { - testExpressionTranslationWithContext("../TextField/normalize-space(text())", "BT-00-Code", + testExpressionTranslationWithContext("../ChildNode/SubLevelTextField/../../TextField/normalize-space(text())", "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text"); } + @Test + void testFieldReferenceWithFieldContextOverride_WithPredicate() { + testExpressionTranslationWithContext( + "../ChildNode/SubLevelTextField['a' = 'a']/../../TextField/normalize-space(text())", + "BT-00-Code", + "BT-01-SubLevel-Text['a' == 'a']::BT-00-Text"); + } + @Test void testFieldReferenceWithFieldContextOverride_WithIntegerField() { - testExpressionTranslationWithContext("../IntegerField/number()", "BT-00-Code", + testExpressionTranslationWithContext("../ChildNode/SubLevelTextField/../../IntegerField/number()", "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Integer"); } @@ -1107,7 +1115,7 @@ void testFieldReferenceWithNodeContextOverride() { @Test void testFieldReferenceWithNodeContextOverride_WithPredicate() { - testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", + testExpressionTranslationWithContext("../..[PathNode/IndicatorField = true()]/PathNode/IntegerField/number()", "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::BT-00-Integer"); } 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 eebde7e..6768382 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1265,13 +1265,13 @@ void testFieldReferenceWithPredicate_WithFieldReferenceInPredicate() { @Test void testFieldReferenceWithFieldContextOverride() { - testExpressionTranslationWithContext("../TextField/normalize-space(text())", "BT-00-Code", + testExpressionTranslationWithContext("../ChildNode/SubLevelTextField/../../TextField/normalize-space(text())", "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text"); } @Test void testFieldReferenceWithFieldContextOverride_WithIntegerField() { - testExpressionTranslationWithContext("../IntegerField/number()", "BT-00-Code", + testExpressionTranslationWithContext("../ChildNode/SubLevelTextField/../../IntegerField/number()", "BT-00-Code", "BT-01-SubLevel-Text::integerField"); } @@ -1281,9 +1281,32 @@ void testFieldReferenceWithNodeContextOverride() { "ND-Root::integerField"); } + @Test + void testFieldReferenceWithNodeContextOverride_AbsoluteAnchor() { + testExpressionTranslationWithContext("/*/PathNode/TextField", "BT-00-Text", + "/ND-Root::BT-00-Text is present"); + } + + @Test + void testFieldReferenceWithFieldContextOverride_AbsoluteAnchor() { + testExpressionTranslationWithContext( + "/*/PathNode/ChildNode/SubLevelTextField/../../TextField", "BT-00-Code", + "/BT-01-SubLevel-Text::BT-00-Text is present"); + } + + @Test + void testFieldReferenceWithNodeContextOverride_WithPredicateFromTheSdk() { + // The anchor's path carries a predicate that comes from the SDK rather than from the + // expression. It has to survive for the same reason a written one does. + testExpressionTranslationWithContext( + "../FieldsPrivacy[FieldIdentifierCode/text()='rep-text-priv']/ReasonCode", + "BT-00-Text", + "ND-PrivacyForRepeatableText::BT-197(BT-00)-Repeatable-Text is present"); + } + @Test void testFieldReferenceWithNodeContextOverride_WithPredicate() { - testExpressionTranslationWithContext("../../PathNode/IntegerField/number()", "BT-00-Text", + testExpressionTranslationWithContext("../..[PathNode/IndicatorField = true()]/PathNode/IntegerField/number()", "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::integerField"); } @@ -3820,19 +3843,21 @@ void testLinkedProperty_InSequenceContext() { @Test void testFieldContextOverride_WithPredicateOnContext() { - // joinPaths simplifies: ../ChildNode/SubLevelTextField['a'='a']/../../TextField → ../TextField - // because navigating UP from a predicated node reaches the parent regardless + // The walk to the context is kept: the override says the value is reached by a path that + // starts at the context, so where no SubLevelTextField satisfies the predicate there is + // nothing to start from and nothing is selected. testExpressionTranslationWithContext( - "../TextField/normalize-space(text())", + "../ChildNode/SubLevelTextField['a' = 'a']/../../TextField/normalize-space(text())", "BT-00-Code", "BT-01-SubLevel-Text['a' == 'a']::BT-00-Text"); } @Test void testFieldContextOverride_WithIndexerOnContext() { - // joinPaths simplifies the indexed context path the same way as predicates + // An indexed context is kept for the same reason as a predicated one: without a first + // SubLevelTextField there is no starting point. testExpressionTranslationWithContext( - "../TextField/normalize-space(text())", + "../ChildNode/SubLevelTextField[1]/../../TextField/normalize-space(text())", "BT-00-Code", "BT-01-SubLevel-Text[1]::BT-00-Text"); } @@ -3840,7 +3865,7 @@ void testFieldContextOverride_WithIndexerOnContext() { @Test void testFieldContextOverride_InSequenceContext() { testExpressionTranslationWithContext( - "'test' = ../TextField/normalize-space(text())", + "'test' = ../ChildNode/SubLevelTextField/../../TextField/normalize-space(text())", "BT-00-Code", "'test' in BT-01-SubLevel-Text::BT-00-Text"); } @@ -3855,9 +3880,10 @@ void testNodeContextOverride_InSequenceContext() { @Test void testStackedOverride_NodeAndField() { - // joinPaths simplifies: .../ChildNode/SubLevelTextField/../../TextField → .../TextField + // The walk to the context is kept even with nothing written on it, so the override + // selects nothing where the context is absent. testExpressionTranslationWithContext( - "../../PathNode/TextField/normalize-space(text())", + "../../PathNode/ChildNode/SubLevelTextField/../../TextField/normalize-space(text())", "BT-00-Text", "ND-Root::BT-01-SubLevel-Text::BT-00-Text"); } @@ -3866,7 +3892,7 @@ void testStackedOverride_NodeAndField() { @Test void testFieldContextOverride_InPresenceCondition() { testExpressionTranslationWithContext( - "../TextField", + "../ChildNode/SubLevelTextField/../../TextField", "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Text is present"); } @@ -3875,7 +3901,7 @@ void testFieldContextOverride_InPresenceCondition() { @Test void testFieldContextOverride_InAttributeContext() { testExpressionTranslationWithContext( - "../CodeField/@listName", + "../ChildNode/SubLevelTextField/../../CodeField/@listName", "BT-00-Code", "BT-01-SubLevel-Text::BT-00-Code/@listName"); } @@ -3884,7 +3910,7 @@ void testFieldContextOverride_InAttributeContext() { @Test void testFieldContextOverride_WithPredicate_InSequenceContext() { testExpressionTranslationWithContext( - "'test' = ../TextField/normalize-space(text())", + "'test' = ../ChildNode/SubLevelTextField['a' = 'a']/../../TextField/normalize-space(text())", "BT-00-Code", "'test' in BT-01-SubLevel-Text['a' == 'a']::BT-00-Text"); } @@ -3893,7 +3919,7 @@ void testFieldContextOverride_WithPredicate_InSequenceContext() { @Test void testFieldContextOverride_WithPredicate_InPresenceCondition() { testExpressionTranslationWithContext( - "../TextField", + "../ChildNode/SubLevelTextField['a' = 'a']/../../TextField", "BT-00-Code", "BT-01-SubLevel-Text['a' == 'a']::BT-00-Text is present"); } @@ -3902,7 +3928,7 @@ void testFieldContextOverride_WithPredicate_InPresenceCondition() { @Test void testFieldContextOverride_WithPredicate_InAttributeContext() { testExpressionTranslationWithContext( - "../CodeField/@listName", + "../ChildNode/SubLevelTextField['a' = 'a']/../../CodeField/@listName", "BT-00-Code", "BT-01-SubLevel-Text['a' == 'a']::BT-00-Code/@listName"); } @@ -3911,7 +3937,7 @@ void testFieldContextOverride_WithPredicate_InAttributeContext() { @Test void testFieldContextOverride_WithIndexer_InSequenceContext() { testExpressionTranslationWithContext( - "'test' = ../TextField/normalize-space(text())", + "'test' = ../ChildNode/SubLevelTextField[1]/../../TextField/normalize-space(text())", "BT-00-Code", "'test' in BT-01-SubLevel-Text[1]::BT-00-Text"); } @@ -3920,7 +3946,7 @@ void testFieldContextOverride_WithIndexer_InSequenceContext() { @Test void testFieldContextOverride_WithIndexer_InPresenceCondition() { testExpressionTranslationWithContext( - "../TextField", + "../ChildNode/SubLevelTextField[1]/../../TextField", "BT-00-Code", "BT-01-SubLevel-Text[1]::BT-00-Text is present"); } @@ -3929,7 +3955,7 @@ void testFieldContextOverride_WithIndexer_InPresenceCondition() { @Test void testFieldContextOverride_WithIndexer_InAttributeContext() { testExpressionTranslationWithContext( - "../CodeField/@listName", + "../ChildNode/SubLevelTextField[1]/../../CodeField/@listName", "BT-00-Code", "BT-01-SubLevel-Text[1]::BT-00-Code/@listName"); } @@ -3956,7 +3982,7 @@ void testNodeContextOverride_InAttributeContext() { @Test void testNodeContextOverride_WithPredicate_InSequenceContext() { testExpressionTranslationWithContext( - "'test' = ../../PathNode/TextField/normalize-space(text())", + "'test' = ../..[PathNode/IndicatorField = true()]/PathNode/TextField/normalize-space(text())", "BT-00-Text", "'test' in ND-Root[BT-00-Indicator == TRUE]::BT-00-Text"); } @@ -3965,7 +3991,7 @@ void testNodeContextOverride_WithPredicate_InSequenceContext() { @Test void testNodeContextOverride_WithPredicate_InPresenceCondition() { testExpressionTranslationWithContext( - "../../PathNode/TextField", + "../..[PathNode/IndicatorField = true()]/PathNode/TextField", "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::BT-00-Text is present"); } @@ -3974,7 +4000,7 @@ void testNodeContextOverride_WithPredicate_InPresenceCondition() { @Test void testNodeContextOverride_WithPredicate_InAttributeContext() { testExpressionTranslationWithContext( - "../../PathNode/CodeField/@listName", + "../..[PathNode/IndicatorField = true()]/PathNode/CodeField/@listName", "BT-00-Text", "ND-Root[BT-00-Indicator == TRUE]::BT-00-Code/@listName"); } @@ -4010,7 +4036,7 @@ void testVariableContextOverride_InAttributeContext() { @Test void testStackedOverride_NodeAndField_InSequenceContext() { testExpressionTranslationWithContext( - "'test' = ../../PathNode/TextField/normalize-space(text())", + "'test' = ../../PathNode/ChildNode/SubLevelTextField/../../TextField/normalize-space(text())", "BT-00-Text", "'test' in ND-Root::BT-01-SubLevel-Text::BT-00-Text"); } @@ -4019,7 +4045,7 @@ void testStackedOverride_NodeAndField_InSequenceContext() { @Test void testStackedOverride_NodeAndField_InPresenceCondition() { testExpressionTranslationWithContext( - "../../PathNode/TextField", + "../../PathNode/ChildNode/SubLevelTextField/../../TextField", "BT-00-Text", "ND-Root::BT-01-SubLevel-Text::BT-00-Text is present"); } @@ -4028,7 +4054,7 @@ void testStackedOverride_NodeAndField_InPresenceCondition() { @Test void testStackedOverride_NodeAndField_InAttributeContext() { testExpressionTranslationWithContext( - "../../PathNode/CodeField/@listName", + "../../PathNode/ChildNode/SubLevelTextField/../../CodeField/@listName", "BT-00-Text", "ND-Root::BT-01-SubLevel-Text::BT-00-Code/@listName"); } From 970fe4cb35b1b759a7c736b6a405890b2f380c13 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 6 Sep 2026 03:10:52 +0200 Subject: [PATCH 04/10] pom: Depend on eForms Core 1.9.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 79fd445..3a007c9 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ 1.16.0-beta.2 2.0.0-alpha.3 - 1.8.0 + 1.9.0-SNAPSHOT 4.13.1 From bd90ed9ddaea28bc8b49f2527e3c88c24e6e0514 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 6 Sep 2026 13:18:31 +0200 Subject: [PATCH 05/10] Follow Simplification into XPathProcessor --- src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java | 2 +- src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java index 7d2c2bc..4d5da9c 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java @@ -13,7 +13,7 @@ */ package eu.europa.ted.efx.xpath; -import eu.europa.ted.eforms.xpath.Simplification; +import eu.europa.ted.eforms.xpath.XPathProcessor.Simplification; import eu.europa.ted.eforms.xpath.XPathProcessor; import eu.europa.ted.efx.model.expressions.Expression; import eu.europa.ted.efx.model.expressions.PathExpression; 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 43d037e..51bf005 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -27,7 +27,7 @@ import eu.europa.ted.eforms.sdk.component.SdkComponent; import eu.europa.ted.eforms.sdk.component.SdkComponentType; -import eu.europa.ted.eforms.xpath.Simplification; +import eu.europa.ted.eforms.xpath.XPathProcessor.Simplification; import eu.europa.ted.efx.interfaces.ScriptGenerator; import eu.europa.ted.efx.interfaces.TranslatorOptions; import eu.europa.ted.efx.model.expressions.Expression; From 7a02dddc07034e401bd50515c5004310ec22916b Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 6 Sep 2026 13:18:37 +0200 Subject: [PATCH 06/10] pom: Pin eForms Core to the released 1.9.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3a007c9..408340b 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ 1.16.0-beta.2 2.0.0-alpha.3 - 1.9.0-SNAPSHOT + 1.9.0 4.13.1 From 187974cb2545e9533cbda52b8fcaf3df51746f31 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 6 Sep 2026 14:20:27 +0200 Subject: [PATCH 07/10] docs: Reword the 2.0.0-alpha.8 release notes --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 102bd1c..89fdf5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,14 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation ## In this release -This is an incremental update over 2.0.0-alpha.7. It adds support for the new EFX selector construct in both versions of the language, corrects the selection of the preferred language outside view templates and the transpilation of context overrides, and moves to the eForms SDK grammars and eForms Core library released alongside it. +This is an incremental update over 2.0.0-alpha.7. ### Changes since 2.0.0-alpha.7 -- **Selectors**: an EFX expression can now identify the XML elements a reference points to, instead of the values held in them. Written `&{reference}` in EFX-1, and `&{reference}` or `WITH context SELECT reference` in EFX-2, it transpiles to the path of the elements rather than to their values. Everywhere else in EFX a field reference means the field's value; a selector is the one place where an application needs to be told where a value lives. The first use of it is `privacy.undisclosedFieldSelector`, which has to name the elements a notice viewer must withhold from publication. Implemented in both translators, and covered by the compute dependency extractor. -- **Preferred language selection**: in EFX-1 the language of a multilingual text field is chosen implicitly, using a variable that the notice viewer's XSL defines. That variable exists only while a view template is being translated, so the selection is now applied there and nowhere else. -- **Context overrides**: `context::field` now transpiles to a path that keeps the walk to the context. The override means the value of the field by a path that starts at the context, so where the context is not present in the notice there is no starting point and nothing is selected. Until now that walk was removed as redundant, which made `${field}` and `${context::field}` transpile to the same thing and left the override with no effect. A predicate written on the context, as in `ND-Lot[cbc:ID = 'LOT-0001']::BT-137`, was discarded as well, so the condition the author wrote had no effect either. Expressions using a context override therefore produce a longer path than before, one that yields nothing where the context is absent, and one that applies any predicate written on the context. No SDK content is affected: context overrides are not used in any released SDK. -- Upgraded the eForms Core Java dependency to 1.9.0, which preserves predicates when paths are joined or given an axis, and lets the caller decide how far a joined path is shortened. +- **Selectors**: an EFX expression can now yield the path of the XML elements a reference points to, instead of the values held in them. Write `&{reference}` in EFX-1, and `&{reference}` or `WITH context SELECT reference` in EFX-2. The first use of this new feature is in the `privacy.undisclosedFieldSelector` property in `fields.json` of SDK 1.16.0-beta.2 and SDK 2.0.0-alpha.3. The new property identifies the elements that must be masked and effectively withheld from publication. +- **Preferred language selection**: fixed an issue with the implicit invocation in EFX-1 of the `preferred-language-text` function when multilingual text fields are referenced. The function is now called correctly only when transpiling view templates, which is the only context where this functionality is applicable. EFX-2 requires explicit invocation of the function and is therefore unaffected by this fix. +- **Context overrides**: fixed multiple issues that caused the transpiler to produce valid but inaccurate XPaths when using a context override (`context::field`) to modify the path through which the value of a field is reached. Transpilation now preserves navigation steps and predicates accurately, and expressions using a context override will now produce a longer XPath, which however honours all predicates and enforces the presence of the context to select the designated value(s). This feature has not been used so far in any published SDK, so this change does not have an impact on the interpretation of existing rules and templates published in any SDK version. +- The eForms Core Java dependency is now 1.9.0. The following sections describe the features of the 2.0.0 line, unchanged since 2.0.0-alpha.7. @@ -78,7 +78,7 @@ Documentation for the EFX Toolkit is available at: Date: Sun, 6 Sep 2026 14:43:19 +0200 Subject: [PATCH 08/10] docs: Describe the preferred-language methods where they are implemented --- .../ted/efx/interfaces/ScriptGenerator.java | 4 +++- .../efx/sdk1/xpath/XPathScriptGeneratorV1.java | 5 ----- .../ted/efx/xpath/XPathScriptGenerator.java | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 63acfbc..50d8d7d 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -804,7 +804,9 @@ public StringExpression composeReplaceRegexFunction(StringExpression text, * Given a reference to a multilingual field, this function should generate the target language script * that returns the text value of the field in the preferred language. * - * Calling the function in EFX 2 + * In EFX-2 the template author calls this explicitly, through the + * {@code preferred-language-text} function. EFX-1 has no such function, so template + * translation applies it implicitly to every multilingual field it renders. * * @since SDK 2.0.0 * @see #getPreferredLanguage(PathExpression) 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 1a90ac4..82d92d5 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 @@ -80,11 +80,6 @@ public BooleanExpression composePatternMatchCondition(StringExpression expressio * If the reference already comes with a predicate that filters by @languageID, * then the template author has already pinned a language and the value is * retrieved as-is. - * - * Both EFX-1 and EFX-2 implementations of the feature rely on the existence of a - * $PREFERRED_LANGUAGES variable in the XSLT. - * This function returns the list of languages used in the visualisation in the - * order of preference (visualisation language followed by notice language(s)). */ @Override public StringExpression getTextInPreferredLanguage(final PathExpression fieldReference) { 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 51bf005..f221913 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -790,11 +790,27 @@ public StringLiteral getStringLiteralFromUnquotedString(String value) { return new StringLiteral("'" + value + "'"); } + /** + * Emits a call to {@code efx:preferred-language}, a function of the notice viewer's XSLT runtime + * library. It returns the identifier of the first language, among those the visualisation + * prefers, for which the field holds a value. + * + * @see #getTextInPreferredLanguage(PathExpression) + */ @Override public StringExpression getPreferredLanguage(PathExpression fieldReference) { return new StringExpression("efx:preferred-language(" + fieldReference.getScript() + ")"); } + /** + * Emits a call to {@code efx:preferred-language-text}, a function of the notice viewer's XSLT + * runtime library. + * + * Both EFX-1 and EFX-2 reach this method, and the function they call relies on a + * {@code $PREFERRED_LANGUAGES} variable defined by the XSLT. That variable holds the languages + * used in the visualisation, in order of preference: the visualisation language followed by the + * notice languages. + */ @Override public StringExpression getTextInPreferredLanguage(PathExpression fieldReference) { return new StringExpression("efx:preferred-language-text(" + fieldReference.getScript() + ")"); From be45e20a5d219dd5528f3751f807b4f812923f4f Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 6 Sep 2026 15:44:29 +0200 Subject: [PATCH 09/10] docs: Record the XPathContextualizer.join signature change as breaking --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89fdf5d..53b7be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,7 @@ NOTE: Transpilation of EFX-1 to XPath and XSL in this version of the EFX Toolkit ## Breaking changes -No new breaking changes are introduced in 2.0.0-alpha.8. For the breaking changes introduced earlier in the 2.0.0 line, see the [2.0.0-alpha.6 release notes](https://github.com/OP-TED/efx-toolkit-java/releases/tag/2.0.0-alpha.6). +`XPathContextualizer.join` now takes a third argument, which says how far the joined path may be shortened; the two-argument form has been removed. For the breaking changes introduced earlier in the 2.0.0 line, see the [2.0.0-alpha.6 release notes](https://github.com/OP-TED/efx-toolkit-java/releases/tag/2.0.0-alpha.6). ## Future development From d720a5921a2e9f2b150a521a3b4440f6af77cb6e Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 6 Sep 2026 16:33:39 +0200 Subject: [PATCH 10/10] pom: Bump version to 2.0.0-SNAPSHOT and update dependencies --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 408340b..345745e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ eu.europa.ted.eforms efx-toolkit-java - 2.0.0-alpha.8 + 2.0.0-SNAPSHOT jar EFX Toolkit for Java @@ -46,9 +46,9 @@ ${project.build.directory}/eforms-sdk/antlr4 - 1.16.0-beta.2 - 2.0.0-alpha.3 - 1.9.0 + 1.16.0-SNAPSHOT + 2.0.0-SNAPSHOT + 1.10.0-SNAPSHOT 4.13.1