From d5ddd4061f2fd3b12d04b88e15833dc9418e034f Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Mon, 31 Oct 2022 11:18:25 +0100
Subject: [PATCH 01/39] [Version]: Set development version to 1.2.1-SNAPSHOT.

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 259a1695..e47b4b59 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
 
   <groupId>eu.europa.ted.eforms</groupId>
   <artifactId>efx-toolkit-java</artifactId>
-  <version>1.2.0</version>
+  <version>1.2.1-SNAPSHOT</version>
   <packaging>jar</packaging>
 
   <name>EFX Toolkit for Java</name>

From 7a9e78b5653fdf298e5bf6c1e7269d48ea0875be Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Wed, 29 Mar 2023 23:46:15 +0200
Subject: [PATCH 02/39] [workflows]: Publish (to snapshots repository) when
 there are pushes on "develop".

---
 .github/workflows/publish.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 4399ecca..f56a6a71 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,6 +1,10 @@
 name: Publish package to the Maven Central Repository
 
 on:
+  push:
+    branches:
+      - 'develop'
+
   release:
     types: [created]
 

From 670a5f2b256663523af8e376da06de91b150f288 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Fri, 14 Apr 2023 00:23:22 +0200
Subject: [PATCH 03/39] XPath for sequences should now be rendered correctly.

---
 .../antlr4/eu/europa/ted/efx/xpath/XPath20.g4 |  11 +-
 .../ted/efx/xpath/XPathContextualizer.java    | 156 ++++++++++++++++--
 .../ted/efx/xpath/XPathScriptGenerator.java   |  24 +--
 .../ted/efx/EfxExpressionCombinedTest.java    |   6 +-
 .../ted/efx/EfxExpressionTranslatorTest.java  | 139 ++++++++++------
 .../ted/efx/EfxTemplateTranslatorTest.java    |   4 +-
 6 files changed, 252 insertions(+), 88 deletions(-)

diff --git a/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4 b/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4
index 50e996ed..893ef349 100644
--- a/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4
+++ b/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4
@@ -4,13 +4,9 @@
 // 
 // This is a faithful implementation of the XPath version 2.0 grammar
 // from the spec at https://www.w3.org/TR/xpath20/
-//
-// Note: Some minor adoptations were done
-// to simplify the translator using this grammar.
 
 grammar XPath20;
 
-
 // [1]
 xpath : expr EOF ;
 expr : exprsingle ( COMMA exprsingle)* ;
@@ -43,8 +39,8 @@ nodecomp : KW_IS | LL | GG ;
 // [25]
 pathexpr : ( SLASH relativepathexpr?) | ( SS relativepathexpr) | relativepathexpr ;
 relativepathexpr : stepexpr (( SLASH | SS) stepexpr)* ;
-stepexpr : step predicatelist;
-step: primaryexpr | reversestep | forwardstep; 
+stepexpr : filterexpr | axisstep ;
+axisstep : (reversestep | forwardstep) predicatelist ;
 forwardstep : (forwardaxis nodetest) | abbrevforwardstep ;
 // [30]
 forwardaxis : ( KW_CHILD COLONCOLON) | ( KW_DESCENDANT COLONCOLON) | ( KW_ATTRIBUTE COLONCOLON) | ( KW_SELF COLONCOLON) | ( KW_DESCENDANT_OR_SELF COLONCOLON) | ( KW_FOLLOWING_SIBLING COLONCOLON) | ( KW_FOLLOWING COLONCOLON) | ( KW_NAMESPACE COLONCOLON) ;
@@ -56,6 +52,7 @@ abbrevreversestep : DD ;
 nodetest : kindtest | nametest ;
 nametest : qname | wildcard ;
 wildcard : STAR | (NCName CS) | ( SC NCName) ;
+filterexpr : primaryexpr predicatelist ;
 predicatelist : predicate* ;
 // [40]
 predicate : OB expr CB ;
@@ -343,4 +340,4 @@ fragment FragChar : '\u0009' | '\u000a' | '\u000d'
 Whitespace :  ('\u000d' | '\u000a' | '\u0020' | '\u0009')+ -> skip ;
 
 // Not per spec. Specified for testing.
-SEMI : ';' ;
+SEMI : ';' ;
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
index f1413ec2..f3cffa4c 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
@@ -9,6 +9,7 @@
 import java.util.Queue;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+
 import org.antlr.v4.runtime.CharStream;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
@@ -16,22 +17,37 @@
 import org.antlr.v4.runtime.misc.Interval;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
+
 import eu.europa.ted.efx.model.Expression.PathExpression;
+import eu.europa.ted.efx.xpath.XPath20Parser.AxisstepContext;
+import eu.europa.ted.efx.xpath.XPath20Parser.FilterexprContext;
 import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext;
-import eu.europa.ted.efx.xpath.XPath20Parser.StepexprContext;
 
 public class XPathContextualizer extends XPath20BaseListener {
 
   private final CharStream inputStream;
-  private final Queue<StepInfo> steps = new LinkedList<>();
+  private final LinkedList<StepInfo> steps = new LinkedList<>();
 
   public XPathContextualizer(CharStream inputStream) {
     this.inputStream = inputStream;
   }
 
+  /**
+   * Parses the XPath represented by th e given {@link PathExpression}} and
+   * returns a queue containing a {@link StepInfo} object for each step that the
+   * XPath is comprised of.
+   */
   private static Queue<StepInfo> getSteps(PathExpression xpath) {
+    return getSteps(xpath.script);
+  }
+
+  /**
+   * Parses the given xpath and returns a queue containing a {@link StepInfo} for
+   * each step that the XPath is comprised of.
+   */
+  private static Queue<StepInfo> getSteps(String xpath) {
 
-    final CharStream inputStream = CharStreams.fromString(xpath.script);
+    final CharStream inputStream = CharStreams.fromString(xpath);
     final XPath20Lexer lexer = new XPath20Lexer(inputStream);
     final CommonTokenStream tokens = new CommonTokenStream(lexer);
     final XPath20Parser parser = new XPath20Parser(tokens);
@@ -44,7 +60,12 @@ private static Queue<StepInfo> getSteps(PathExpression xpath) {
     return contextualizer.steps;
   }
 
-
+  /**
+   * Makes the given xpath relative to the given context xpath.
+   * @param contextXpath
+   * @param xpath
+   * @return
+   */
   public static PathExpression contextualize(final PathExpression contextXpath,
       final PathExpression xpath) {
 
@@ -60,6 +81,57 @@ public static PathExpression contextualize(final PathExpression contextXpath,
     return getContextualizedXpath(contextSteps, pathSteps);
   }
 
+  public static PathExpression addPredicate(final PathExpression pathExpression, final String predicate) {
+    return new PathExpression(addPredicate(pathExpression.script, predicate));
+  }
+
+  /**
+   * Attempts to add a predicate to the given xpath.
+   * It will add the predicate to the last axis-step in the xpath.
+   * If there is no axis-step in the xpath then it will add the predicate to the last step.
+   * If the xpath is empty then it will still return a PathExpression but with an empty xpath.
+   */
+  public static String addPredicate(final String xpath, final String predicate) {
+    if (predicate == null) {
+      return xpath;
+    }
+    
+    String _predicate = predicate.trim();
+
+    if (_predicate.isEmpty()) {
+      return xpath;
+    }
+
+    if (!_predicate.startsWith("[")) {
+      _predicate = "[" + _predicate;
+    }
+
+    if (!_predicate.endsWith("]")) {
+      _predicate = _predicate + "]";
+    }
+
+    LinkedList<StepInfo> steps = new LinkedList<>(getSteps(xpath));
+
+    StepInfo lastAxisStep = getLastAxisStep(steps);
+    if (lastAxisStep != null) {
+      lastAxisStep.predicates.add(_predicate); 
+    } else if (steps.size() > 0) {
+      steps.getLast().predicates.add(_predicate);
+    }
+    return steps.stream().map(s -> s.stepText + s.getPredicateText()).collect(Collectors.joining("/"));
+  }
+
+  private static StepInfo getLastAxisStep(LinkedList<StepInfo> steps) {
+    int i = steps.size() - 1;
+    while (i >= 0 && !AxisStepInfo.class.isInstance(steps.get(i))) {
+      i--;
+    }
+    if (i < 0) {
+      return null;
+    }
+    return steps.get(i);
+  }
+
   public static PathExpression join(final PathExpression first, final PathExpression second) {
 
     if (first == null || first.script.trim().isEmpty()) {
@@ -173,9 +245,44 @@ private Boolean inPredicateMode() {
   }
 
   @Override
-  public void exitStepexpr(StepexprContext ctx) {
-    if (!inPredicateMode()) {
-      this.steps.offer(new StepInfo(ctx, this::getInputText));
+  public void exitAxisstep(AxisstepContext ctx) {
+    if (inPredicateMode()) {
+      return;
+    }
+
+    // When we recognize a step, we add it to the queue if is is empty.
+    // If the queue is not empty, and the depth of the new step is not smaller than
+    // the depth of the last step in the queue, then this step needs to be added to
+    // the queue too.
+    // Otherwise, the last step in the queue is a sub-expression of the new step,
+    // and we need to
+    // replace it in the queue with the new step.
+    if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) {
+      this.steps.offer(new AxisStepInfo(ctx, this::getInputText));
+    } else {
+      Interval removedInterval = ctx.getSourceInterval();
+      while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) {
+        this.steps.removeLast();
+      }
+      this.steps.offer(new AxisStepInfo(ctx, this::getInputText));
+    }    
+  }
+
+  @Override
+  public void exitFilterexpr(FilterexprContext ctx) {
+    if (inPredicateMode()) {
+      return;
+    }
+
+    // Same logic as for axis steps here (sse exitAxisstep).
+    if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) {
+      this.steps.offer(new FilterStepInfo(ctx, this::getInputText));
+    } else {
+      Interval removedInterval = ctx.getSourceInterval();
+      while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) {
+        this.steps.removeLast();
+      }
+      this.steps.offer(new FilterStepInfo(ctx, this::getInputText));
     }
   }
 
@@ -189,14 +296,33 @@ public void exitPredicate(PredicateContext ctx) {
     this.predicateMode--;
   }
 
-  private class StepInfo {
+  public class AxisStepInfo extends StepInfo {
+
+    public AxisStepInfo(AxisstepContext ctx, Function<ParserRuleContext, String> getInputText) {
+      super(ctx.reversestep() != null? getInputText.apply(ctx.reversestep()) : getInputText.apply(ctx.forwardstep()), 
+      ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval());
+    }
+  }
+
+  public class FilterStepInfo extends StepInfo {
+
+    public FilterStepInfo(FilterexprContext ctx, Function<ParserRuleContext, String> getInputText) {
+      super(getInputText.apply(ctx.primaryexpr()), 
+      ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval());
+    }
+  }
+
+  public class StepInfo {
     String stepText;
     List<String> predicates;
-
-    public StepInfo(StepexprContext ctx, Function<ParserRuleContext, String> getInputText) {
-      this.stepText = getInputText.apply(ctx.step());
-      this.predicates =
-          ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList());
+    int a;
+    int b;
+
+    protected StepInfo(String stepText, List<String> predicates, Interval interval) {
+      this.stepText = stepText;
+      this.predicates = predicates;
+      this.a = interval.a;
+      this.b = interval.b;
     }
 
     public Boolean isVariableStep() {
@@ -240,5 +366,9 @@ public Boolean isTheSameAs(final StepInfo contextStep) {
       Collections.sort(contextPredicates);
       return pathPredicates.equals(contextPredicates);
     }
+
+    public Boolean isPartOf(Interval interval) {
+      return this.a >= interval.a && this.b <= interval.b;
+    }
   }
 }
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
index 03624b47..db965139 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -15,7 +15,9 @@
 import eu.europa.ted.efx.model.Expression;
 import eu.europa.ted.efx.model.Expression.BooleanExpression;
 import eu.europa.ted.efx.model.Expression.DateExpression;
+import eu.europa.ted.efx.model.Expression.DateListExpression;
 import eu.europa.ted.efx.model.Expression.DurationExpression;
+import eu.europa.ted.efx.model.Expression.DurationListExpression;
 import eu.europa.ted.efx.model.Expression.IteratorExpression;
 import eu.europa.ted.efx.model.Expression.IteratorListExpression;
 import eu.europa.ted.efx.model.Expression.ListExpression;
@@ -24,7 +26,9 @@
 import eu.europa.ted.efx.model.Expression.NumericListExpression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
 import eu.europa.ted.efx.model.Expression.StringExpression;
+import eu.europa.ted.efx.model.Expression.StringListExpression;
 import eu.europa.ted.efx.model.Expression.TimeExpression;
+import eu.europa.ted.efx.model.Expression.TimeListExpression;
 
 @SdkComponent(versions = {"0.6", "0.7", "1"}, componentType = SdkComponentType.SCRIPT_GENERATOR)
 public class XPathScriptGenerator implements ScriptGenerator {
@@ -67,19 +71,19 @@ public <T extends Expression> T composeFieldReferenceWithAxis(final PathExpressi
   public <T extends Expression> T composeFieldValueReference(PathExpression fieldReference,
       Class<T> type) {
 
-    if (StringExpression.class.isAssignableFrom(type)) {
+    if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate(fieldReference.script + "/normalize-space(text())", type);
     }
-    if (NumericExpression.class.isAssignableFrom(type)) {
+    if (NumericExpression.class.isAssignableFrom(type) || NumericListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate(fieldReference.script + "/number()", type);
     }
-    if (DateExpression.class.isAssignableFrom(type)) {
+    if (DateExpression.class.isAssignableFrom(type) || DateListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate(fieldReference.script + "/xs:date(text())", type);
     }
-    if (TimeExpression.class.isAssignableFrom(type)) {
+    if (TimeExpression.class.isAssignableFrom(type) || TimeListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate(fieldReference.script + "/xs:time(text())", type);
     }
-    if (DurationExpression.class.isAssignableFrom(type)) {
+    if (DurationExpression.class.isAssignableFrom(type) || DurationListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate("(for $F in " + fieldReference.script + " return (if ($F/@unitCode='WEEK')" + //
           " then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D'))" + //
           " else if ($F/@unitCode='DAY')" + //
@@ -504,15 +508,13 @@ public <T extends Expression, L extends ListExpression<T>> L composeUnionFunctio
   }
 
   @Override
-  public <T extends Expression, L extends ListExpression<T>> L composeIntersectFunction(L listOne,
-      L listTwo, Class<L> listType) {
-        return Expression.instantiate("distinct-values(" + listOne.script + "[.= " + listTwo.script + "])", listType);
+  public <T extends Expression, L extends ListExpression<T>> L composeIntersectFunction(L listOne, L listTwo, Class<L> listType) {
+    return Expression.instantiate("distinct-values(for $L1 in " + listOne.script + " return if (some $L2 in " + listTwo.script + " satisfies $L1 = $L2) then $L1 else ())", listType);
   }
 
   @Override
-  public <T extends Expression, L extends ListExpression<T>> L composeExceptFunction(L listOne,
-      L listTwo, Class<L> listType) {
-        return Expression.instantiate("distinct-values(" + listOne.script + "[not(. = " + listTwo.script + ")])", listType);
+  public <T extends Expression, L extends ListExpression<T>> L composeExceptFunction(L listOne, L listTwo, Class<L> listType) {
+    return Expression.instantiate("distinct-values(for $L1 in " + listOne.script + " return if (every $L2 in " + listTwo.script + " satisfies $L1 != $L2) then $L1 else ())", listType);
   }
 
 
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java
index ecf9a344..d188f433 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java
@@ -32,18 +32,18 @@ void testNotPresentAndNotPresent() {
 
   @Test
   void testCountWithNodeContextOverride() {
-    assertEquals("count(../../PathNode/CodeField) = 1",
+    assertEquals("count(../../PathNode/CodeField/normalize-space(text())) = 1",
         test("BT-00-Text", "count(ND-Root::BT-00-Code) == 1"));
   }
 
   @Test
   void testCountWithAbsoluteFieldReference() {
-    assertEquals("count(/*/PathNode/CodeField) = 1", test("BT-00-Text", "count(/BT-00-Code) == 1"));
+    assertEquals("count(/*/PathNode/CodeField/normalize-space(text())) = 1", test("BT-00-Text", "count(/BT-00-Code) == 1"));
   }
 
   @Test
   void testCountWithAbsoluteFieldReferenceAndPredicate() {
-    assertEquals("count(/*/PathNode/CodeField[../IndicatorField = true()]) = 1",
+    assertEquals("count(/*/PathNode/CodeField[../IndicatorField = true()]/normalize-space(text())) = 1",
         test("BT-00-Text", "count(/BT-00-Code[BT-00-Indicator == TRUE]) == 1"));
   }
 }
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index 6cd2ae9d..ace681f3 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -299,7 +299,7 @@ void testStringQuantifiedExpression_UsingLiterals() {
 
   @Test
   void testStringQuantifiedExpression_UsingFieldReference() {
-    assertEquals("every $x in PathNode/TextField satisfies $x <= 'a'",
+    assertEquals("every $x in PathNode/TextField/normalize-space(text()) satisfies $x <= 'a'",
         test("ND-Root", "every text:$x in BT-00-Text satisfies $x <= 'a'"));
   }
 
@@ -323,7 +323,7 @@ void testNumericQuantifiedExpression_UsingLiterals() {
 
   @Test
   void testNumericQuantifiedExpression_UsingFieldReference() {
-    assertEquals("every $x in PathNode/NumberField satisfies $x <= 1",
+    assertEquals("every $x in PathNode/NumberField/number() satisfies $x <= 1",
         test("ND-Root", "every number:$x in BT-00-Number satisfies $x <= 1"));
   }
 
@@ -337,13 +337,13 @@ void testDateQuantifiedExpression_UsingLiterals() {
 
   @Test
   void testDateQuantifiedExpression_UsingFieldReference() {
-    assertEquals("every $x in PathNode/StartDateField satisfies $x <= xs:date('2012-01-01Z')",
+    assertEquals("every $x in PathNode/StartDateField/xs:date(text()) satisfies $x <= xs:date('2012-01-01Z')",
         test("ND-Root", "every date:$x in BT-00-StartDate satisfies $x <= 2012-01-01Z"));
   }
 
   @Test
   void testDateQuantifiedExpression_UsingMultipleIterators() {
-    assertEquals("every $x in PathNode/StartDateField, $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')",
+    assertEquals("every $x in PathNode/StartDateField/xs:date(text()), $y in ($x,xs:date('2022-02-02Z')), $i in (true(),true()) satisfies $x <= xs:date('2012-01-01Z')",
         test("ND-Root", "every date:$x in BT-00-StartDate, date:$y in ($x, 2022-02-02Z), indicator:$i in (ALWAYS, TRUE) satisfies $x <= 2012-01-01Z"));
   }
 
@@ -357,7 +357,7 @@ void testTimeQuantifiedExpression_UsingLiterals() {
 
   @Test
   void testTimeQuantifiedExpression_UsingFieldReference() {
-    assertEquals("every $x in PathNode/StartTimeField satisfies $x <= xs:time('00:00:00Z')",
+    assertEquals("every $x in PathNode/StartTimeField/xs:time(text()) satisfies $x <= xs:time('00:00:00Z')",
         test("ND-Root", "every time:$x in BT-00-StartTime satisfies $x <= 00:00:00Z"));
   }
 
@@ -371,7 +371,7 @@ void testDurationQuantifiedExpression_UsingLiterals() {
   @Test
   void testDurationQuantifiedExpression_UsingFieldReference() {
     assertEquals(
-        "every $x in PathNode/MeasureField satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))",
+        "every $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies boolean(for $T in (current-date()) return ($T + $x <= $T + xs:dayTimeDuration('P1D')))",
         test("ND-Root", "every measure:$x in BT-00-Measure satisfies $x <= P1D"));
   }
 
@@ -470,7 +470,7 @@ void testStringsSequenceFromIteration_UsingMultipleIterators() {
   
   @Test
   void testStringsSequenceFromIteration_UsingObjectVariable() {
-    assertEquals("for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField return 'text'",
+    assertEquals("for $n in PathNode/TextField[../NumberField], $d in $n/../StartDateField/xs:date(text()) return 'text'",
         test("ND-Root", "for context:$n in BT-00-Text[BT-00-Number is present], date:$d in $n::BT-00-StartDate return 'text'"));
   }
 
@@ -482,7 +482,7 @@ void testStringsSequenceFromIteration_UsingNodeContextVariable() {
 
   @Test
   void testStringsFromStringIteration_UsingFieldReference() {
-    assertEquals("'a' = (for $x in PathNode/TextField return concat($x, 'text'))",
+    assertEquals("'a' = (for $x in PathNode/TextField/normalize-space(text()) return concat($x, 'text'))",
         test("ND-Root", "'a' in (for text:$x in BT-00-Text return concat($x, 'text'))"));
   }
 
@@ -508,7 +508,7 @@ void testStringsFromNumericIteration_UsingLiterals() {
 
   @Test
   void testStringsFromNumericIteration_UsingFieldReference() {
-    assertEquals("'a' = (for $x in PathNode/NumberField return 'y')",
+    assertEquals("'a' = (for $x in PathNode/NumberField/number() return 'y')",
         test("ND-Root", "'a' in (for number:$x in BT-00-Number return 'y')"));
   }
 
@@ -521,7 +521,7 @@ void testStringsFromDateIteration_UsingLiterals() {
 
   @Test
   void testStringsFromDateIteration_UsingFieldReference() {
-    assertEquals("'a' = (for $x in PathNode/StartDateField return 'y')",
+    assertEquals("'a' = (for $x in PathNode/StartDateField/xs:date(text()) return 'y')",
         test("ND-Root", "'a' in (for date:$x in BT-00-StartDate return 'y')"));
   }
 
@@ -534,7 +534,7 @@ void testStringsFromTimeIteration_UsingLiterals() {
 
   @Test
   void testStringsFromTimeIteration_UsingFieldReference() {
-    assertEquals("'a' = (for $x in PathNode/StartTimeField return 'y')",
+    assertEquals("'a' = (for $x in PathNode/StartTimeField/xs:time(text()) return 'y')",
         test("ND-Root", "'a' in (for time:$x in BT-00-StartTime return 'y')"));
   }
 
@@ -548,7 +548,7 @@ void testStringsFromDurationIteration_UsingLiterals() {
 
   @Test
   void testStringsFromDurationIteration_UsingFieldReference() {
-    assertEquals("'a' = (for $x in PathNode/MeasureField return 'y')",
+    assertEquals("'a' = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 'y')",
         test("ND-Root", "'a' in (for measure:$x in BT-00-Measure return 'y')"));
   }
 
@@ -562,7 +562,7 @@ void testNumbersFromStringIteration_UsingLiterals() {
 
   @Test
   void testNumbersFromStringIteration_UsingFieldReference() {
-    assertEquals("123 = (for $x in PathNode/TextField return number($x))",
+    assertEquals("123 = (for $x in PathNode/TextField/normalize-space(text()) return number($x))",
         test("ND-Root", "123 in (for text:$x in BT-00-Text return number($x))"));
   }
 
@@ -588,7 +588,7 @@ void testNumbersFromNumericIteration_UsingLiterals() {
 
   @Test
   void testNumbersFromNumericIteration_UsingFieldReference() {
-    assertEquals("123 = (for $x in PathNode/NumberField return 0)",
+    assertEquals("123 = (for $x in PathNode/NumberField/number() return 0)",
         test("ND-Root", "123 in (for number:$x in BT-00-Number return 0)"));
   }
 
@@ -601,7 +601,7 @@ void testNumbersFromDateIteration_UsingLiterals() {
 
   @Test
   void testNumbersFromDateIteration_UsingFieldReference() {
-    assertEquals("123 = (for $x in PathNode/StartDateField return 0)",
+    assertEquals("123 = (for $x in PathNode/StartDateField/xs:date(text()) return 0)",
         test("ND-Root", "123 in (for date:$x in BT-00-StartDate return 0)"));
   }
 
@@ -614,7 +614,7 @@ void testNumbersFromTimeIteration_UsingLiterals() {
 
   @Test
   void testNumbersFromTimeIteration_UsingFieldReference() {
-    assertEquals("123 = (for $x in PathNode/StartTimeField return 0)",
+    assertEquals("123 = (for $x in PathNode/StartTimeField/xs:time(text()) return 0)",
         test("ND-Root", "123 in (for time:$x in BT-00-StartTime return 0)"));
   }
 
@@ -628,7 +628,7 @@ void testNumbersFromDurationIteration_UsingLiterals() {
 
   @Test
   void testNumbersFromDurationIteration_UsingFieldReference() {
-    assertEquals("123 = (for $x in PathNode/MeasureField return 0)",
+    assertEquals("123 = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return 0)",
         test("ND-Root", "123 in (for measure:$x in BT-00-Measure return 0)"));
   }
 
@@ -642,7 +642,7 @@ void testDatesFromStringIteration_UsingLiterals() {
 
   @Test
   void testDatesFromStringIteration_UsingFieldReference() {
-    assertEquals("xs:date('2022-01-01Z') = (for $x in PathNode/TextField return xs:date($x))",
+    assertEquals("xs:date('2022-01-01Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:date($x))",
         test("ND-Root", "2022-01-01Z in (for text:$x in BT-00-Text return date($x))"));
   }
 
@@ -671,7 +671,7 @@ void testDatesFromNumericIteration_UsingLiterals() {
   @Test
   void testDatesFromNumericIteration_UsingFieldReference() {
     assertEquals(
-        "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField return xs:date('2022-01-01Z'))",
+        "xs:date('2022-01-01Z') = (for $x in PathNode/NumberField/number() return xs:date('2022-01-01Z'))",
         test("ND-Root", "2022-01-01Z in (for number:$x in BT-00-Number return 2022-01-01Z)"));
   }
 
@@ -686,7 +686,7 @@ void testDatesFromDateIteration_UsingLiterals() {
   @Test
   void testDatesFromDateIteration_UsingFieldReference() {
     assertEquals(
-        "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField return xs:date('2022-01-01Z'))",
+        "xs:date('2022-01-01Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:date('2022-01-01Z'))",
         test("ND-Root", "2022-01-01Z in (for date:$x in BT-00-StartDate return 2022-01-01Z)"));
   }
 
@@ -701,7 +701,7 @@ void testDatesFromTimeIteration_UsingLiterals() {
   @Test
   void testDatesFromTimeIteration_UsingFieldReference() {
     assertEquals(
-        "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField return xs:date('2022-01-01Z'))",
+        "xs:date('2022-01-01Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:date('2022-01-01Z'))",
         test("ND-Root", "2022-01-01Z in (for time:$x in BT-00-StartTime return 2022-01-01Z)"));
   }
 
@@ -716,7 +716,7 @@ void testDatesFromDurationIteration_UsingLiterals() {
   @Test
   void testDatesFromDurationIteration_UsingFieldReference() {
     assertEquals(
-        "xs:date('2022-01-01Z') = (for $x in PathNode/MeasureField return xs:date('2022-01-01Z'))",
+        "xs:date('2022-01-01Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:date('2022-01-01Z'))",
         test("ND-Root", "2022-01-01Z in (for measure:$x in BT-00-Measure return 2022-01-01Z)"));
   }
 
@@ -730,7 +730,7 @@ void testTimesFromStringIteration_UsingLiterals() {
 
   @Test
   void testTimesFromStringIteration_UsingFieldReference() {
-    assertEquals("xs:time('12:00:00Z') = (for $x in PathNode/TextField return xs:time($x))",
+    assertEquals("xs:time('12:00:00Z') = (for $x in PathNode/TextField/normalize-space(text()) return xs:time($x))",
         test("ND-Root", "12:00:00Z in (for text:$x in BT-00-Text return time($x))"));
   }
 
@@ -758,7 +758,7 @@ void testTimesFromNumericIteration_UsingLiterals() {
   @Test
   void testTimesFromNumericIteration_UsingFieldReference() {
     assertEquals(
-        "xs:time('12:00:00Z') = (for $x in PathNode/NumberField return xs:time('12:00:00Z'))",
+        "xs:time('12:00:00Z') = (for $x in PathNode/NumberField/number() return xs:time('12:00:00Z'))",
         test("ND-Root", "12:00:00Z in (for number:$x in BT-00-Number return 12:00:00Z)"));
   }
 
@@ -773,7 +773,7 @@ void testTimesFromDateIteration_UsingLiterals() {
   @Test
   void testTimesFromDateIteration_UsingFieldReference() {
     assertEquals(
-        "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField return xs:time('12:00:00Z'))",
+        "xs:time('12:00:00Z') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:time('12:00:00Z'))",
         test("ND-Root", "12:00:00Z in (for date:$x in BT-00-StartDate return 12:00:00Z)"));
   }
 
@@ -788,7 +788,7 @@ void testTimesFromTimeIteration_UsingLiterals() {
   @Test
   void testTimesFromTimeIteration_UsingFieldReference() {
     assertEquals(
-        "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField return xs:time('12:00:00Z'))",
+        "xs:time('12:00:00Z') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:time('12:00:00Z'))",
         test("ND-Root", "12:00:00Z in (for time:$x in BT-00-StartTime return 12:00:00Z)"));
   }
 
@@ -803,7 +803,7 @@ void testTimesFromDurationIteration_UsingLiterals() {
   @Test
   void testTimesFromDurationIteration_UsingFieldReference() {
     assertEquals(
-        "xs:time('12:00:00Z') = (for $x in PathNode/MeasureField return xs:time('12:00:00Z'))",
+        "xs:time('12:00:00Z') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:time('12:00:00Z'))",
         test("ND-Root", "12:00:00Z in (for measure:$x in BT-00-Measure return 12:00:00Z)"));
   }
 
@@ -819,7 +819,7 @@ void testDurationsFromStringIteration_UsingLiterals() {
   @Test
   void testDurationsFromStringIteration_UsingFieldReference() {
     assertEquals(
-        "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField return xs:dayTimeDuration($x))",
+        "xs:dayTimeDuration('P1D') = (for $x in PathNode/TextField/normalize-space(text()) return xs:dayTimeDuration($x))",
         test("ND-Root", "P1D in (for text:$x in BT-00-Text return day-time-duration($x))"));
   }
 
@@ -848,7 +848,7 @@ void testDurationsFromNumericIteration_UsingLiterals() {
   @Test
   void testDurationsFromNumericIteration_UsingFieldReference() {
     assertEquals(
-        "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField return xs:dayTimeDuration('P1D'))",
+        "xs:dayTimeDuration('P1D') = (for $x in PathNode/NumberField/number() return xs:dayTimeDuration('P1D'))",
         test("ND-Root", "P1D in (for number:$x in BT-00-Number return P1D)"));
   }
 
@@ -862,7 +862,7 @@ void testDurationsFromDateIteration_UsingLiterals() {
   @Test
   void testDurationsFromDateIteration_UsingFieldReference() {
     assertEquals(
-        "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField return xs:dayTimeDuration('P1D'))",
+        "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartDateField/xs:date(text()) return xs:dayTimeDuration('P1D'))",
         test("ND-Root", "P1D in (for date:$x in BT-00-StartDate return P1D)"));
   }
 
@@ -876,7 +876,7 @@ void testDurationsFromTimeIteration_UsingLiterals() {
   @Test
   void testDurationsFromTimeIteration_UsingFieldReference() {
     assertEquals(
-        "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField return xs:dayTimeDuration('P1D'))",
+        "xs:dayTimeDuration('P1D') = (for $x in PathNode/StartTimeField/xs:time(text()) return xs:dayTimeDuration('P1D'))",
         test("ND-Root", "P1D in (for time:$x in BT-00-StartTime return P1D)"));
   }
 
@@ -890,7 +890,7 @@ void testDurationsFromDurationIteration_UsingLiterals() {
   @Test
   void testDurationsFromDurationIteration_UsingFieldReference() {
     assertEquals(
-        "xs:dayTimeDuration('P1D') = (for $x in PathNode/MeasureField return xs:dayTimeDuration('P1D'))",
+        "xs:dayTimeDuration('P1D') = (for $x in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return xs:dayTimeDuration('P1D'))",
         test("ND-Root", "P1D in (for measure:$x in BT-00-Measure return P1D)"));
   }
 
@@ -1103,12 +1103,12 @@ void testEndsWithFunction() {
 
   @Test
   void testCountFunction_UsingFieldReference() {
-    assertEquals("count(PathNode/TextField)", test("ND-Root", "count(BT-00-Text)"));
+    assertEquals("count(PathNode/TextField/normalize-space(text()))", test("ND-Root", "count(BT-00-Text)"));
   }
 
   @Test
   void testCountFunction_UsingSequenceFromIteration() {
-    assertEquals("count(for $x in PathNode/TextField return concat($x, '-xyz'))",
+    assertEquals("count(for $x in PathNode/TextField/normalize-space(text()) return concat($x, '-xyz'))",
         test("ND-Root", "count(for text:$x in BT-00-Text return concat($x, '-xyz'))"));
   }
 
@@ -1120,12 +1120,12 @@ void testNumberFunction() {
 
   @Test
   void testSumFunction_UsingFieldReference() {
-    assertEquals("sum(PathNode/NumberField)", test("ND-Root", "sum(BT-00-Number)"));
+    assertEquals("sum(PathNode/NumberField/number())", test("ND-Root", "sum(BT-00-Number)"));
   }
 
   @Test
   void testSumFunction_UsingNumericSequenceFromIteration() {
-    assertEquals("sum(for $v in PathNode/NumberField return $v + 1)",
+    assertEquals("sum(for $v in PathNode/NumberField/number() return $v + 1)",
         test("ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"));
   }
 
@@ -1212,7 +1212,7 @@ void testDistinctValuesFunction_WithBooleanSequences() {
   
   @Test
   void testDistinctValuesFunction_WithFieldReferences() {
-    assertEquals("distinct-values(PathNode/TextField)",
+    assertEquals("distinct-values(PathNode/TextField/normalize-space(text()))",
         test("ND-Root", "distinct-values(BT-00-Text)"));
   }
 
@@ -1250,7 +1250,7 @@ void testUnionFunction_WithBooleanSequences() {
   
   @Test
   void testUnionFunction_WithFieldReferences() {
-    assertEquals("distinct-values((PathNode/TextField, PathNode/TextField))",
+    assertEquals("distinct-values((PathNode/TextField/normalize-space(text()), PathNode/TextField/normalize-space(text())))",
         test("ND-Root", "value-union(BT-00-Text, BT-00-Text)"));
   }
 
@@ -1264,37 +1264,37 @@ void testUnionFunction_WithTypeMismatch() {
 
   @Test
   void testIntersectFunction_WithStringSequences() {
-    assertEquals("distinct-values(('one','two')[.= ('two','three','four')])",
+    assertEquals("distinct-values(for $L1 in ('one','two') return if (some $L2 in ('two','three','four') satisfies $L1 = $L2) then $L1 else ())",
         test("ND-Root", "value-intersect(('one', 'two'), ('two', 'three', 'four'))"));
   }
 
   @Test
   void testIntersectFunction_WithNumberSequences() {
-    assertEquals("distinct-values((1,2,3)[.= (2,3,4)])",
+    assertEquals("distinct-values(for $L1 in (1,2,3) return if (some $L2 in (2,3,4) satisfies $L1 = $L2) then $L1 else ())",
         test("ND-Root", "value-intersect((1, 2, 3), (2, 3, 4))"));
   }
 
   @Test
   void testIntersectFunction_WithDateSequences() {
-    assertEquals("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[.= (xs:date('2018-01-01Z'),xs:date('2022-01-02Z'))])",
+    assertEquals("distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (some $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 = $L2) then $L1 else ())",
         test("ND-Root", "value-intersect((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"));
   }
 
   @Test
   void testIntersectFunction_WithTimeSequences() {
-    assertEquals("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[.= (xs:time('12:00:00Z'),xs:time('14:00:00Z'))])",
+    assertEquals("distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (some $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 = $L2) then $L1 else ())",
         test("ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"));
   }
 
   @Test
   void testIntersectFunction_WithBooleanSequences() {
-    assertEquals("distinct-values((true(),false())[.= (false(),false())])",
+    assertEquals("distinct-values(for $L1 in (true(),false()) return if (some $L2 in (false(),false()) satisfies $L1 = $L2) then $L1 else ())",
         test("ND-Root", "value-intersect((TRUE, FALSE), (FALSE, NEVER))"));
   }
   
   @Test
   void testIntersectFunction_WithFieldReferences() {
-    assertEquals("distinct-values(PathNode/TextField[.= PathNode/TextField])",
+    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (some $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 = $L2) then $L1 else ())",
         test("ND-Root", "value-intersect(BT-00-Text, BT-00-Text)"));
   }
 
@@ -1308,40 +1308,75 @@ void testIntersectFunction_WithTypeMismatch() {
 
   @Test
   void testExceptFunction_WithStringSequences() {
-    assertEquals("distinct-values(('one','two')[not(. = ('two','three','four'))])",
+    assertEquals("distinct-values(for $L1 in ('one','two') return if (every $L2 in ('two','three','four') satisfies $L1 != $L2) then $L1 else ())",
         test("ND-Root", "value-except(('one', 'two'), ('two', 'three', 'four'))"));
   }
 
   @Test
   void testExceptFunction_WithNumberSequences() {
-    assertEquals("distinct-values((1,2,3)[not(. = (2,3,4))])",
+    assertEquals("distinct-values(for $L1 in (1,2,3) return if (every $L2 in (2,3,4) satisfies $L1 != $L2) then $L1 else ())",
         test("ND-Root", "value-except((1, 2, 3), (2, 3, 4))"));
   }
 
   @Test
   void testExceptFunction_WithDateSequences() {
-    assertEquals("distinct-values((xs:date('2018-01-01Z'),xs:date('2020-01-01Z'))[not(. = (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')))])",
+    assertEquals("distinct-values(for $L1 in (xs:date('2018-01-01Z'),xs:date('2020-01-01Z')) return if (every $L2 in (xs:date('2018-01-01Z'),xs:date('2022-01-02Z')) satisfies $L1 != $L2) then $L1 else ())",
         test("ND-Root", "value-except((2018-01-01Z, 2020-01-01Z), (2018-01-01Z, 2022-01-02Z))"));
   }
 
   @Test
   void testExceptFunction_WithTimeSequences() {
-    assertEquals("distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z'))[not(. = (xs:time('12:00:00Z'),xs:time('14:00:00Z')))])",
+    assertEquals("distinct-values(for $L1 in (xs:time('12:00:00Z'),xs:time('13:00:00Z')) return if (every $L2 in (xs:time('12:00:00Z'),xs:time('14:00:00Z')) satisfies $L1 != $L2) then $L1 else ())",
         test("ND-Root", "value-except((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"));
   }
 
+  @Test
+  void testExceptFunction_WithDurationSequences() {
+    assertEquals("distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (every $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except((P1W, P2D), (P2D, P5D))"));
+  }
+
   @Test
   void testExceptFunction_WithBooleanSequences() {
-    assertEquals("distinct-values((true(),false())[not(. = (false(),false()))])",
+    assertEquals("distinct-values(for $L1 in (true(),false()) return if (every $L2 in (false(),false()) satisfies $L1 != $L2) then $L1 else ())",
         test("ND-Root", "value-except((TRUE, FALSE), (FALSE, NEVER))"));
   }
   
   @Test
-  void testExceptFunction_WithFieldReferences() {
-    assertEquals("distinct-values(PathNode/TextField[not(. = PathNode/TextField)])",
+  void testExceptFunction_WithTextFieldReferences() {
+    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+  }
+
+  @Test
+  void testExceptFunction_WithNumberFieldReferences() {
+    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+  }
+  @Test
+  void testExceptFunction_WithBooleanReferences() {
+    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
         test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
   }
 
+    @Test
+  void testExceptFunction_WithDateReferences() {
+    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+  }
+
+  @Test
+  void testExceptFunction_WithTimeFieldReferences() {
+    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+  }
+
+  @Test
+  void testExceptFunction_WithDurationReferences() {
+    assertEquals("distinct-values(for $L1 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return if (every $L2 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-Measure, BT-00-Measure)"));
+  }
+
   @Test
   void testExceptFunction_WithTypeMismatch() {
     assertThrows(ParseCancellationException.class,
@@ -1388,7 +1423,7 @@ void testSequenceEqualFunction_WithDurationSequences() {
 
   @Test
   void testSequenceEqualFunction_WithFieldReferences() {
-    assertEquals("deep-equal(sort(PathNode/TextField), sort(PathNode/TextField))",
+    assertEquals("deep-equal(sort(PathNode/TextField/normalize-space(text())), sort(PathNode/TextField/normalize-space(text())))",
         test("ND-Root", "sequence-equal(BT-00-Text, BT-00-Text)"));
   }
 
diff --git a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
index 9e4d2653..8fc77372 100644
--- a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
@@ -161,9 +161,9 @@ void testTemplateLineIdentUnexpected() {
   @Test
   void testTemplateLine_VariableScope() {
     assertEquals(
-        lines("declare block01 = { outline('1') eval(for $x in . return $x)", //
+        lines("declare block01 = { outline('1') eval(for $x in ./normalize-space(text()) return $x)", //
         "for-each(.).call(block0101) }", //
-        "declare block0101 = { eval(for $x in . return $x) }", //
+        "declare block0101 = { eval(for $x in ./normalize-space(text()) return $x) }", //
         "for-each(/*/PathNode/TextField).call(block01)"),//
         translate(lines("{BT-00-Text} ${for text:$x in BT-00-Text return $x}", "    {BT-00-Text} ${for text:$x in BT-00-Text return $x}")));
 

From 643ce9a7c8b2214b19f0ba5da7b738d6f3058f80 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Fri, 14 Apr 2023 08:55:22 +0200
Subject: [PATCH 04/39] Applied selected changes in preparation of merging with
 SDK2 branch

---
 pom.xml                                       |   6 +-
 .../ted/eforms/sdk/SdkSymbolResolver.java     |   3 +-
 .../ted/efx/interfaces/MarkupGenerator.java   |  19 +-
 .../ted/efx/interfaces/ScriptGenerator.java   |   6 +-
 .../eu/europa/ted/efx/model/CallStack.java    | 292 +++++++++++++++---
 ...ckObjectBase.java => CallStackObject.java} |   4 +-
 .../eu/europa/ted/efx/model/ContentBlock.java |  51 ++-
 .../ted/efx/model/ContentBlockStack.java      |   8 +-
 .../java/eu/europa/ted/efx/model/Context.java |  26 +-
 .../eu/europa/ted/efx/model/Expression.java   |  11 +-
 .../eu/europa/ted/efx/model/Identifier.java   |  11 +
 .../java/eu/europa/ted/efx/model/Markup.java  |   2 +-
 .../eu/europa/ted/efx/model/Variable.java     |  16 +
 .../eu/europa/ted/efx/model/VariableList.java |  29 ++
 .../efx/sdk0/v6/EfxTemplateTranslator06.java  |  12 +-
 .../sdk0/v7/EfxExpressionTranslator07.java    |   7 +-
 .../efx/sdk0/v7/EfxTemplateTranslator07.java  |  12 +-
 .../efx/sdk1/EfxExpressionTranslatorV1.java   | 121 ++++++--
 .../ted/efx/sdk1/EfxTemplateTranslatorV1.java |  23 +-
 .../ted/efx/xpath/XPathScriptGenerator.java   |  14 +-
 .../ted/efx/EfxTemplateTranslatorTest.java    | 130 ++++----
 .../ted/efx/mock/MarkupGeneratorMock.java     |  25 +-
 22 files changed, 631 insertions(+), 197 deletions(-)
 rename src/main/java/eu/europa/ted/efx/model/{CallStackObjectBase.java => CallStackObject.java} (66%)
 create mode 100644 src/main/java/eu/europa/ted/efx/model/Identifier.java
 create mode 100644 src/main/java/eu/europa/ted/efx/model/Variable.java
 create mode 100644 src/main/java/eu/europa/ted/efx/model/VariableList.java

diff --git a/pom.xml b/pom.xml
index e47b4b59..59d341b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
 
   <groupId>eu.europa.ted.eforms</groupId>
   <artifactId>efx-toolkit-java</artifactId>
-  <version>1.2.1-SNAPSHOT</version>
+  <version>1.3.0-SNAPSHOT</version>
   <packaging>jar</packaging>
 
   <name>EFX Toolkit for Java</name>
@@ -56,7 +56,7 @@
     <sdk.antlr4.dir>${project.build.directory}/eforms-sdk/antlr4</sdk.antlr4.dir>
 
     <!-- Versions - eForms -->
-    <version.eforms-core>1.0.0</version.eforms-core>
+    <version.eforms-core>1.0.2-SNAPSHOT</version.eforms-core>
 
     <!-- Versions - Third-party libraries -->
     <version.antlr4>4.9.3</version.antlr4>
@@ -231,7 +231,7 @@
             <artifactItem>
               <groupId>eu.europa.ted.eforms</groupId>
               <artifactId>eforms-sdk</artifactId>
-              <version>1.2.1</version>
+              <version>1.6.0</version>
               <type>jar</type>
               <includes>eforms-sdk/efx-grammar/**/*.g4</includes>
               <outputDirectory>${sdk.antlr4.dir}/eu/europa/ted/efx/sdk1</outputDirectory>
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java
index 12581f4e..ef93b247 100644
--- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java
+++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java
@@ -3,8 +3,9 @@
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
+
 import org.antlr.v4.runtime.misc.ParseCancellationException;
-import eu.europa.ted.eforms.sdk.SdkConstants;
+
 import eu.europa.ted.eforms.sdk.component.SdkComponent;
 import eu.europa.ted.eforms.sdk.component.SdkComponentType;
 import eu.europa.ted.eforms.sdk.entity.SdkCodelist;
diff --git a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
index b944d725..bb6f3773 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
@@ -14,6 +14,10 @@
 package eu.europa.ted.efx.interfaces;
 
 import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.tuple.Pair;
+
 import eu.europa.ted.efx.model.Expression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
 import eu.europa.ted.efx.model.Expression.StringExpression;
@@ -68,9 +72,22 @@ public interface MarkupGenerator {
    */
   Markup composeFragmentDefinition(final String name, String number, Markup content);
 
+  /**
+   * Given a fragment name (identifier) and some pre-rendered content, this method
+   * returns the code
+   * that encapsulates it in the target template.
+   */
+  Markup composeFragmentDefinition(final String name, String number, Markup content, Set<String> parameters);
+
+  /**
+   * @deprecated Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead.
+   */
+  @Deprecated(since = "2.0.0", forRemoval = true)
+  Markup renderFragmentInvocation(final String name, final PathExpression context);
+
   /**
    * Given a fragment name (identifier), and an evaluation context, this method returns the code
    * that invokes (uses) the fragment.
    */
-  Markup renderFragmentInvocation(final String name, final PathExpression context);
+  Markup renderFragmentInvocation(final String name, final PathExpression context, final Set<Pair<String, String>> variables);
 }
diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java
index 3fc28ed3..931c8c29 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java
@@ -21,11 +21,11 @@
 import eu.europa.ted.efx.model.Expression.IteratorExpression;
 import eu.europa.ted.efx.model.Expression.IteratorListExpression;
 import eu.europa.ted.efx.model.Expression.ListExpression;
-import eu.europa.ted.efx.model.Expression.ListExpressionBase;
 import eu.europa.ted.efx.model.Expression.NumericExpression;
 import eu.europa.ted.efx.model.Expression.NumericListExpression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
 import eu.europa.ted.efx.model.Expression.StringExpression;
+import eu.europa.ted.efx.model.Expression.StringListExpression;
 import eu.europa.ted.efx.model.Expression.TimeExpression;
 
 /**
@@ -250,7 +250,7 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand,
   @Deprecated(since = "0.7.0", forRemoval = true)
   public NumericExpression composeCountOperation(final PathExpression set);
 
-  public NumericExpression composeCountOperation(final ListExpressionBase list);
+  public NumericExpression composeCountOperation(final ListExpression<? extends Expression> list);
 
   public NumericExpression composeToNumberConversion(StringExpression text);
 
@@ -292,7 +292,7 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri
 
   public BooleanExpression composeUniqueValueCondition(PathExpression needle, PathExpression haystack);
 
-  public BooleanExpression composeSequenceEqualFunction(ListExpressionBase one, ListExpressionBase two);
+  public BooleanExpression composeSequenceEqualFunction(ListExpression<? extends Expression> one, ListExpression<? extends Expression> two);
 
   /*
    * Date Functions
diff --git a/src/main/java/eu/europa/ted/efx/model/CallStack.java b/src/main/java/eu/europa/ted/efx/model/CallStack.java
index 723f3e33..2a7f03a3 100644
--- a/src/main/java/eu/europa/ted/efx/model/CallStack.java
+++ b/src/main/java/eu/europa/ted/efx/model/CallStack.java
@@ -2,67 +2,277 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Stack;
+
 import org.antlr.v4.runtime.misc.ParseCancellationException;
 
-public class CallStack extends Stack<CallStackObjectBase> {
+/**
+ * The call stack is a stack of stack frames. Each stack frame represents a
+ * scope. The top of the stack is the current scope. The bottom of the stack is
+ * the global scope.
+ */
+public class CallStack  {
+
+  private static final String TYPE_MISMATCH = "Type mismatch. Expected %s instead of %s.";
+  private static final String UNDECLARED_IDENTIFIER = "Identifier not declared: ";
+  private static final String IDENTIFIER_ALREADY_DECLARED = "Identifier already declared: ";
+  private static final String STACK_UNDERFLOW = "Stack underflow. Return values were available in the dropped frame, but no stack frame is left to consume them.";
+
+  /**
+   * Stack frames are means of controlling the scope of variables and parameters.
+   * Certain sub-expressions are scoped, meaning that variables and parameters are
+   * only available within the scope of the sub-expression.
+   */
+  class StackFrame extends Stack<CallStackObject> {
+
+    /**
+     * Keeps a list of all identifiers declared in the current scope as well as
+     * their type.
+     */
+    Map<String, Class<? extends Expression>> typeRegister = new HashMap<String, Class<? extends Expression>>();
+
+    /**
+     * Keeps a list of all parameter values declared in the current scope.
+     */
+    Map<String, Expression> valueRegister = new HashMap<String, Expression>();
+
+    /**
+     * Registers a parameter identifier and pushes a parameter declaration on the
+     * current stack frame. Also stores the parameter value.
+     */
+    void pushParameterDeclaration(String parameterName, Expression parameterDeclarationExpression,
+        Expression parameterValue) {
+      this.declareIdentifier(parameterName, parameterDeclarationExpression.getClass());
+      this.storeValue(parameterName, parameterValue);
+      this.push(parameterDeclarationExpression);
+    }
+
+    /**
+     * Registers a variable identifier and pushes a variable declaration on the
+     * current stack frame.
+     */
+    void pushVariableDeclaration(String variableName, Expression variableDeclarationExpression) {
+      this.declareIdentifier(variableName, variableDeclarationExpression.getClass());
+      this.push(variableDeclarationExpression);
+    }
+
+    /**
+     * Registers an identifier in the current scope.
+     * This registration is later used to check if an identifier is declared in the
+     * current scope.
+     */
+    void declareIdentifier(String identifier, Class<? extends Expression> type) {
+      this.typeRegister.put(identifier, type);
+    }
+
+    /**
+     * Used to store parameter values.
+     */
+    void storeValue(String identifier, Expression value) {
+      this.valueRegister.put(identifier, value);
+    }
+
+    /**
+     * Returns the object at the top of the stack and removes it from the stack.
+     * The object must be of the expected type.
+     */
+    synchronized <T extends CallStackObject> T pop(Class<T> expectedType) {
+      Class<? extends CallStackObject> actualType = peek().getClass();
+      if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) {
+        throw new ParseCancellationException(String.format(TYPE_MISMATCH, expectedType.getSimpleName(), this.peek().getClass().getSimpleName()));
+      }
+      return expectedType.cast(this.pop());
+    }
+
+    /**
+     * Clears the stack frame and all its registers.
+     */
+    @Override
+    public void clear() {
+        super.clear();
+        this.typeRegister.clear();
+        this.valueRegister.clear();  
+    }
+  }
 
-  Map<String, Class<? extends Expression>> variableTypes =
-      new HashMap<String, Class<? extends Expression>>();
+  /**
+   * The stack of stack frames.
+   */
+  Stack<StackFrame> frames;
 
-  Map<String, Expression> parameterValues =
-      new HashMap<String, Expression>();
+  /**
+   * Default and only constructor.
+   * Adds a global scope to the stack.
+   */
+  public CallStack() {
+    this.frames = new Stack<>();
+    this.frames.push(new StackFrame()); // The global scope
+  }
+
+  /**
+   * Creates a new stack frame and pushes it on top of the call stack.
+   * 
+   * This method is called at the begin boundary of scoped sub-expression to
+   * allow for the declaration of local variables.
+   */
+  public void pushStackFrame() {
+    this.frames.push(new StackFrame());
+  }
 
-  public CallStack() {}
+  /**
+   * Drops the current stack frame and passes the return values to the previous
+   * stack frame.
+   * 
+   * This method is called at the end boundary of scoped sub-expressions.
+   * Variables local to the sub-expression must go out of scope and the return
+   * values are passed to the parent expression.
+   */
+  public void popStackFrame() {
+    StackFrame droppedFrame = this.frames.pop();
+
+    // If the dropped frame is not empty, then it contains return values that should
+    // be passed to the next frame on the stack.
+    if (droppedFrame.size() > 0) {
+      if (this.frames.empty()) {
+        throw new ParseCancellationException(STACK_UNDERFLOW);
+      }
+      this.frames.peek().addAll(droppedFrame);
+    }
+  }
 
+  /**
+   * Pushes a parameter declaration on the current stack frame.
+   * Checks if another identifier with the same name is already declared in the
+   * current scope.
+   */
   public void pushParameterDeclaration(String parameterName, Expression parameterDeclaration, Expression parameterValue) {
-    if (this.variableTypes.containsKey(parameterName)) {
-      throw new ParseCancellationException("A parameter with the name " + parameterDeclaration.script + " already exists");
-    } else if (parameterDeclaration.getClass() == Expression.class) {
-      throw new ParseCancellationException();
-    } else {
-      this.variableTypes.put(parameterName, parameterDeclaration.getClass());
-      this.parameterValues.put(parameterName, parameterValue);
-      this.push(parameterDeclaration);
+    if (this.inScope(parameterName)) {
+      throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + parameterDeclaration.script);
     }
+    this.frames.peek().pushParameterDeclaration(parameterName, parameterDeclaration, parameterValue);
   }
 
+  /**
+   * Pushes a variable declaration on the current stack frame.
+   * Checks if another identifier with the same name is already declared in the
+   * current scope.
+   */
   public void pushVariableDeclaration(String variableName, Expression variableDeclaration) {
-    if (parameterValues.containsKey(variableName)) {
-      throw new ParseCancellationException("A parameter with the name " + variableDeclaration.script + " has already been declared.");
-    } else if (this.variableTypes.containsKey(variableName)) {
-      throw new ParseCancellationException("A variable with the name " + variableDeclaration.script + " has already been declared.");
-    } else if (variableDeclaration.getClass() == Expression.class) {
-      throw new ParseCancellationException();
-    } else {
-      this.variableTypes.put(variableName, variableDeclaration.getClass());
-      this.push(variableDeclaration);
+    if (this.inScope(variableName)) {
+      throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + variableDeclaration.script);
     }
+    this.frames.peek().pushVariableDeclaration(variableName, variableDeclaration);
   }
 
-  public void pushVariableReference(String variableName, Expression variableReference) {
-    if (this.parameterValues.containsKey(variableName)) {
-      this.push(parameterValues.get(variableName));
-    } else if (this.variableTypes.containsKey(variableName)) {
-      this.push(Expression.instantiate(variableReference.script, variableTypes.get(variableName)));
-    } else {
-      throw new ParseCancellationException("A variable or parameter with the name " + variableName + " has not been declared.");
+  /**
+   * Declares a template variable. Template variables are tracked to ensure proper
+   * scoping. However, their declaration is not pushed on the stack as they are
+   * declared at the template level (in Markup) and not at the expression level
+   * (not in the target language script).
+   */
+  public void declareTemplateVariable(String variableName, Class<? extends Expression> variableType) {
+    if (this.inScope(variableName)) {
+      throw new ParseCancellationException(IDENTIFIER_ALREADY_DECLARED + variableName);
     }
+    this.frames.peek().declareIdentifier(variableName, variableType);
   }
 
-  public synchronized <T extends CallStackObjectBase> T pop(Class<T> expectedType) {
-    Class<? extends CallStackObjectBase> actualType = peek().getClass();
-    if (!expectedType.isAssignableFrom(actualType) && !actualType.equals(Expression.class)) {
-      throw new ParseCancellationException("Type mismatch. Expected " + expectedType.getSimpleName()
-          + " instead of " + this.peek().getClass().getSimpleName());
-    }
-    return expectedType.cast(this.pop());
+  /**
+   * Checks if an identifier is declared in the current scope.
+   */
+  boolean inScope(String identifier) {
+    return this.frames.stream().anyMatch(f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier) );      
+  }
+
+  /**
+   * Returns the stack frame containing the given identifier.
+   */
+  StackFrame findFrameContaining(String identifier) {
+    return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier) || f.valueRegister.containsKey(identifier)).findFirst().orElse(null);
+  }
+
+  /**
+   * Gets the value of a parameter.
+   */
+  Optional<Expression> getParameter(String identifier) {
+    return this.frames.stream().filter(f -> f.valueRegister.containsKey(identifier)).findFirst().map(x -> x.valueRegister.get(identifier));
+  }
+
+  /**
+   * Gets the type of a variable.
+   */
+  Optional<Class<? extends Expression>> getVariable(String identifier) {
+    return this.frames.stream().filter(f -> f.typeRegister.containsKey(identifier)).findFirst().map(x -> x.typeRegister.get(identifier));
+  }
+
+  /**
+   * Pushes a variable reference on the current stack frame.
+   * Makes sure there is no name collision with other identifiers already in
+   * scope.
+   */
+  public void pushVariableReference(String variableName, Expression variableReference) {
+    getParameter(variableName).ifPresentOrElse(parameterValue -> this.push(parameterValue),
+        () -> getVariable(variableName).ifPresentOrElse(
+            variableType -> this.pushVariableReference(variableReference, variableType),
+            () -> {
+              throw new ParseCancellationException(UNDECLARED_IDENTIFIER + variableName);
+            }));
+  }
+
+  /**
+   * Pushes a variable reference on the current stack frame.
+   * This method is private because it is only used for to improve the readability
+   * of its public counterpart.
+   */
+  private void pushVariableReference(Expression variableReference, Class<? extends Expression> variableType) {
+    this.frames.peek().push(Expression.instantiate(variableReference.script, variableType));
+  }
+
+  /**
+   * Pushes an object on the current stack frame.
+   * No checks, no questions asked.
+   */
+  public void push(CallStackObject item) {
+    this.frames.peek().push(item);
+  }
+
+  /**
+   * Returns the object at the top of the current stack frame and removes it from
+   * the stack.
+   * 
+   * @param expectedType The that the returned object is expected to have.
+   */
+  public synchronized <T extends CallStackObject> T pop(Class<T> expectedType) {
+    return this.frames.peek().pop(expectedType);
+  }
+
+  /**
+   * Returns the object at the top of the current stack frame without removing it
+   * from the stack.
+   */
+  public synchronized CallStackObject peek() {
+    return this.frames.peek().peek();
+  }
+
+  /**
+   * Returns the number of elements in the current stack frame.
+   */
+  public int size() {
+    return this.frames.peek().size();
+  }
+
+  /**
+   * Returns true if the current stack frame is empty.
+   */
+  public boolean empty() {
+    return this.frames.peek().empty();
   }
 
-  @Override
+  /**
+   * Clears the current stack frame.
+   */
   public void clear() {
-      super.clear();
-      this.variableTypes.clear();
-      this.parameterValues.clear();  
+    this.frames.peek().clear();
   }
 }
diff --git a/src/main/java/eu/europa/ted/efx/model/CallStackObjectBase.java b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java
similarity index 66%
rename from src/main/java/eu/europa/ted/efx/model/CallStackObjectBase.java
rename to src/main/java/eu/europa/ted/efx/model/CallStackObject.java
index 63126da4..07a3d3e7 100644
--- a/src/main/java/eu/europa/ted/efx/model/CallStackObjectBase.java
+++ b/src/main/java/eu/europa/ted/efx/model/CallStackObject.java
@@ -1,12 +1,12 @@
 package eu.europa.ted.efx.model;
 
 /**
- * Base class for objects pushed in the Sdk6EfxExpressionTranslator.
+ * Base class for objects pushed in the EfxExpressionTranslator.
  * call-stack.
  * 
  * As the EfxExpressionTranslator translates EFX to a target language, the objects in the call-stack
  * are typically code snippets in the target language.
  */
-public abstract class CallStackObjectBase {
+public abstract class CallStackObject {
 
 }
diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java
index f57b1950..c2fb4153 100644
--- a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java
+++ b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java
@@ -1,10 +1,16 @@
 package eu.europa.ted.efx.model;
 
 import java.util.Comparator;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
+import java.util.Set;
+import java.util.stream.Collectors;
+
 import org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.apache.commons.lang3.tuple.Pair;
+
 import eu.europa.ted.efx.interfaces.MarkupGenerator;
 
 public class ContentBlock {
@@ -15,6 +21,7 @@ public class ContentBlock {
   private final Context context;
   private final Queue<ContentBlock> children = new LinkedList<>();
   private final int number;
+  private final VariableList variables;
 
   private ContentBlock() {
     this.parent = null;
@@ -23,38 +30,40 @@ private ContentBlock() {
     this.content = new Markup("");
     this.context = null;
     this.number = 0;
+    this.variables = new VariableList();
   }
 
   public ContentBlock(final ContentBlock parent, final String id, final int number,
-      final Markup content, Context contextPath) {
+      final Markup content, Context contextPath, VariableList variables) {
     this.parent = parent;
     this.id = id;
     this.indentationLevel = parent.indentationLevel + 1;
     this.content = content;
     this.context = contextPath;
     this.number = number;
+    this.variables = variables;
   }
 
   public static ContentBlock newRootBlock() {
     return new ContentBlock();
   }
 
-  public ContentBlock addChild(final int number, final Markup content, final Context context) {
+  public ContentBlock addChild(final int number, final Markup content, final Context context, final VariableList variables) {
     // number < 0 means "autogenerate", number == 0 means "no number", number > 0 means "use this number"
     final int outlineNumber = number >= 0 ? number
         : children.stream().map(b -> b.number).max(Comparator.naturalOrder()).orElse(0) + 1;
 
     String newBlockId = String.format("%s%02d", this.id, this.children.size() + 1);
-    ContentBlock newBlock = new ContentBlock(this, newBlockId, outlineNumber, content, context);
+    ContentBlock newBlock = new ContentBlock(this, newBlockId, outlineNumber, content, context, variables);
     this.children.add(newBlock);
     return newBlock;
   }
 
-  public ContentBlock addSibling(final int number, final Markup content, final Context context) {
+  public ContentBlock addSibling(final int number, final Markup content, final Context context, final VariableList variables) {
     if (this.parent == null) {
       throw new ParseCancellationException("Cannot add sibling to root block");
     }
-    return this.parent.addChild(number, content, context);
+    return this.parent.addChild(number, content, context, variables);
   }
 
   public ContentBlock findParentByLevel(final int parentIndentationLevel) {
@@ -104,6 +113,29 @@ public Context getParentContext() {
     return this.parent.getContext();
   }
 
+  public Set<Variable<? extends Expression>> getOwnVariables() {
+    Set<Variable<? extends Expression>> variables = new LinkedHashSet<>();
+    if (this.context != null && this.context.variable() != null) {
+      variables.add(this.context.variable());
+    }
+    variables.addAll(this.variables.asList());
+    return variables;
+  }
+
+  public Set<Variable<? extends Expression>> getAllVariables() {
+    if (this.parent == null) {
+      return new LinkedHashSet<>(this.getOwnVariables());
+    }
+    final Set<Variable<? extends Expression>> merged = new LinkedHashSet<>();
+    merged.addAll(parent.getAllVariables());
+    merged.addAll(this.getOwnVariables());
+    return merged;
+  }
+
+  public Set<String> getTemplateParameters() {
+    return this.getAllVariables().stream().map(v -> v.name).collect(Collectors.toSet());
+  }
+
   public Markup renderContent(MarkupGenerator markupGenerator) {
     StringBuilder sb = new StringBuilder();
     sb.append(this.content.script);
@@ -115,13 +147,18 @@ public Markup renderContent(MarkupGenerator markupGenerator) {
 
   public void renderTemplate(MarkupGenerator markupGenerator, List<Markup> templates) {
     templates.add(markupGenerator.composeFragmentDefinition(this.id, this.getOutlineNumber(),
-        this.renderContent(markupGenerator)));
+        this.renderContent(markupGenerator), this.getTemplateParameters()));
     for (ContentBlock child : this.children) {
       child.renderTemplate(markupGenerator, templates);
     }
   }
 
   public Markup renderCallTemplate(MarkupGenerator markupGenerator) {
-    return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath());
+    Set<Pair<String, String>> variables = new LinkedHashSet<>();
+    if (this.parent != null) {
+      variables.addAll(parent.getAllVariables().stream().map(v -> Pair.of(v.name, v.referenceExpression.script)).collect(Collectors.toList()));
+    }
+    variables.addAll(this.getOwnVariables().stream().map(v -> Pair.of(v.name, v.initializationExpression.script)).collect(Collectors.toList()));
+    return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath(), variables);
   }
 }
diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java
index f3c4c97a..d1d4e142 100644
--- a/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java
+++ b/src/main/java/eu/europa/ted/efx/model/ContentBlockStack.java
@@ -8,16 +8,16 @@ public class ContentBlockStack extends Stack<ContentBlock> {
    * Adds a new child block to the top of the stack. When the child is later removed, its parent
    * will return to the top of the stack again.
    */
-  public void pushChild(final int number, final Markup content, final Context context) {
-    this.push(this.peek().addChild(number, content, context));
+  public void pushChild(final int number, final Markup content, final Context context, final VariableList variables) {
+    this.push(this.peek().addChild(number, content, context, variables));
   }
 
   /**
    * Removes the block at the top of the stack and replaces it by a new sibling block. When the last
    * sibling is later removed, their parent block will return to the top of the stack again.
    */
-  public void pushSibling(final int number, final Markup content, Context context) {
-    this.push(this.pop().addSibling(number, content, context));
+  public void pushSibling(final int number, final Markup content, Context context, final VariableList variables) {
+    this.push(this.pop().addSibling(number, content, context, variables));
   }
 
   /**
diff --git a/src/main/java/eu/europa/ted/efx/model/Context.java b/src/main/java/eu/europa/ted/efx/model/Context.java
index 4c1d7f19..7383fa32 100644
--- a/src/main/java/eu/europa/ted/efx/model/Context.java
+++ b/src/main/java/eu/europa/ted/efx/model/Context.java
@@ -19,11 +19,20 @@ public abstract class Context {
    */
   public static class FieldContext extends Context {
 
+    public FieldContext(final String fieldId, final PathExpression absolutePath,
+        final PathExpression relativePath, final Variable<PathExpression> variable) {
+      super(fieldId, absolutePath, relativePath, variable);
+    }
+
     public FieldContext(final String fieldId, final PathExpression absolutePath,
         final PathExpression relativePath) {
       super(fieldId, absolutePath, relativePath);
     }
 
+    public FieldContext(final String fieldId, final PathExpression absolutePath, final Variable<PathExpression> variable) {
+      super(fieldId, absolutePath, variable);
+    }
+
     public FieldContext(final String fieldId, final PathExpression absolutePath) {
       super(fieldId, absolutePath);
     }
@@ -47,14 +56,25 @@ public NodeContext(final String nodeId, final PathExpression absolutePath) {
   private final String symbol;
   private final PathExpression absolutePath;
   private final PathExpression relativePath;
+  private final Variable<PathExpression> variable;
 
   protected Context(final String symbol, final PathExpression absolutePath,
-      final PathExpression relativePath) {
+      final PathExpression relativePath, final Variable<PathExpression> variable) {
+    this.variable = variable;
     this.symbol = symbol;
     this.absolutePath = absolutePath;
     this.relativePath = relativePath == null ? absolutePath : relativePath;
   }
 
+  protected Context(final String symbol, final PathExpression absolutePath,
+      final PathExpression relativePath) {
+        this(symbol, absolutePath, relativePath, null);
+  }
+
+  protected Context(final String symbol, final PathExpression absolutePath, final Variable<PathExpression> variable) {
+    this(symbol, absolutePath, absolutePath, variable);
+  }
+   
   protected Context(final String symbol, final PathExpression absolutePath) {
     this(symbol, absolutePath, absolutePath);
   }
@@ -67,6 +87,10 @@ public Boolean isNodeContext() {
     return this.getClass().equals(NodeContext.class);
   }
 
+  public Variable<PathExpression> variable() {
+    return variable;
+  }
+
   /**
    * Returns the [field or node] identifier that was used to create this context.
    */
diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java
index 701a6df8..edfbce30 100644
--- a/src/main/java/eu/europa/ted/efx/model/Expression.java
+++ b/src/main/java/eu/europa/ted/efx/model/Expression.java
@@ -16,7 +16,7 @@
  * language. It also enables to EFX translator to perform type checking of EFX expressions.
  *
  */
-public class Expression extends CallStackObjectBase {
+public class Expression extends CallStackObject {
 
   /**
    * eForms types are mapped to Expression types.
@@ -177,17 +177,10 @@ public DurationExpression(final String script) {
     }
   }
 
-  public static class ListExpressionBase extends Expression {
-
-    public ListExpressionBase(final String script) {
-      super(script);
-    }
-  }
-
   /**
    * Used to represent a list of strings in the target language.
    */
-  public static class ListExpression<T extends Expression> extends ListExpressionBase {
+  public static class ListExpression<T extends Expression> extends Expression {
 
     public ListExpression(final String script) {
       super(script);
diff --git a/src/main/java/eu/europa/ted/efx/model/Identifier.java b/src/main/java/eu/europa/ted/efx/model/Identifier.java
new file mode 100644
index 00000000..a73e03b9
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/Identifier.java
@@ -0,0 +1,11 @@
+package eu.europa.ted.efx.model;
+
+public class Identifier<T extends Expression> {
+    public String name;
+    public T referenceExpression;
+
+    public Identifier(String name, T referenceExpression) {
+        this.name = name;
+        this.referenceExpression = referenceExpression;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/Markup.java b/src/main/java/eu/europa/ted/efx/model/Markup.java
index 2fd9fa6b..ed55e2a2 100644
--- a/src/main/java/eu/europa/ted/efx/model/Markup.java
+++ b/src/main/java/eu/europa/ted/efx/model/Markup.java
@@ -3,7 +3,7 @@
 /**
  * Represents markup in the target template language.
  */
-public class Markup extends CallStackObjectBase {
+public class Markup extends CallStackObject {
 
   /**
    * Stores the markup script in the target language.
diff --git a/src/main/java/eu/europa/ted/efx/model/Variable.java b/src/main/java/eu/europa/ted/efx/model/Variable.java
new file mode 100644
index 00000000..e5d1a093
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/Variable.java
@@ -0,0 +1,16 @@
+package eu.europa.ted.efx.model;
+
+public class Variable<T extends Expression> extends Identifier<T> {
+    public T initializationExpression;
+
+    public Variable(String variableName, T initializationExpression, T referenceExpression) {
+        super(variableName, referenceExpression);
+        this.name = variableName;
+        this.initializationExpression = initializationExpression;
+    }
+
+    public Variable(Identifier<T> identifier, T initializationExpression) {
+        super(identifier.name, identifier.referenceExpression);
+        this.initializationExpression = initializationExpression;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/VariableList.java b/src/main/java/eu/europa/ted/efx/model/VariableList.java
new file mode 100644
index 00000000..ffc281c3
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/VariableList.java
@@ -0,0 +1,29 @@
+package eu.europa.ted.efx.model;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class VariableList extends CallStackObject {
+
+    LinkedList<Variable<? extends Expression>> variables;
+
+    public VariableList() {
+        this.variables = new LinkedList<>();
+    }
+
+    public <T extends Expression> void push(Variable<T> variable) {
+        this.variables.push(variable);
+    }
+
+    public synchronized Variable<? extends Expression> pop() {
+        return this.variables.pop();
+    }
+
+    public boolean isEmpty() {
+        return this.variables.isEmpty();
+    }
+
+    public List<Variable<? extends Expression>> asList() {
+        return this.variables;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java
index 8afd4f48..9f43d2c0 100644
--- a/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java
+++ b/src/main/java/eu/europa/ted/efx/sdk0/v6/EfxTemplateTranslator06.java
@@ -29,6 +29,7 @@
 import eu.europa.ted.efx.model.Expression.PathExpression;
 import eu.europa.ted.efx.model.Expression.StringExpression;
 import eu.europa.ted.efx.model.Markup;
+import eu.europa.ted.efx.model.VariableList;
 import eu.europa.ted.efx.sdk0.v6.EfxParser.AssetIdContext;
 import eu.europa.ted.efx.sdk0.v6.EfxParser.AssetTypeContext;
 import eu.europa.ted.efx.sdk0.v6.EfxParser.ContextDeclarationBlockContext;
@@ -439,13 +440,14 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) {
 
   @Override
   public void exitTemplateLine(TemplateLineContext ctx) {
+    final VariableList variables = new VariableList(); // template variables not supported by EFX prior to 2.0.0
     final Context lineContext = this.efxContext.pop();
     final int indentLevel = this.getIndentLevel(ctx);
     final int indentChange = indentLevel - this.blockStack.currentIndentationLevel();
     final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup("");
     final Integer outlineNumber =
         ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1;
-    assert this.stack.isEmpty() : "Stack should be empty at this point.";
+    assert this.stack.empty() : "Stack should be empty at this point.";
 
     if (indentChange > 1) {
       throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED);
@@ -453,7 +455,7 @@ public void exitTemplateLine(TemplateLineContext ctx) {
       if (this.blockStack.isEmpty()) {
         throw new ParseCancellationException(START_INDENT_AT_ZERO);
       }
-      this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()));
+      this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables);
     } else if (indentChange < 0) {
       // lower indent level
       for (int i = indentChange; i < 0; i++) {
@@ -462,14 +464,14 @@ public void exitTemplateLine(TemplateLineContext ctx) {
         this.blockStack.pop();
       }
       assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION;
-      this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()));
+      this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables);
     } else if (indentChange == 0) {
 
       if (blockStack.isEmpty()) {
         assert indentLevel == 0 : UNEXPECTED_INDENTATION;
-        this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext())));
+        this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables));
       } else {
-        this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()));
+        this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables);
       }
     }
   }
diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java
index d7cf16d8..c14d38de 100644
--- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java
+++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxExpressionTranslator07.java
@@ -19,7 +19,7 @@
 import eu.europa.ted.efx.interfaces.ScriptGenerator;
 import eu.europa.ted.efx.interfaces.SymbolResolver;
 import eu.europa.ted.efx.model.CallStack;
-import eu.europa.ted.efx.model.CallStackObjectBase;
+import eu.europa.ted.efx.model.CallStackObject;
 import eu.europa.ted.efx.model.Context.FieldContext;
 import eu.europa.ted.efx.model.Context.NodeContext;
 import eu.europa.ted.efx.model.ContextStack;
@@ -546,7 +546,7 @@ private <T extends Expression, L extends ListExpression<T>> void exitList(int li
 
   @Override
   public void exitUntypedConditonalExpression(UntypedConditonalExpressionContext ctx) {
-    Class<? extends CallStackObjectBase> typeWhenFalse = this.stack.peek().getClass();
+    Class<? extends CallStackObject> typeWhenFalse = this.stack.peek().getClass();
     if (typeWhenFalse == BooleanExpression.class) {
       this.exitConditionalBooleanExpression();
     } else if (typeWhenFalse == NumericExpression.class) {
@@ -1233,7 +1233,8 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) {
 
   @Override
   public void exitCountFunction(CountFunctionContext ctx) {
-    this.stack.push(this.script.composeCountOperation(this.stack.pop(ListExpression.class)));
+    ListExpression<?> expression = this.stack.pop(ListExpression.class);
+    this.stack.push(this.script.composeCountOperation(expression));
   }
 
   @Override
diff --git a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java
index f1d8ebb6..b76fd1ac 100644
--- a/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java
+++ b/src/main/java/eu/europa/ted/efx/sdk0/v7/EfxTemplateTranslator07.java
@@ -29,6 +29,7 @@
 import eu.europa.ted.efx.model.Expression.PathExpression;
 import eu.europa.ted.efx.model.Expression.StringExpression;
 import eu.europa.ted.efx.model.Markup;
+import eu.europa.ted.efx.model.VariableList;
 import eu.europa.ted.efx.sdk0.v7.EfxParser.AssetIdContext;
 import eu.europa.ted.efx.sdk0.v7.EfxParser.AssetTypeContext;
 import eu.europa.ted.efx.sdk0.v7.EfxParser.ContextDeclarationBlockContext;
@@ -439,13 +440,14 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) {
 
   @Override
   public void exitTemplateLine(TemplateLineContext ctx) {
+    final VariableList variables = new VariableList(); // template variables not supported prior to EFX 2
     final Context lineContext = this.efxContext.pop();
     final int indentLevel = this.getIndentLevel(ctx);
     final int indentChange = indentLevel - this.blockStack.currentIndentationLevel();
     final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup("");
     final Integer outlineNumber =
         ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1;
-    assert this.stack.isEmpty() : "Stack should be empty at this point.";
+    assert this.stack.empty() : "Stack should be empty at this point.";
 
     if (indentChange > 1) {
       throw new ParseCancellationException(INDENTATION_LEVEL_SKIPPED);
@@ -453,7 +455,7 @@ public void exitTemplateLine(TemplateLineContext ctx) {
       if (this.blockStack.isEmpty()) {
         throw new ParseCancellationException(START_INDENT_AT_ZERO);
       }
-      this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()));
+      this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables);
     } else if (indentChange < 0) {
       // lower indent level
       for (int i = indentChange; i < 0; i++) {
@@ -462,14 +464,14 @@ public void exitTemplateLine(TemplateLineContext ctx) {
         this.blockStack.pop();
       }
       assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION;
-      this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()));
+      this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables);
     } else if (indentChange == 0) {
 
       if (blockStack.isEmpty()) {
         assert indentLevel == 0 : UNEXPECTED_INDENTATION;
-        this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext())));
+        this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables));
       } else {
-        this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()));
+        this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables);
       }
     }
   }
diff --git a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
index c0ddd161..5c5abbad 100644
--- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
+++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
@@ -6,6 +6,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.stream.Collectors;
+
 import org.antlr.v4.runtime.BaseErrorListener;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
@@ -15,13 +16,15 @@
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 import org.antlr.v4.runtime.tree.TerminalNode;
+import org.apache.commons.lang3.StringUtils;
+
 import eu.europa.ted.eforms.sdk.component.SdkComponent;
 import eu.europa.ted.eforms.sdk.component.SdkComponentType;
 import eu.europa.ted.efx.interfaces.EfxExpressionTranslator;
 import eu.europa.ted.efx.interfaces.ScriptGenerator;
 import eu.europa.ted.efx.interfaces.SymbolResolver;
 import eu.europa.ted.efx.model.CallStack;
-import eu.europa.ted.efx.model.CallStackObjectBase;
+import eu.europa.ted.efx.model.CallStackObject;
 import eu.europa.ted.efx.model.Context;
 import eu.europa.ted.efx.model.Context.FieldContext;
 import eu.europa.ted.efx.model.Context.NodeContext;
@@ -560,7 +563,7 @@ private <T extends Expression, L extends ListExpression<T>> void exitList(int li
 
   @Override
   public void exitUntypedConditionalExpression(UntypedConditionalExpressionContext ctx) {
-    Class<? extends CallStackObjectBase> typeWhenFalse = this.stack.peek().getClass();
+    Class<? extends CallStackObject> typeWhenFalse = this.stack.peek().getClass();
     if (typeWhenFalse == BooleanExpression.class) {
       this.exitConditionalBooleanExpression();
     } else if (typeWhenFalse == NumericExpression.class) {
@@ -1109,8 +1112,8 @@ public void exitCodelistReference(CodelistReferenceContext ctx) {
 
   @Override
   public void exitVariableReference(VariableReferenceContext ctx) {
-    this.stack.pushVariableReference(ctx.Variable().getText(),
-        this.script.composeVariableReference(ctx.Variable().getText(), Expression.class));
+    this.stack.pushVariableReference(this.getVariableName(ctx),
+        this.script.composeVariableReference(this.getVariableName(ctx), Expression.class));
   }
 
   /*** Parameter Declarations ***/
@@ -1118,32 +1121,32 @@ public void exitVariableReference(VariableReferenceContext ctx) {
 
   @Override
   public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) {
-    this.exitParameterDeclaration(ctx.Variable().getText(), StringExpression.class);
+    this.exitParameterDeclaration(this.getVariableName(ctx), StringExpression.class);
   }
 
   @Override
   public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) {
-    this.exitParameterDeclaration(ctx.Variable().getText(), NumericExpression.class);
+    this.exitParameterDeclaration(this.getVariableName(ctx), NumericExpression.class);
   }
 
   @Override
   public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) {
-    this.exitParameterDeclaration(ctx.Variable().getText(), BooleanExpression.class);
+    this.exitParameterDeclaration(this.getVariableName(ctx), BooleanExpression.class);
   }
 
   @Override
   public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) {
-    this.exitParameterDeclaration(ctx.Variable().getText(), DateExpression.class);
+    this.exitParameterDeclaration(this.getVariableName(ctx), DateExpression.class);
   }
 
   @Override
   public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) {
-    this.exitParameterDeclaration(ctx.Variable().getText(), TimeExpression.class);
+    this.exitParameterDeclaration(this.getVariableName(ctx), TimeExpression.class);
   }
 
   @Override
   public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) {
-    this.exitParameterDeclaration(ctx.Variable().getText(), DurationExpression.class);
+    this.exitParameterDeclaration(this.getVariableName(ctx), DurationExpression.class);
   }
 
   private <T extends Expression> void exitParameterDeclaration(String parameterName, Class<T> parameterType) {
@@ -1160,44 +1163,51 @@ private <T extends Expression> void exitParameterDeclaration(String parameterNam
 
   @Override
   public void exitStringVariableDeclaration(StringVariableDeclarationContext ctx) {
-      this.stack.pushVariableDeclaration(ctx.Variable().getText(),
-          this.script.composeVariableDeclaration(ctx.Variable().getText(), StringExpression.class));
+    String variableName = this.getVariableName(ctx);
+      this.stack.pushVariableDeclaration(variableName,
+          this.script.composeVariableDeclaration(variableName, StringExpression.class));
   }
 
   @Override
   public void exitBooleanVariableDeclaration(BooleanVariableDeclarationContext ctx) {
-    this.stack.pushVariableDeclaration(ctx.Variable().getText(),
-        this.script.composeVariableDeclaration(ctx.Variable().getText(), BooleanExpression.class));
+    String variableName = this.getVariableName(ctx);
+    this.stack.pushVariableDeclaration(variableName,
+        this.script.composeVariableDeclaration(variableName, BooleanExpression.class));
   }
 
   @Override
   public void exitNumericVariableDeclaration(NumericVariableDeclarationContext ctx) {
-    this.stack.pushVariableDeclaration(ctx.Variable().getText(),
-        this.script.composeVariableDeclaration(ctx.Variable().getText(), NumericExpression.class));
+    String variableName = this.getVariableName(ctx);
+    this.stack.pushVariableDeclaration(variableName,
+        this.script.composeVariableDeclaration(variableName, NumericExpression.class));
   }
 
   @Override
   public void exitDateVariableDeclaration(DateVariableDeclarationContext ctx) {
-    this.stack.pushVariableDeclaration(ctx.Variable().getText(),
-        this.script.composeVariableDeclaration(ctx.Variable().getText(), DateExpression.class));
+    String variableName = this.getVariableName(ctx);
+    this.stack.pushVariableDeclaration(variableName,
+        this.script.composeVariableDeclaration(variableName, DateExpression.class));
   }
 
   @Override
   public void exitTimeVariableDeclaration(TimeVariableDeclarationContext ctx) {
-    this.stack.pushVariableDeclaration(ctx.Variable().getText(),
-        this.script.composeVariableDeclaration(ctx.Variable().getText(), TimeExpression.class));
+    String variableName = this.getVariableName(ctx);
+    this.stack.pushVariableDeclaration(variableName,
+        this.script.composeVariableDeclaration(variableName, TimeExpression.class));
   }
 
   @Override
   public void exitDurationVariableDeclaration(DurationVariableDeclarationContext ctx) {
-    this.stack.pushVariableDeclaration(ctx.Variable().getText(),
-        this.script.composeVariableDeclaration(ctx.Variable().getText(), DurationExpression.class));
+    String variableName = this.getVariableName(ctx);
+    this.stack.pushVariableDeclaration(variableName,
+        this.script.composeVariableDeclaration(variableName, DurationExpression.class));
   }
 
   @Override
   public void exitContextVariableDeclaration(ContextVariableDeclarationContext ctx) {
-    this.stack.pushVariableDeclaration(ctx.Variable().getText(),
-        this.script.composeVariableDeclaration(ctx.Variable().getText(), ContextExpression.class));
+    String variableName = this.getVariableName(ctx);
+    this.stack.pushVariableDeclaration(variableName,
+        this.script.composeVariableDeclaration(variableName, ContextExpression.class));
   }
 
   /*** Boolean functions ***/
@@ -1239,7 +1249,8 @@ public void exitSequenceEqualFunction(SequenceEqualFunctionContext ctx) {
 
   @Override
   public void exitCountFunction(CountFunctionContext ctx) {
-    this.stack.push(this.script.composeCountOperation(this.stack.pop(ListExpression.class)));
+    final ListExpression<?> expression = this.stack.pop(ListExpression.class);
+    this.stack.push(this.script.composeCountOperation(expression));
   }
 
   @Override
@@ -1448,7 +1459,6 @@ public void exitExceptFunction(ExceptFunctionContext ctx) {
     }
   }
 
-
   private <T extends Expression, L extends ListExpression<T>> void exitExceptFunction(
       Class<L> listType) {
     final L two = this.stack.pop(listType);
@@ -1456,4 +1466,63 @@ private <T extends Expression, L extends ListExpression<T>> void exitExceptFunct
     this.stack.push(this.script.composeExceptFunction(one, two, listType));
   }
 
+  private String getVariableName(String efxVariableIdentifier) {
+    return StringUtils.stripStart(efxVariableIdentifier, "$");
+  }
+
+  private String getVariableName(VariableReferenceContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  protected String getVariableName(ContextVariableDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+    private String getVariableName(StringVariableDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(NumericVariableDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(BooleanVariableDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(DateVariableDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(TimeVariableDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(DurationVariableDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(StringParameterDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(NumericParameterDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(BooleanParameterDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(DateParameterDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(TimeParameterDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
+
+  private String getVariableName(DurationParameterDeclarationContext ctx) {
+    return this.getVariableName(ctx.Variable().getText());
+  }
 }
diff --git a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java
index 5b4696e4..0be1f12e 100644
--- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java
+++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java
@@ -30,6 +30,7 @@
 import eu.europa.ted.efx.model.Expression.StringExpression;
 import eu.europa.ted.efx.model.Expression.StringListExpression;
 import eu.europa.ted.efx.model.Markup;
+import eu.europa.ted.efx.model.VariableList;
 import eu.europa.ted.efx.sdk1.EfxParser.*;
 import eu.europa.ted.efx.xpath.XPathAttributeLocator;
 
@@ -284,19 +285,18 @@ private void shorthandIndirectLabelReference(final String fieldId) {
         : this.script.composeFieldValueReference(
             symbols.getRelativePathOfField(fieldId, currentContext.absolutePath()),
             PathExpression.class);
-    final String loopVariableName = "$item";
-
+    final StringExpression loopVariable = this.script.composeVariableReference("item", StringExpression.class);
     switch (fieldType) {
       case "indicator":
         this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression(
             this.script.composeIteratorList(
-                List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))),
+                List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))),
             this.script.composeStringConcatenation(
                 List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR),
                     this.script.getStringLiteralFromUnquotedString("|"),
                     this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN),
                     this.script.getStringLiteralFromUnquotedString("-"),
-                    this.script.composeVariableReference(loopVariableName, StringExpression.class),
+                    loopVariable,
                     this.script.getStringLiteralFromUnquotedString("|"),
                     this.script.getStringLiteralFromUnquotedString(fieldId))),
             StringListExpression.class)));
@@ -305,7 +305,7 @@ private void shorthandIndirectLabelReference(final String fieldId) {
       case "internal-code":
         this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression(
             this.script.composeIteratorList(
-                List.of(this.script.composeIteratorExpression(loopVariableName, valueReference))),
+                List.of(this.script.composeIteratorExpression(loopVariable.script, valueReference))),
             this.script.composeStringConcatenation(List.of(
                 this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE),
                 this.script.getStringLiteralFromUnquotedString("|"),
@@ -314,7 +314,7 @@ private void shorthandIndirectLabelReference(final String fieldId) {
                 this.script.getStringLiteralFromUnquotedString(
                     this.symbols.getRootCodelistOfField(fieldId)),
                 this.script.getStringLiteralFromUnquotedString("."),
-                this.script.composeVariableReference(loopVariableName, StringExpression.class))),
+                loopVariable)),
             StringListExpression.class)));
         break;
       default:
@@ -456,13 +456,14 @@ public void exitContextDeclarationBlock(ContextDeclarationBlockContext ctx) {
 
   @Override
   public void exitTemplateLine(TemplateLineContext ctx) {
+    final VariableList variables = new VariableList(); // template variables not supported prior to EFX 2
     final Context lineContext = this.efxContext.pop();
     final int indentLevel = this.getIndentLevel(ctx);
     final int indentChange = indentLevel - this.blockStack.currentIndentationLevel();
     final Markup content = ctx.template() != null ? this.stack.pop(Markup.class) : new Markup("");
     final Integer outlineNumber =
         ctx.OutlineNumber() != null ? Integer.parseInt(ctx.OutlineNumber().getText().trim()) : -1;
-    assert this.stack.isEmpty() : "Stack should be empty at this point.";
+    assert this.stack.empty() : "Stack should be empty at this point.";
     this.stack.clear(); // Variable scope boundary. Clear declared variables 
 
     if (indentChange > 1) {
@@ -471,7 +472,7 @@ public void exitTemplateLine(TemplateLineContext ctx) {
       if (this.blockStack.isEmpty()) {
         throw new ParseCancellationException(START_INDENT_AT_ZERO);
       }
-      this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()));
+      this.blockStack.pushChild(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.currentContext()), variables);
     } else if (indentChange < 0) {
       // lower indent level
       for (int i = indentChange; i < 0; i++) {
@@ -480,14 +481,14 @@ public void exitTemplateLine(TemplateLineContext ctx) {
         this.blockStack.pop();
       }
       assert this.blockStack.currentIndentationLevel() == indentLevel : UNEXPECTED_INDENTATION;
-      this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()));
+      this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables);
     } else if (indentChange == 0) {
 
       if (blockStack.isEmpty()) {
         assert indentLevel == 0 : UNEXPECTED_INDENTATION;
-        this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext())));
+        this.blockStack.push(this.rootBlock.addChild(outlineNumber, content, this.relativizeContext(lineContext, this.rootBlock.getContext()), variables));
       } else {
-        this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()));
+        this.blockStack.pushSibling(outlineNumber, content, this.relativizeContext(lineContext, this.blockStack.parentContext()), variables);
       }
     }
   }
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
index 03624b47..51e3c289 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -1,6 +1,7 @@
 package eu.europa.ted.efx.xpath;
 
 import static java.util.Map.entry;
+
 import java.lang.reflect.Constructor;
 import java.util.List;
 import java.util.Map;
@@ -8,7 +9,9 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+
 import org.antlr.v4.runtime.misc.ParseCancellationException;
+
 import eu.europa.ted.eforms.sdk.component.SdkComponent;
 import eu.europa.ted.eforms.sdk.component.SdkComponentType;
 import eu.europa.ted.efx.interfaces.ScriptGenerator;
@@ -19,7 +22,6 @@
 import eu.europa.ted.efx.model.Expression.IteratorExpression;
 import eu.europa.ted.efx.model.Expression.IteratorListExpression;
 import eu.europa.ted.efx.model.Expression.ListExpression;
-import eu.europa.ted.efx.model.Expression.ListExpressionBase;
 import eu.europa.ted.efx.model.Expression.NumericExpression;
 import eu.europa.ted.efx.model.Expression.NumericListExpression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
@@ -104,12 +106,12 @@ public <T extends Expression> T composeFieldAttributeReference(PathExpression fi
 
   @Override
   public <T extends Expression> T composeVariableReference(String variableName, Class<T> type) {
-    return Expression.instantiate(variableName, type);
+    return Expression.instantiate("$" + variableName, type);
   }
 
   @Override
   public <T extends Expression> T composeVariableDeclaration(String variableName, Class<T> type) {
-    return Expression.instantiate(variableName, type);
+    return Expression.instantiate("$" + variableName, type);
   }
 
   @Override
@@ -348,8 +350,8 @@ public BooleanExpression composeComparisonOperation(Expression leftOperand, Stri
   }
 
   @Override
-  public BooleanExpression composeSequenceEqualFunction(ListExpressionBase one,
-      ListExpressionBase two) {
+  public BooleanExpression composeSequenceEqualFunction(ListExpression<? extends Expression> one,
+  ListExpression<? extends Expression> two) {
     return new BooleanExpression("deep-equal(sort(" + one.script + "), sort(" + two.script + "))");
   }
 
@@ -361,7 +363,7 @@ public NumericExpression composeCountOperation(PathExpression nodeSet) {
   }
 
   @Override
-  public NumericExpression composeCountOperation(ListExpressionBase list) {
+  public NumericExpression composeCountOperation(ListExpression<? extends Expression> list) {
     return new NumericExpression("count(" + list.script + ")");
   }
 
diff --git a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
index 9e4d2653..94585500 100644
--- a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
@@ -26,7 +26,7 @@ private String lines(String... lines) {
 
   @Test
   void testTemplateLineNoIdent() {
-    assertEquals("declare block01 = { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01)",
+    assertEquals("let block01() -> { text('foo') }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text} foo"));
   }
 
@@ -35,12 +35,12 @@ void testTemplateLineNoIdent() {
    */
   @Test
   void testTemplateLineOutline_Autogenerated() {
-    assertEquals(lines("declare block01 = { outline('1') text('foo')", 
-    "for-each(../..).call(block0101) }", 
-    "declare block0101 = { outline('1.1') text('bar')", 
-    "for-each(PathNode/NumberField).call(block010101) }", 
-    "declare block010101 = { text('foo') }", 
-    "for-each(/*/PathNode/TextField).call(block01)"), //
+    assertEquals(lines("let block01() -> { #1: text('foo')", 
+    "for-each(../..).call(block0101()) }", 
+    "let block0101() -> { #1.1: text('bar')", 
+    "for-each(PathNode/NumberField).call(block010101()) }", 
+    "let block010101() -> { text('foo') }", 
+    "for-each(/*/PathNode/TextField).call(block01())"), //
         translate(lines("{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo")));
   }
 
@@ -50,12 +50,12 @@ void testTemplateLineOutline_Autogenerated() {
    */
   @Test
   void testTemplateLineOutline_Explicit() {
-    assertEquals(lines("declare block01 = { outline('2') text('foo')", 
-    "for-each(../..).call(block0101) }", 
-    "declare block0101 = { outline('2.3') text('bar')", 
-    "for-each(PathNode/NumberField).call(block010101) }", 
-    "declare block010101 = { text('foo') }", 
-    "for-each(/*/PathNode/TextField).call(block01)"), //
+    assertEquals(lines("let block01() -> { #2: text('foo')", 
+    "for-each(../..).call(block0101()) }", 
+    "let block0101() -> { #2.3: text('bar')", 
+    "for-each(PathNode/NumberField).call(block010101()) }", 
+    "let block010101() -> { text('foo') }", 
+    "for-each(/*/PathNode/TextField).call(block01())"), //
         translate(lines("2{BT-00-Text} foo", "\t3{ND-Root} bar", "\t\t{BT-00-Number} foo")));
   }
 
@@ -66,12 +66,12 @@ void testTemplateLineOutline_Explicit() {
    */
   @Test
   void testTemplateLineOutline_Mixed() {
-    assertEquals(lines("declare block01 = { outline('2') text('foo')", 
-    "for-each(../..).call(block0101) }", 
-    "declare block0101 = { outline('2.1') text('bar')", 
-    "for-each(PathNode/NumberField).call(block010101) }", 
-    "declare block010101 = { text('foo') }", 
-    "for-each(/*/PathNode/TextField).call(block01)"), //
+    assertEquals(lines("let block01() -> { #2: text('foo')", 
+    "for-each(../..).call(block0101()) }", 
+    "let block0101() -> { #2.1: text('bar')", 
+    "for-each(PathNode/NumberField).call(block010101()) }", 
+    "let block010101() -> { text('foo') }", 
+    "for-each(/*/PathNode/TextField).call(block01())"), //
         translate(lines("2{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo")));
   }
 
@@ -81,12 +81,12 @@ void testTemplateLineOutline_Mixed() {
    */
   @Test
   void testTemplateLineOutline_Suppressed() {
-    assertEquals(lines("declare block01 = { outline('2') text('foo')", 
-    "for-each(../..).call(block0101) }", 
-    "declare block0101 = { text('bar')", 
-    "for-each(PathNode/NumberField).call(block010101) }", 
-    "declare block010101 = { text('foo') }", 
-    "for-each(/*/PathNode/TextField).call(block01)"), //
+    assertEquals(lines("let block01() -> { #2: text('foo')", 
+    "for-each(../..).call(block0101()) }", 
+    "let block0101() -> { text('bar')", 
+    "for-each(PathNode/NumberField).call(block010101()) }", 
+    "let block010101() -> { text('foo') }", 
+    "for-each(/*/PathNode/TextField).call(block01())"), //
         translate(lines("2{BT-00-Text} foo", "\t0{ND-Root} bar", "\t\t{BT-00-Number} foo")));
   }
 
@@ -97,12 +97,12 @@ void testTemplateLineOutline_Suppressed() {
   @Test
   void testTemplateLineOutline_SuppressedAtParent() {
     // Outline is ignored if the line has no children
-    assertEquals(lines("declare block01 = { text('foo')", 
-    "for-each(../..).call(block0101) }", 
-    "declare block0101 = { outline('1') text('bar')", 
-    "for-each(PathNode/NumberField).call(block010101) }", 
-    "declare block010101 = { text('foo') }", 
-    "for-each(/*/PathNode/TextField).call(block01)"), //
+    assertEquals(lines("let block01() -> { text('foo')", 
+    "for-each(../..).call(block0101()) }", 
+    "let block0101() -> { #1: text('bar')", 
+    "for-each(PathNode/NumberField).call(block010101()) }", 
+    "let block010101() -> { text('foo') }", 
+    "for-each(/*/PathNode/TextField).call(block01())"), //
         translate(lines("0{BT-00-Text} foo", "\t{ND-Root} bar", "\t\t{BT-00-Number} foo")));
   }
 
@@ -114,18 +114,18 @@ void testTemplateLineFirstIndented() {
   @Test
   void testTemplateLineIdentTab() {
     assertEquals(
-        lines("declare block01 = { outline('1') text('foo')", "for-each(.).call(block0101) }", //
-        "declare block0101 = { text('bar') }", //
-        "for-each(/*/PathNode/TextField).call(block01)"),//
+        lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", //
+        "let block0101() -> { text('bar') }", //
+        "for-each(/*/PathNode/TextField).call(block01())"),//
         translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar")));
   }
 
   @Test
   void testTemplateLineIdentSpaces() {
     assertEquals(
-        lines("declare block01 = { outline('1') text('foo')", "for-each(.).call(block0101) }", //
-        "declare block0101 = { text('bar') }", //
-        "for-each(/*/PathNode/TextField).call(block01)"),//
+        lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", //
+        "let block0101() -> { text('bar') }", //
+        "for-each(/*/PathNode/TextField).call(block01())"),//
         translate(lines("{BT-00-Text} foo", "    {BT-00-Text} bar")));
   }
 
@@ -144,11 +144,11 @@ void testTemplateLineIdentMixedSpaceThenTab() {
   @Test
   void testTemplateLineIdentLower() {
     assertEquals(
-        lines("declare block01 = { outline('1') text('foo')", "for-each(.).call(block0101) }", 
-        "declare block0101 = { text('bar') }", 
-        "declare block02 = { text('code') }", 
-        "for-each(/*/PathNode/TextField).call(block01)", 
-        "for-each(/*/PathNode/CodeField).call(block02)"),
+        lines("let block01() -> { #1: text('foo')", "for-each(.).call(block0101()) }", 
+        "let block0101() -> { text('bar') }", 
+        "let block02() -> { text('code') }", 
+        "for-each(/*/PathNode/TextField).call(block01())", 
+        "for-each(/*/PathNode/CodeField).call(block02())"),
         translate(lines("{BT-00-Text} foo", "\t{BT-00-Text} bar", "{BT-00-Code} code")));
   }
 
@@ -161,10 +161,10 @@ void testTemplateLineIdentUnexpected() {
   @Test
   void testTemplateLine_VariableScope() {
     assertEquals(
-        lines("declare block01 = { outline('1') eval(for $x in . return $x)", //
-        "for-each(.).call(block0101) }", //
-        "declare block0101 = { eval(for $x in . return $x) }", //
-        "for-each(/*/PathNode/TextField).call(block01)"),//
+        lines("let block01() -> { #1: eval(for $x in . return $x)", //
+        "for-each(.).call(block0101()) }", //
+        "let block0101() -> { eval(for $x in . return $x) }", //
+        "for-each(/*/PathNode/TextField).call(block01())"),//
         translate(lines("{BT-00-Text} ${for text:$x in BT-00-Text return $x}", "    {BT-00-Text} ${for text:$x in BT-00-Text return $x}")));
 
   }
@@ -175,28 +175,28 @@ void testTemplateLine_VariableScope() {
   @Test
   void testStandardLabelReference() {
     assertEquals(
-        "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{field|name|BT-00-Text}"));
   }
 
   @Test
   void testStandardLabelReference_UsingLabelTypeAsAssetId() {
     assertEquals(
-        "declare block01 = { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(concat('auxiliary', '|', 'text', '|', 'value')) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{auxiliary|text|value}"));
   }
 
   @Test
   void testShorthandBtLabelReference() {
     assertEquals(
-        "declare block01 = { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(concat('business-term', '|', 'name', '|', 'BT-00')) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{name|BT-00}"));
   }
 
   @Test
   void testShorthandFieldLabelReference() {
     assertEquals(
-        "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{name|BT-00-Text}"));
   }
 
@@ -208,42 +208,42 @@ void testShorthandBtLabelReference_MissingLabelType() {
   @Test
   void testShorthandIndirectLabelReferenceForIndicator() {
     assertEquals(
-        "declare block01 = { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{BT-00-Indicator}"));
   }
 
   @Test
   void testShorthandIndirectLabelReferenceForCode() {
     assertEquals(
-        "declare block01 = { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(for $item in ../CodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{BT-00-Code}"));
   }
 
   @Test
   void testShorthandIndirectLabelReferenceForInternalCode() {
     assertEquals(
-        "declare block01 = { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(for $item in ../InternalCodeField return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{BT-00-Internal-Code}"));
   }
 
   @Test
   void testShorthandIndirectLabelReferenceForCodeAttribute() {
     assertEquals(
-        "declare block01 = { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{BT-00-CodeAttribute}"));
   }
 
   @Test
   void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameAttributeInContext() {
     assertEquals(
-        "declare block01 = { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01)",
+        "let block01() -> { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())",
         translate("{BT-00-CodeAttribute}  #{BT-00-CodeAttribute}"));
   }
 
   @Test
   void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() {
     assertEquals(
-        "declare block01 = { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01)",
+        "let block01() -> { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())",
         translate("{BT-00-Code}  #{BT-00-CodeAttribute}"));
   }
 
@@ -260,14 +260,14 @@ void testShorthandIndirectLabelReferenceForAttribute() {
   @Test
   void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndIndicatorField() {
     assertEquals(
-        "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/IndicatorField).call(block01)",
+        "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/IndicatorField).call(block01())",
         translate("{BT-00-Indicator}  #{name}"));
   }
 
   @Test
   void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndCodeField() {
     assertEquals(
-        "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nfor-each(/*/PathNode/CodeField).call(block01)",
+        "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Code')) }\nfor-each(/*/PathNode/CodeField).call(block01())",
         translate("{BT-00-Code}  #{name}"));
   }
 
@@ -279,7 +279,7 @@ void testShorthandLabelReferenceFromContext_WithValueLabelTypeAndTextField() {
   @Test
   void testShorthandLabelReferenceFromContext_WithOtherLabelType() {
     assertEquals(
-        "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text')) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{name}"));
   }
 
@@ -291,14 +291,14 @@ void testShorthandLabelReferenceFromContext_WithUnknownLabelType() {
   @Test
   void testShorthandLabelReferenceFromContext_WithNodeContext() {
     assertEquals(
-        "declare block01 = { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01)",
+        "let block01() -> { label(concat('node', '|', 'name', '|', 'ND-Root')) }\nfor-each(/*).call(block01())",
         translate("{ND-Root}  #{name}"));
   }
 
   @Test
   void testShorthandIndirectLabelReferenceFromContextField() {
     assertEquals(
-        "declare block01 = { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01)",
+        "let block01() -> { label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())",
         translate("{BT-00-Code} #value"));
   }
 
@@ -312,14 +312,14 @@ void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() {
 
   @Test
   void testShorthandFieldValueReferenceFromContextField() {
-    assertEquals("declare block01 = { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01)",
+    assertEquals("let block01() -> { eval(.) }\nfor-each(/*/PathNode/CodeField).call(block01())",
         translate("{BT-00-Code} $value"));
   }
 
   @Test
   void testShorthandFieldValueReferenceFromContextField_WithText() {
     assertEquals(
-        "declare block01 = { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01)",
+        "let block01() -> { text('blah ')label(for $item in . return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(.)text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())",
         translate("{BT-00-Code} blah #value blah $value blah"));
   }
 
@@ -334,14 +334,14 @@ void testShorthandFieldValueReferenceFromContextField_WithNodeContext() {
   @Test
   void testNestedExpression() {
     assertEquals(
-        "declare block01 = { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\nfor-each(/*/PathNode/TextField).call(block01)",
+        "let block01() -> { label(concat('field', '|', 'name', '|', ./normalize-space(text()))) }\nfor-each(/*/PathNode/TextField).call(block01())",
         translate("{BT-00-Text}  #{field|name|${BT-00-Text}}"));
   }
 
   @Test
   void testEndOfLineComments() {
     assertEquals(
-        "declare block01 = { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01)",
+        "let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01())",
         translate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah"));
   }
 }
diff --git a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
index c79a3fc5..9b56446a 100644
--- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
+++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
@@ -1,8 +1,13 @@
 package eu.europa.ted.efx.mock;
 
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
+
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
 import eu.europa.ted.efx.interfaces.MarkupGenerator;
 import eu.europa.ted.efx.model.Expression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
@@ -33,15 +38,29 @@ public Markup renderFreeText(String freeText) {
 
   @Override
   public Markup composeFragmentDefinition(String name, String number, Markup content) {
+    return this.composeFragmentDefinition(name, number, content, new LinkedHashSet<>());
+  }
+
+  @Override
+  public Markup composeFragmentDefinition(String name, String number, Markup content, Set<String> parameters) {
     if (StringUtils.isBlank(number)) {
-      return new Markup(String.format("declare %s = { %s }", name, content.script));
+      return new Markup(String.format("let %s(%s) -> { %s }", name,
+          parameters.stream().collect(Collectors.joining(", ")), content.script));
     }
-    return new Markup(String.format("declare %s = { outline('%s') %s }", name, number, content.script));
+    return new Markup(String.format("let %s(%s) -> { #%s: %s }", name,
+        parameters.stream().collect(Collectors.joining(", ")), number, content.script));
   }
 
   @Override
   public Markup renderFragmentInvocation(String name, PathExpression context) {
-    return new Markup(String.format("for-each(%s).call(%s)", context.script, name));
+    return this.renderFragmentInvocation(name, context, new LinkedHashSet<>());
+  }
+
+  @Override
+  public Markup renderFragmentInvocation(String name, PathExpression context,
+      Set<Pair<String, String>> variables) {
+    return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name, variables.stream()
+        .map(v -> String.format("%s:%s", v.getLeft(), v.getRight())).collect(Collectors.joining(", "))));
   }
 
   @Override

From e1336283caa2a629bdd8771ffa0952ef89b286da Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Fri, 14 Apr 2023 13:31:05 +0200
Subject: [PATCH 05/39] [repositories]: Added repository for OSS snapshots,
 where eforms-core-java snapshots are available.

---
 pom.xml | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/pom.xml b/pom.xml
index 59d341b1..61ab9a1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -165,6 +165,20 @@
     </dependency>
   </dependencies>
 
+  <repositories>
+    <repository>
+      <id>oss-snapshots</id>
+      <name>OSS Snapshots repository</name>
+      <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
+      <releases>
+        <enabled>false</enabled>
+      </releases>
+      <snapshots>
+        <enabled>true</enabled>
+      </snapshots>
+    </repository>
+  </repositories>
+
   <build>
     <pluginManagement>
       <plugins>

From 1293325e373efc54bfee962f22a1db752c0c408b Mon Sep 17 00:00:00 2001
From: Bertrand Lorentz <bertrand.lorentz@publications.europa.eu>
Date: Fri, 14 Apr 2023 16:10:46 +0200
Subject: [PATCH 06/39] test: Fix testExceptFunction_With*References methods

Those methods had all the same body, so not really testing what was
intended. Use fields of the proper types instead.
Also harmonize the method names to:
testExceptFunction_With<TYPE>FieldReferences
---
 .../ted/efx/EfxExpressionTranslatorTest.java  | 23 ++++++++++---------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index ace681f3..497d9f6e 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -1350,29 +1350,30 @@ void testExceptFunction_WithTextFieldReferences() {
 
   @Test
   void testExceptFunction_WithNumberFieldReferences() {
-    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
-        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+    assertEquals("distinct-values(for $L1 in PathNode/IntegerField/number() return if (every $L2 in PathNode/IntegerField/number() satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-Integer, BT-00-Integer)"));
   }
+
   @Test
-  void testExceptFunction_WithBooleanReferences() {
-    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
-        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+  void testExceptFunction_WithBooleanFieldReferences() {
+    assertEquals("distinct-values(for $L1 in PathNode/IndicatorField return if (every $L2 in PathNode/IndicatorField satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-Indicator, BT-00-Indicator)"));
   }
 
     @Test
-  void testExceptFunction_WithDateReferences() {
-    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
-        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+  void testExceptFunction_WithDateFieldReferences() {
+    assertEquals("distinct-values(for $L1 in PathNode/StartDateField/xs:date(text()) return if (every $L2 in PathNode/StartDateField/xs:date(text()) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-StartDate, BT-00-StartDate)"));
   }
 
   @Test
   void testExceptFunction_WithTimeFieldReferences() {
-    assertEquals("distinct-values(for $L1 in PathNode/TextField/normalize-space(text()) return if (every $L2 in PathNode/TextField/normalize-space(text()) satisfies $L1 != $L2) then $L1 else ())",
-        test("ND-Root", "value-except(BT-00-Text, BT-00-Text)"));
+    assertEquals("distinct-values(for $L1 in PathNode/StartTimeField/xs:time(text()) return if (every $L2 in PathNode/StartTimeField/xs:time(text()) satisfies $L1 != $L2) then $L1 else ())",
+        test("ND-Root", "value-except(BT-00-StartTime, BT-00-StartTime)"));
   }
 
   @Test
-  void testExceptFunction_WithDurationReferences() {
+  void testExceptFunction_WithDurationFieldReferences() {
     assertEquals("distinct-values(for $L1 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) return if (every $L2 in (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())) satisfies $L1 != $L2) then $L1 else ())",
         test("ND-Root", "value-except(BT-00-Measure, BT-00-Measure)"));
   }

From 56ae083ab8b4ab1873b2008519e7ea6ea4474aad Mon Sep 17 00:00:00 2001
From: Bertrand Lorentz <bertrand.lorentz@publications.europa.eu>
Date: Fri, 14 Apr 2023 16:51:35 +0200
Subject: [PATCH 07/39] test: Add missing unit tests for durations

The code coverage report showed some functions related to durations that
were not covered.
---
 .../ted/efx/EfxExpressionTranslatorTest.java  | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index 497d9f6e..bd2e16d5 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -1170,6 +1170,18 @@ void testDateFromStringFunction() {
         test("ND-Root", "date(BT-00-Text)"));
   }
 
+  @Test
+  void testDatePlusMeasureFunction() {
+    assertEquals("(PathNode/StartDateField/xs:date(text()) + (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))",
+        test("ND-Root", "add-measure(BT-00-StartDate, BT-00-Measure)"));
+  }
+
+  @Test
+  void testDateMinusMeasureFunction() {
+    assertEquals("(PathNode/StartDateField/xs:date(text()) - (for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))",
+        test("ND-Root", "subtract-measure(BT-00-StartDate, BT-00-Measure)"));
+  }
+
   /*** Time functions ***/
 
   @Test
@@ -1178,6 +1190,20 @@ void testTimeFromStringFunction() {
         test("ND-Root", "time(BT-00-Text)"));
   }
 
+  /*** Duration functions ***/
+
+  @Test
+  void testDayTimeDurationFromStringFunction() {
+    assertEquals("xs:yearMonthDuration(PathNode/TextField/normalize-space(text()))",
+        test("ND-Root", "year-month-duration(BT-00-Text)"));
+  }
+
+  @Test
+  void testYearMonthDurationFromStringFunction() {
+    assertEquals("xs:dayTimeDuration(PathNode/TextField/normalize-space(text()))",
+        test("ND-Root", "day-time-duration(BT-00-Text)"));
+  }
+
   /*** Sequence Functions ***/
 
   @Test
@@ -1204,6 +1230,12 @@ void testDistinctValuesFunction_WithTimeSequences() {
         test("ND-Root", "distinct-values((12:00:00Z, 13:00:00Z, 12:00:00Z, 14:00:00Z))"));
   }
 
+  @Test
+  void testDistinctValuesFunction_WithDurationSequences() {
+    assertEquals("distinct-values((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')))",
+        test("ND-Root", "distinct-values((P1W, P2D, P2D, P5D))"));
+  }
+
   @Test
   void testDistinctValuesFunction_WithBooleanSequences() {
     assertEquals("distinct-values((true(),false(),false(),false()))",
@@ -1242,6 +1274,12 @@ void testUnionFunction_WithTimeSequences() {
         test("ND-Root", "value-union((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"));
   }
 
+  @Test
+  void testUnionFunction_WithDurationSequences() {
+    assertEquals("distinct-values(((xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')), (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D'))))",
+        test("ND-Root", "value-union((P1W, P2D), (P2D, P5D))"));
+  }
+
   @Test
   void testUnionFunction_WithBooleanSequences() {
     assertEquals("distinct-values(((true(),false()), (false(),false())))",
@@ -1286,6 +1324,12 @@ void testIntersectFunction_WithTimeSequences() {
         test("ND-Root", "value-intersect((12:00:00Z, 13:00:00Z), (12:00:00Z, 14:00:00Z))"));
   }
 
+  @Test
+  void testIntersectFunction_WithDurationSequences() {
+    assertEquals("distinct-values(for $L1 in (xs:dayTimeDuration('P7D'),xs:dayTimeDuration('P2D')) return if (some $L2 in (xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')) satisfies $L1 = $L2) then $L1 else ())",
+        test("ND-Root", "value-intersect((P1W, P2D), (P2D, P5D))"));
+  }
+
   @Test
   void testIntersectFunction_WithBooleanSequences() {
     assertEquals("distinct-values(for $L1 in (true(),false()) return if (some $L2 in (false(),false()) satisfies $L1 = $L2) then $L1 else ())",

From 14bae4119fd1747696f4eba50cbed8d126e2314c Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Sat, 15 Apr 2023 12:36:27 +0200
Subject: [PATCH 08/39] Removed unnecessary interface changes

---
 .../europa/ted/efx/interfaces/MarkupGenerator.java | 14 +-------------
 .../java/eu/europa/ted/efx/model/ContentBlock.java |  4 ++--
 .../europa/ted/efx/mock/MarkupGeneratorMock.java   |  2 --
 3 files changed, 3 insertions(+), 17 deletions(-)

diff --git a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
index bb6f3773..926bec38 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
@@ -72,22 +72,10 @@ public interface MarkupGenerator {
    */
   Markup composeFragmentDefinition(final String name, String number, Markup content);
 
-  /**
-   * Given a fragment name (identifier) and some pre-rendered content, this method
-   * returns the code
-   * that encapsulates it in the target template.
-   */
-  Markup composeFragmentDefinition(final String name, String number, Markup content, Set<String> parameters);
-
-  /**
-   * @deprecated Use {@link #renderFragmentInvocation(String, PathExpression, Set)} instead.
-   */
-  @Deprecated(since = "2.0.0", forRemoval = true)
-  Markup renderFragmentInvocation(final String name, final PathExpression context);
 
   /**
    * Given a fragment name (identifier), and an evaluation context, this method returns the code
    * that invokes (uses) the fragment.
    */
-  Markup renderFragmentInvocation(final String name, final PathExpression context, final Set<Pair<String, String>> variables);
+  Markup renderFragmentInvocation(final String name, final PathExpression context);
 }
diff --git a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java
index c2fb4153..26817626 100644
--- a/src/main/java/eu/europa/ted/efx/model/ContentBlock.java
+++ b/src/main/java/eu/europa/ted/efx/model/ContentBlock.java
@@ -147,7 +147,7 @@ public Markup renderContent(MarkupGenerator markupGenerator) {
 
   public void renderTemplate(MarkupGenerator markupGenerator, List<Markup> templates) {
     templates.add(markupGenerator.composeFragmentDefinition(this.id, this.getOutlineNumber(),
-        this.renderContent(markupGenerator), this.getTemplateParameters()));
+        this.renderContent(markupGenerator)));
     for (ContentBlock child : this.children) {
       child.renderTemplate(markupGenerator, templates);
     }
@@ -159,6 +159,6 @@ public Markup renderCallTemplate(MarkupGenerator markupGenerator) {
       variables.addAll(parent.getAllVariables().stream().map(v -> Pair.of(v.name, v.referenceExpression.script)).collect(Collectors.toList()));
     }
     variables.addAll(this.getOwnVariables().stream().map(v -> Pair.of(v.name, v.initializationExpression.script)).collect(Collectors.toList()));
-    return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath(), variables);
+    return markupGenerator.renderFragmentInvocation(this.id, this.context.relativePath());
   }
 }
diff --git a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
index 9b56446a..830e906e 100644
--- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
+++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
@@ -41,7 +41,6 @@ public Markup composeFragmentDefinition(String name, String number, Markup conte
     return this.composeFragmentDefinition(name, number, content, new LinkedHashSet<>());
   }
 
-  @Override
   public Markup composeFragmentDefinition(String name, String number, Markup content, Set<String> parameters) {
     if (StringUtils.isBlank(number)) {
       return new Markup(String.format("let %s(%s) -> { %s }", name,
@@ -56,7 +55,6 @@ public Markup renderFragmentInvocation(String name, PathExpression context) {
     return this.renderFragmentInvocation(name, context, new LinkedHashSet<>());
   }
 
-  @Override
   public Markup renderFragmentInvocation(String name, PathExpression context,
       Set<Pair<String, String>> variables) {
     return new Markup(String.format("for-each(%s).call(%s(%s))", context.script, name, variables.stream()

From 3ab8c5f20f210cccc476084a452419c2c3561b7d Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Thu, 20 Apr 2023 01:43:33 +0200
Subject: [PATCH 09/39] Improved numeric formatting (TEDEFO-2028)

---
 .../ted/eforms/sdk/ComponentFactory.java      | 10 +--
 .../java/eu/europa/ted/efx/EfxTranslator.java | 47 ++++++++++---
 .../europa/ted/efx/EfxTranslatorOptions.java  | 22 ++++++
 .../efx/component/EfxTranslatorFactory.java   | 11 +--
 .../TranslatorDependencyFactory.java          |  4 +-
 .../ted/efx/interfaces/TranslatorOptions.java |  7 ++
 .../europa/ted/efx/model/DecimalFormat.java   | 69 +++++++++++++++++++
 .../eu/europa/ted/efx/model/Expression.java   | 32 +++++++++
 .../ted/efx/xpath/XPathScriptGenerator.java   | 30 +++++---
 .../ted/efx/EfxExpressionCombinedTest.java    |  6 +-
 .../ted/efx/EfxExpressionTranslatorTest.java  | 12 ++--
 .../ted/efx/EfxTemplateTranslatorTest.java    |  6 +-
 .../ted/efx/mock/DependencyFactoryMock.java   |  8 ++-
 13 files changed, 223 insertions(+), 41 deletions(-)
 create mode 100644 src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
 create mode 100644 src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java
 create mode 100644 src/main/java/eu/europa/ted/efx/model/DecimalFormat.java

diff --git a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java
index 736fc563..a88dd0fc 100644
--- a/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java
+++ b/src/main/java/eu/europa/ted/eforms/sdk/ComponentFactory.java
@@ -4,11 +4,13 @@
 import java.text.MessageFormat;
 import java.util.HashMap;
 import java.util.Map;
+
 import eu.europa.ted.eforms.sdk.component.SdkComponentFactory;
 import eu.europa.ted.eforms.sdk.component.SdkComponentType;
 import eu.europa.ted.efx.interfaces.MarkupGenerator;
 import eu.europa.ted.efx.interfaces.ScriptGenerator;
 import eu.europa.ted.efx.interfaces.SymbolResolver;
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 
 public class ComponentFactory extends SdkComponentFactory {
   public static final ComponentFactory INSTANCE = new ComponentFactory();
@@ -43,15 +45,15 @@ public static SymbolResolver getSymbolResolver(final String sdkVersion, final Pa
     });
   }
 
-  public static MarkupGenerator getMarkupGenerator(final String sdkVersion)
+  public static MarkupGenerator getMarkupGenerator(final String sdkVersion, TranslatorOptions options)
       throws InstantiationException {
     return ComponentFactory.INSTANCE.getComponentImpl(sdkVersion,
-        SdkComponentType.MARKUP_GENERATOR, MarkupGenerator.class);
+        SdkComponentType.MARKUP_GENERATOR, MarkupGenerator.class, options);
   }
 
-  public static ScriptGenerator getScriptGenerator(final String sdkVersion)
+  public static ScriptGenerator getScriptGenerator(final String sdkVersion, TranslatorOptions options)
       throws InstantiationException {
     return ComponentFactory.INSTANCE.getComponentImpl(sdkVersion,
-        SdkComponentType.SCRIPT_GENERATOR, ScriptGenerator.class);
+        SdkComponentType.SCRIPT_GENERATOR, ScriptGenerator.class, options);
   }
 }
diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslator.java b/src/main/java/eu/europa/ted/efx/EfxTranslator.java
index 4c09c53f..c7a86a70 100644
--- a/src/main/java/eu/europa/ted/efx/EfxTranslator.java
+++ b/src/main/java/eu/europa/ted/efx/EfxTranslator.java
@@ -18,6 +18,7 @@
 import java.nio.file.Path;
 import eu.europa.ted.efx.component.EfxTranslatorFactory;
 import eu.europa.ted.efx.interfaces.TranslatorDependencyFactory;
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 
 /**
  * Provided for convenience, this class exposes static methods that allow you to quickly instantiate
@@ -25,6 +26,8 @@
  */
 public class EfxTranslator {
  
+  private static TranslatorOptions defaultOptions = EfxTranslatorOptions.DEFAULT;
+
   /**
    * Instantiates an EFX expression translator and translates a given expression.
    * 
@@ -39,11 +42,17 @@ public class EfxTranslator {
    * @throws InstantiationException
    */
   public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
-      final String expression, final String... expressionParameters) throws InstantiationException {
-    return EfxTranslatorFactory.getEfxExpressionTranslator(sdkVersion, dependencyFactory)
+      final String expression, TranslatorOptions options, final String... expressionParameters)
+      throws InstantiationException {
+    return EfxTranslatorFactory.getEfxExpressionTranslator(sdkVersion, dependencyFactory, options)
         .translateExpression(expression, expressionParameters);
   }
 
+  public static String translateExpression(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
+      final String expression, final String... expressionParameters) throws InstantiationException {
+    return translateExpression(dependencyFactory, sdkVersion, expression, defaultOptions, expressionParameters);
+  }
+
   /**
    * Instantiates an EFX template translator and translates the EFX template contained in the given
    * file.
@@ -58,13 +67,19 @@ public static String translateExpression(final TranslatorDependencyFactory depen
    * @throws IOException
    * @throws InstantiationException
    */
-  public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, 
-      final Path pathname)
+  public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
+      final Path pathname, TranslatorOptions options)
       throws IOException, InstantiationException {
-    return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory)
+    return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory, options)
         .renderTemplate(pathname);
   }
 
+  public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
+      final Path pathname)
+      throws IOException, InstantiationException {
+    return translateTemplate(dependencyFactory, sdkVersion, pathname, defaultOptions);
+  }
+
   /**
    * Instantiates an EFX template translator and translates the given EFX template.
    * 
@@ -78,12 +93,18 @@ public static String translateTemplate(final TranslatorDependencyFactory depende
    * @throws InstantiationException
    */
   public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
-      final String template)
+      final String template, TranslatorOptions options)
       throws InstantiationException {
-    return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory)
+    return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory, options)
         .renderTemplate(template);
   }
 
+  public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
+      final String template)
+      throws InstantiationException {
+    return translateTemplate(dependencyFactory, sdkVersion, template, defaultOptions);
+  }
+
   /**
    * Instantiates an EFX template translator and translates the EFX template contained in the given
    * InputStream.
@@ -98,10 +119,16 @@ public static String translateTemplate(final TranslatorDependencyFactory depende
    * @throws IOException
    * @throws InstantiationException
    */
-  public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion, 
-      final InputStream stream)
+  public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
+      final InputStream stream, TranslatorOptions options)
       throws IOException, InstantiationException {
-    return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory)
+    return EfxTranslatorFactory.getEfxTemplateTranslator(sdkVersion, dependencyFactory, options)
         .renderTemplate(stream);
   }
+
+  public static String translateTemplate(final TranslatorDependencyFactory dependencyFactory, final String sdkVersion,
+      final InputStream stream)
+      throws IOException, InstantiationException {
+    return translateTemplate(dependencyFactory, sdkVersion, stream, defaultOptions);
+  }
 }
diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
new file mode 100644
index 00000000..61aafbf1
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
@@ -0,0 +1,22 @@
+package eu.europa.ted.efx;
+
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
+import eu.europa.ted.efx.model.DecimalFormat;
+
+public class EfxTranslatorOptions implements TranslatorOptions {
+
+    // Change to EfxDecimalFormatSymbols.EFX_DEFAULT to use the decimal format
+    // preferred by OP (space as thousands separator and comma as decimal separator).
+    public static final EfxTranslatorOptions DEFAULT = new EfxTranslatorOptions(DecimalFormat.XSL_DEFAULT);
+
+    private final DecimalFormat symbols;
+
+    public EfxTranslatorOptions(DecimalFormat symbols) {
+        this.symbols = symbols;
+    }
+
+    @Override
+    public DecimalFormat getDecimalFormat() {
+        return this.symbols;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java b/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java
index f3e7313b..e40ae6bb 100644
--- a/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java
+++ b/src/main/java/eu/europa/ted/efx/component/EfxTranslatorFactory.java
@@ -5,6 +5,7 @@
 import eu.europa.ted.efx.interfaces.EfxExpressionTranslator;
 import eu.europa.ted.efx.interfaces.EfxTemplateTranslator;
 import eu.europa.ted.efx.interfaces.TranslatorDependencyFactory;
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 
 public class EfxTranslatorFactory extends SdkComponentFactory {
   public static final EfxTranslatorFactory INSTANCE = new EfxTranslatorFactory();
@@ -14,18 +15,18 @@ private EfxTranslatorFactory() {
   }
 
   public static EfxExpressionTranslator getEfxExpressionTranslator(final String sdkVersion,
-      final TranslatorDependencyFactory factory) throws InstantiationException {
+      final TranslatorDependencyFactory factory, TranslatorOptions options) throws InstantiationException {
     return EfxTranslatorFactory.INSTANCE.getComponentImpl(sdkVersion,
         SdkComponentType.EFX_EXPRESSION_TRANSLATOR, EfxExpressionTranslator.class,
-        factory.createSymbolResolver(sdkVersion), factory.createScriptGenerator(sdkVersion),
+        factory.createSymbolResolver(sdkVersion), factory.createScriptGenerator(sdkVersion, options),
         factory.createErrorListener());
   }
 
   public static EfxTemplateTranslator getEfxTemplateTranslator(final String sdkVersion,
-      final TranslatorDependencyFactory factory) throws InstantiationException {
+      final TranslatorDependencyFactory factory, TranslatorOptions options) throws InstantiationException {
     return EfxTranslatorFactory.INSTANCE.getComponentImpl(sdkVersion,
         SdkComponentType.EFX_TEMPLATE_TRANSLATOR, EfxTemplateTranslator.class,
-        factory.createMarkupGenerator(sdkVersion), factory.createSymbolResolver(sdkVersion),
-        factory.createScriptGenerator(sdkVersion), factory.createErrorListener());
+        factory.createMarkupGenerator(sdkVersion, options), factory.createSymbolResolver(sdkVersion),
+        factory.createScriptGenerator(sdkVersion, options), factory.createErrorListener());
   }
 }
diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java
index e06dcd00..d5211d45 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java
@@ -52,7 +52,7 @@ public interface TranslatorDependencyFactory {
    *        language features that ScriptGenerator instance should be able to handle.
    * @return An instance of ScriptGenerator to be used by the EFX translator.
    */
-  public ScriptGenerator createScriptGenerator(String sdkVersion);
+  public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options);
 
   /**
    * Creates a MarkupGenerator instance.
@@ -65,7 +65,7 @@ public interface TranslatorDependencyFactory {
    *        language features that MarkupGenerator instance should be able to handle.
    * @return The instance of MarkupGenerator to be used by the EFX translator.
    */
-  public MarkupGenerator createMarkupGenerator(String sdkVersion);
+  public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options);
 
   /**
    * Creates an error listener instance.
diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java
new file mode 100644
index 00000000..b0074b52
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java
@@ -0,0 +1,7 @@
+package eu.europa.ted.efx.interfaces;
+
+import eu.europa.ted.efx.model.DecimalFormat;
+
+public interface TranslatorOptions {
+    public DecimalFormat getDecimalFormat();
+}
diff --git a/src/main/java/eu/europa/ted/efx/model/DecimalFormat.java b/src/main/java/eu/europa/ted/efx/model/DecimalFormat.java
new file mode 100644
index 00000000..001060f1
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/DecimalFormat.java
@@ -0,0 +1,69 @@
+package eu.europa.ted.efx.model;
+
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+
+public class DecimalFormat extends DecimalFormatSymbols {
+
+    public final static DecimalFormat XSL_DEFAULT = DecimalFormat.getXslDefault();
+    public final static DecimalFormat EFX_DEFAULT = DecimalFormat.getEfxDefault();
+
+    DecimalFormat(Locale locale) {
+        super(locale);
+    }
+
+    private static DecimalFormat getXslDefault() {
+        DecimalFormat symbols = new DecimalFormat(Locale.US);
+        symbols.setDecimalSeparator('.');
+        symbols.setGroupingSeparator(',');
+        symbols.setMinusSign('-');
+        symbols.setPercent('%');
+        symbols.setPerMill('‰');
+        symbols.setZeroDigit('0');
+        symbols.setDigit('#');
+        symbols.setPatternSeparator(';');
+        symbols.setInfinity("Infinity");
+        symbols.setNaN("NaN");
+        return symbols;
+    }
+
+    private static DecimalFormat getEfxDefault() {
+        DecimalFormat symbols = DecimalFormat.getXslDefault();
+        symbols.setDecimalSeparator(',');
+        symbols.setGroupingSeparator(' ');
+        return symbols;
+    }
+
+    public String adaptFormatString(final String originalString) {
+
+        final char decimalSeparatorPlaceholder = '\uE000';
+        final char groupingSeparatorPlaceholder = '\uE001';
+        final char minusSignPlaceholder = '\uE002';
+        final char percentPlaceholder = '\uE003';
+        final char perMillePlaceholder = '\uE004';
+        final char zeroDigitPlaceholder = '\uE005';
+        final char digitPlaceholder = '\uE006';
+        final char patternSeparatorPlaceholder = '\uE007';    
+
+        String adaptedString = originalString;
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getDecimalSeparator(), decimalSeparatorPlaceholder);
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getGroupingSeparator(), groupingSeparatorPlaceholder);
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getMinusSign(), minusSignPlaceholder); 
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getPercent(), percentPlaceholder);
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getPerMill(), perMillePlaceholder);
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getZeroDigit(), zeroDigitPlaceholder);
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getDigit(), digitPlaceholder);
+        adaptedString = adaptedString.replace(XSL_DEFAULT.getPatternSeparator(), patternSeparatorPlaceholder);
+ 
+        adaptedString = adaptedString.replace(decimalSeparatorPlaceholder, this.getDecimalSeparator());
+        adaptedString = adaptedString.replace(groupingSeparatorPlaceholder, this.getGroupingSeparator());
+        adaptedString = adaptedString.replace(minusSignPlaceholder, this.getMinusSign());
+        adaptedString = adaptedString.replace(percentPlaceholder, this.getPercent());
+        adaptedString = adaptedString.replace(perMillePlaceholder, this.getPerMill());
+        adaptedString = adaptedString.replace(zeroDigitPlaceholder, this.getZeroDigit());
+        adaptedString = adaptedString.replace(digitPlaceholder, this.getDigit());
+        adaptedString = adaptedString.replace(patternSeparatorPlaceholder, this.getPatternSeparator());
+    
+        return adaptedString;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java
index edfbce30..a3afed04 100644
--- a/src/main/java/eu/europa/ted/efx/model/Expression.java
+++ b/src/main/java/eu/europa/ted/efx/model/Expression.java
@@ -67,8 +67,16 @@ public class Expression extends CallStackObject {
    */
   public final String script;
 
+  public final Boolean isLiteral;
+
   public Expression(final String script) {
     this.script = script;
+    this.isLiteral = false;
+  }
+
+  public Expression(final String script, final Boolean isLiteral) {
+    this.script = script;
+    this.isLiteral = isLiteral;
   }
 
   public static <T extends Expression> T instantiate(String script, Class<T> type) {
@@ -125,6 +133,10 @@ public static class BooleanExpression extends Expression {
     public BooleanExpression(final String script) {
       super(script);
     }
+
+    public BooleanExpression(final String script, final Boolean isLiteral) {
+      super(script, isLiteral);
+    }
   }
 
   /**
@@ -135,6 +147,10 @@ public static class NumericExpression extends Expression {
     public NumericExpression(final String script) {
       super(script);
     }
+
+    public NumericExpression(final String script, final Boolean isLiteral) {
+      super(script, isLiteral);
+    }
   }
 
   /**
@@ -145,6 +161,10 @@ public static class StringExpression extends Expression {
     public StringExpression(final String script) {
       super(script);
     }
+
+    public StringExpression(final String script, final Boolean isLiteral) {
+      super(script, isLiteral);
+    }
   }
 
   /**
@@ -155,6 +175,10 @@ public static class DateExpression extends Expression {
     public DateExpression(final String script) {
       super(script);
     }
+
+    public DateExpression(final String script, final Boolean isLiteral) {
+      super(script, isLiteral);
+    }
   }
 
   /**
@@ -165,6 +189,10 @@ public static class TimeExpression extends Expression {
     public TimeExpression(final String script) {
       super(script);
     }
+
+    public TimeExpression(final String script, final Boolean isLiteral) {
+      super(script, isLiteral);
+    }
   }
 
   /**
@@ -175,6 +203,10 @@ public static class DurationExpression extends Expression {
     public DurationExpression(final String script) {
       super(script);
     }
+    
+    public DurationExpression(final String script, final Boolean isLiteral) {
+      super(script, isLiteral);
+    }
   }
 
   /**
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
index cb73bafe..e987d81b 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -15,6 +15,7 @@
 import eu.europa.ted.eforms.sdk.component.SdkComponent;
 import eu.europa.ted.eforms.sdk.component.SdkComponentType;
 import eu.europa.ted.efx.interfaces.ScriptGenerator;
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 import eu.europa.ted.efx.model.Expression;
 import eu.europa.ted.efx.model.Expression.BooleanExpression;
 import eu.europa.ted.efx.model.Expression.DateExpression;
@@ -50,6 +51,11 @@ public class XPathScriptGenerator implements ScriptGenerator {
       entry(">", ">"), //
       entry(">=", ">="));
 
+  protected TranslatorOptions translatorOptions;
+
+  public XPathScriptGenerator(TranslatorOptions translatorOptions) {
+    this.translatorOptions = translatorOptions;
+  }
 
   @Override
   public <T extends Expression> T composeNodeReferenceWithPredicate(PathExpression nodeReference,
@@ -140,40 +146,40 @@ public <T extends Expression, L extends ListExpression<T>> L composeList(List<T>
 
   @Override
   public NumericExpression getNumericLiteralEquivalent(String literal) {
-    return new NumericExpression(literal);
+    return new NumericExpression(literal, true);
   }
 
   @Override
   public StringExpression getStringLiteralEquivalent(String literal) {
-    return new StringExpression(literal);
+    return new StringExpression(literal, true);
   }
 
   @Override
   public BooleanExpression getBooleanEquivalent(boolean value) {
-    return new BooleanExpression(value ? "true()" : "false()");
+    return new BooleanExpression(value ? "true()" : "false()", true);
   }
 
   @Override
   public DateExpression getDateLiteralEquivalent(String literal) {
-    return new DateExpression("xs:date(" + quoted(literal) + ")");
+    return new DateExpression("xs:date(" + quoted(literal) + ")", true);
   }
 
   @Override
   public TimeExpression getTimeLiteralEquivalent(String literal) {
-    return new TimeExpression("xs:time(" + quoted(literal) + ")");
+    return new TimeExpression("xs:time(" + quoted(literal) + ")", true);
   }
 
   @Override
   public DurationExpression getDurationLiteralEquivalent(final String literal) {
     if (literal.contains("M") || literal.contains("Y")) {
-      return new DurationExpression("xs:yearMonthDuration(" + quoted(literal) + ")");
+      return new DurationExpression("xs:yearMonthDuration(" + quoted(literal) + ")", true);
     }
     if (literal.contains("W")) {
       final int weeks = this.getWeeksFromDurationLiteral(literal);
       return new DurationExpression(
-          "xs:dayTimeDuration(" + quoted(String.format("P%dD", weeks * 7)) + ")");
+          "xs:dayTimeDuration(" + quoted(String.format("P%dD", weeks * 7)) + ")", true);
     }
-    return new DurationExpression("xs:dayTimeDuration(" + quoted(literal) + ")");
+    return new DurationExpression("xs:dayTimeDuration(" + quoted(literal) + ")", true);
   }
 
   @Override
@@ -416,7 +422,8 @@ public StringExpression composeSubstringExtraction(StringExpression text,
 
   @Override
   public StringExpression composeToStringConversion(NumericExpression number) {
-    return new StringExpression("format-number(" + number.script + ", '0.##########')");
+    String formatString = this.translatorOptions.getDecimalFormat().adaptFormatString("0.##########");
+    return new StringExpression("format-number(" + number.script + ", '" + formatString + "')");
   }
 
   @Override
@@ -428,12 +435,13 @@ public StringExpression composeStringConcatenation(List<StringExpression> list)
   @Override
   public StringExpression composeNumberFormatting(NumericExpression number,
       StringExpression format) {
-    return new StringExpression("format-number(" + number.script + ", " + format.script + ")");
+        String formatString = format.isLiteral ? this.translatorOptions.getDecimalFormat().adaptFormatString(format.script) : format.script;
+        return new StringExpression("format-number(" + number.script + ", " + formatString + ")");
   }
 
   @Override
   public StringExpression getStringLiteralFromUnquotedString(String value) {
-    return new StringExpression("'" + value + "'");
+    return new StringExpression("'" + value + "'", true);
   }
 
 
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java
index d188f433..dad29bff 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionCombinedTest.java
@@ -2,18 +2,22 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import org.junit.jupiter.api.Test;
+
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 import eu.europa.ted.efx.mock.DependencyFactoryMock;
+import eu.europa.ted.efx.model.DecimalFormat;
 
 /**
  * Test for EFX expressions that combine several aspects of the language.
  */
 class EfxExpressionCombinedTest {
   final private String SDK_VERSION = "eforms-sdk-1.0";
+  final private TranslatorOptions TRANSLATOR_OPTIONS = new EfxTranslatorOptions(DecimalFormat.EFX_DEFAULT);
 
   private String test(final String context, final String expression) {
     try {
       return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE,
-          SDK_VERSION, String.format("{%s} ${%s}", context, expression));
+          SDK_VERSION, String.format("{%s} ${%s}", context, expression), TRANSLATOR_OPTIONS);
     } catch (InstantiationException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index bd2e16d5..68548e44 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -4,10 +4,14 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.antlr.v4.runtime.misc.ParseCancellationException;
 import org.junit.jupiter.api.Test;
+
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 import eu.europa.ted.efx.mock.DependencyFactoryMock;
+import eu.europa.ted.efx.model.DecimalFormat;
 
 class EfxExpressionTranslatorTest {
   final private String SDK_VERSION = "eforms-sdk-1.0";
+  final private TranslatorOptions TRANSLATOR_OPTIONS = new EfxTranslatorOptions(DecimalFormat.EFX_DEFAULT);
 
   private String test(final String context, final String expression) {
     return test1(String.format("{%s} ${%s}", context, expression));
@@ -16,7 +20,7 @@ private String test(final String context, final String expression) {
   private String test1(final String expression, final String... params) {
     try {
       return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, SDK_VERSION, 
-          expression, params);
+          expression, TRANSLATOR_OPTIONS, params);
     } catch (InstantiationException e) {
       throw new RuntimeException(e);
     }
@@ -464,7 +468,7 @@ void testStringsFromStringIteration_UsingLiterals() {
 
   @Test
   void testStringsSequenceFromIteration_UsingMultipleIterators() {
-    assertEquals("'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0.##########'), 'text'))",
+    assertEquals("'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0,##########'), 'text'))",
         test("ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"));
   }
   
@@ -1147,7 +1151,7 @@ void testSubstringFunction() {
 
   @Test
   void testToStringFunction() {
-    assertEquals("format-number(123, '0.##########')", test("ND-Root", "string(123)"));
+    assertEquals("format-number(123, '0,##########')", test("ND-Root", "string(123)"));
   }
 
   @Test
@@ -1157,7 +1161,7 @@ void testConcatFunction() {
 
   @Test
   void testFormatNumberFunction() {
-    assertEquals("format-number(PathNode/NumberField/number(), '#,##0.00')",
+    assertEquals("format-number(PathNode/NumberField/number(), '# ##0,00')",
         test("ND-Root", "format-number(BT-00-Number, '#,##0.00')"));
   }
 
diff --git a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
index c12cb512..8f23e902 100644
--- a/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxTemplateTranslatorTest.java
@@ -4,15 +4,19 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.antlr.v4.runtime.misc.ParseCancellationException;
 import org.junit.jupiter.api.Test;
+
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 import eu.europa.ted.efx.mock.DependencyFactoryMock;
+import eu.europa.ted.efx.model.DecimalFormat;
 
 class EfxTemplateTranslatorTest {
   final private String SDK_VERSION = "eforms-sdk-1.0";
+  final private TranslatorOptions TRANSLATOR_OPTIONS = new EfxTranslatorOptions(DecimalFormat.EFX_DEFAULT);
 
   private String translate(final String template) {
     try {
       return EfxTranslator.translateTemplate(DependencyFactoryMock.INSTANCE,
-          SDK_VERSION, template + "\n");
+          SDK_VERSION, template + "\n", TRANSLATOR_OPTIONS);
     } catch (InstantiationException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java
index 036ac8b2..75d1f3cf 100644
--- a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java
+++ b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java
@@ -1,11 +1,13 @@
 package eu.europa.ted.efx.mock;
 
 import org.antlr.v4.runtime.BaseErrorListener;
+
 import eu.europa.ted.efx.exceptions.ThrowingErrorListener;
 import eu.europa.ted.efx.interfaces.MarkupGenerator;
 import eu.europa.ted.efx.interfaces.ScriptGenerator;
 import eu.europa.ted.efx.interfaces.SymbolResolver;
 import eu.europa.ted.efx.interfaces.TranslatorDependencyFactory;
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
 import eu.europa.ted.efx.xpath.XPathScriptGenerator;
 
 /**
@@ -26,15 +28,15 @@ public SymbolResolver createSymbolResolver(String sdkVersion) {
   }
 
   @Override
-  public ScriptGenerator createScriptGenerator(String sdkVersion) {
+  public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options) {
     if (scriptGenerator == null) {
-      this.scriptGenerator = new XPathScriptGenerator();
+      this.scriptGenerator = new XPathScriptGenerator(options);
     }
     return this.scriptGenerator;
   }
 
   @Override
-  public MarkupGenerator createMarkupGenerator(String sdkVersion) {
+  public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options) {
     if (this.markupGenerator == null) {
       this.markupGenerator = new MarkupGeneratorMock();
     }

From 011f6b7d8c7c8ce04400981afa76a42d00bc385e Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Fri, 21 Apr 2023 19:26:40 +0200
Subject: [PATCH 10/39] [workflows]: Added Github workflow for building on
 push. Changed triggering of publish to happen only when pushing to
 main/develop.

---
 .github/workflows/build.yml   | 21 +++++++++++++++++++++
 .github/workflows/publish.yml |  1 +
 2 files changed, 22 insertions(+)
 create mode 100644 .github/workflows/build.yml

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..1ea3c903
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,21 @@
+name: Build the project
+on:
+  push:
+
+  # Allows to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-java@v3
+        with:
+          java-version: '11'
+          distribution: 'adopt'
+      - name: Build package
+        run: mvn --batch-mode clean install
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index f56a6a71..7752c8ab 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - 'develop'
+      - 'main'
 
   release:
     types: [created]

From 287b5df567ed417e924d5b88fde687b4b0f0db70 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Thu, 4 May 2023 11:51:05 +0200
Subject: [PATCH 11/39] [dependencies]: Set version of eforms-core-java to
 1.0.4-SNAPSHOT.

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 61ab9a1c..6ea4dbdf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
     <sdk.antlr4.dir>${project.build.directory}/eforms-sdk/antlr4</sdk.antlr4.dir>
 
     <!-- Versions - eForms -->
-    <version.eforms-core>1.0.2-SNAPSHOT</version.eforms-core>
+    <version.eforms-core>1.0.4-SNAPSHOT</version.eforms-core>
 
     <!-- Versions - Third-party libraries -->
     <version.antlr4>4.9.3</version.antlr4>

From 532c47bc6e458ad44fca202cd089ff3240f67298 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Thu, 4 May 2023 14:34:05 +0200
Subject: [PATCH 12/39] [POM]: Disabled debug information for releases.

---
 pom.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pom.xml b/pom.xml
index 6ea4dbdf..e47e45c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -356,6 +356,9 @@
     <profile>
       <!-- Profile "release" caters to the requirements for releasing to Maven Central -->
       <id>release</id>
+      <properties>
+        <maven.compiler.debug>false</maven.compiler.debug>
+      </properties>
       <build>
         <plugins>
           <plugin>

From 5dc157d1f1edef13f5289ce10681ea6fb363c223 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Sun, 7 May 2023 20:52:50 +0200
Subject: [PATCH 13/39] Added linguistic info to TranslatorOptions.

---
 .../europa/ted/efx/EfxTranslatorOptions.java  | 55 ++++++++++++++++++-
 .../ted/efx/interfaces/TranslatorOptions.java |  8 +++
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
index 61aafbf1..635a1ee7 100644
--- a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
+++ b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
@@ -1,5 +1,10 @@
 package eu.europa.ted.efx;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
 import eu.europa.ted.efx.interfaces.TranslatorOptions;
 import eu.europa.ted.efx.model.DecimalFormat;
 
@@ -7,16 +12,64 @@ public class EfxTranslatorOptions implements TranslatorOptions {
 
     // Change to EfxDecimalFormatSymbols.EFX_DEFAULT to use the decimal format
     // preferred by OP (space as thousands separator and comma as decimal separator).
-    public static final EfxTranslatorOptions DEFAULT = new EfxTranslatorOptions(DecimalFormat.XSL_DEFAULT);
+    public static final EfxTranslatorOptions DEFAULT = new EfxTranslatorOptions(DecimalFormat.XSL_DEFAULT, Locale.ENGLISH);
 
     private final DecimalFormat symbols;
+    private Locale primaryLocale;
+    private ArrayList<Locale> otherLocales;
 
     public EfxTranslatorOptions(DecimalFormat symbols) {
+        this(symbols, Locale.ENGLISH);
+    }
+
+    public EfxTranslatorOptions(DecimalFormat symbols, String primaryLanguage, String... otherLanguages) {
+        this(symbols, Locale.forLanguageTag(primaryLanguage), Arrays.stream(otherLanguages).map(Locale::forLanguageTag).toArray(Locale[]::new));
+    }
+
+    public EfxTranslatorOptions(DecimalFormat symbols, Locale primaryLocale, Locale... otherLocales) {
         this.symbols = symbols;
+        this.primaryLocale = primaryLocale;
+        this.otherLocales = new ArrayList<>(Arrays.asList(otherLocales));
     }
+    
 
     @Override
     public DecimalFormat getDecimalFormat() {
         return this.symbols;
     }
+
+    @Override
+    public String getPrimaryLanguage2LetterCode() {
+        return this.primaryLocale.getLanguage();
+    }
+
+    @Override
+    public String getPrimaryLanguage3LetterCode() {
+        return this.primaryLocale.getISO3Language();
+    }
+
+    @Override
+    public String[] getAllLanguage2LetterCodes() {
+        List<String> languages = new ArrayList<>();
+        languages.add(primaryLocale.getLanguage());
+        for (Locale locale : otherLocales) {
+            languages.add(locale.getLanguage());
+        }
+        return languages.toArray(new String[0]);
+    }
+    
+    @Override
+    public String[] getAllLanguage3LetterCodes() {
+        List<String> languages = new ArrayList<>();
+        languages.add(primaryLocale.getLanguage());
+        for (Locale locale : otherLocales) {
+            languages.add(locale.getISO3Language());
+        }
+        return languages.toArray(new String[0]);
+    }
+
+    public EfxTranslatorOptions withLanguage(String language) {
+        this.primaryLocale = Locale.forLanguageTag(language);
+        return this;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java
index b0074b52..76540b3c 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorOptions.java
@@ -4,4 +4,12 @@
 
 public interface TranslatorOptions {
     public DecimalFormat getDecimalFormat();
+
+    public String getPrimaryLanguage2LetterCode();
+
+    public String getPrimaryLanguage3LetterCode();
+
+    public String[] getAllLanguage2LetterCodes();
+
+    public String[] getAllLanguage3LetterCodes();
 }

From 6c367b0b5f57f5b9d9abd9360c88144713cdf815 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Mon, 8 May 2023 01:41:08 +0200
Subject: [PATCH 14/39] Added primary language filtering for multilingual text
 fields.

---
 .../eu/europa/ted/efx/model/Expression.java   | 22 +++++++++++++++++--
 .../efx/sdk1/EfxExpressionTranslatorV1.java   |  2 +-
 .../ted/efx/xpath/XPathScriptGenerator.java   |  6 +++++
 .../ted/efx/EfxExpressionTranslatorTest.java  |  2 +-
 4 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java
index a3afed04..5c135c1b 100644
--- a/src/main/java/eu/europa/ted/efx/model/Expression.java
+++ b/src/main/java/eu/europa/ted/efx/model/Expression.java
@@ -25,7 +25,7 @@ public class Expression extends CallStackObject {
       Map.ofEntries(entry("id", StringExpression.class), //
           entry("id-ref", StringExpression.class), //
           entry("text", StringExpression.class), //
-          entry("text-multilingual", StringExpression.class), //
+          entry("text-multilingual", MultilingualStringExpression.class), //
           entry("indicator", BooleanExpression.class), //
           entry("amount", NumericExpression.class), //
           entry("number", NumericExpression.class), //
@@ -47,7 +47,7 @@ public class Expression extends CallStackObject {
       entry("id", StringListExpression.class), //
       entry("id-ref", StringListExpression.class), //
       entry("text", StringListExpression.class), //
-      entry("text-multilingual", StringListExpression.class), //
+      entry("text-multilingual", MultilingualStringListExpression.class), //
       entry("indicator", BooleanListExpression.class), //
       entry("amount", NumericListExpression.class), //
       entry("number", NumericListExpression.class), //
@@ -167,6 +167,17 @@ public StringExpression(final String script, final Boolean isLiteral) {
     }
   }
 
+  public static class MultilingualStringExpression extends StringExpression {
+
+    public MultilingualStringExpression(final String script) {
+      super(script);
+    }
+
+    public MultilingualStringExpression(final String script, final Boolean isLiteral) {
+      super(script, isLiteral);
+    }
+  }
+
   /**
    * Represents a date expression or value in the target language.
    */
@@ -229,6 +240,13 @@ public StringListExpression(final String script) {
     }
   }
 
+  public static class MultilingualStringListExpression extends ListExpression<MultilingualStringExpression> {
+
+    public MultilingualStringListExpression(final String script) {
+      super(script);
+    }
+  }
+
   /**
    * Used to represent a list of numbers in the target language.
    */
diff --git a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
index 5c5abbad..ca6dbcc8 100644
--- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
+++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
@@ -290,7 +290,7 @@ public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) {
   public void exitFieldValueComparison(FieldValueComparisonContext ctx) {
     Expression right = this.stack.pop(Expression.class);
     Expression left = this.stack.pop(Expression.class);
-    if (!left.getClass().equals(right.getClass())) {
+    if (!left.getClass().isAssignableFrom(right.getClass()) && !right.getClass().isAssignableFrom(left.getClass())) {
       throw new ParseCancellationException(TYPE_MISMATCH_CANNOT_COMPARE_VALUES_OF_DIFFERENT_TYPES
           + left.getClass() + " and " + right.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 e987d81b..4effe2c4 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -25,6 +25,8 @@
 import eu.europa.ted.efx.model.Expression.IteratorExpression;
 import eu.europa.ted.efx.model.Expression.IteratorListExpression;
 import eu.europa.ted.efx.model.Expression.ListExpression;
+import eu.europa.ted.efx.model.Expression.MultilingualStringExpression;
+import eu.europa.ted.efx.model.Expression.MultilingualStringListExpression;
 import eu.europa.ted.efx.model.Expression.NumericExpression;
 import eu.europa.ted.efx.model.Expression.NumericListExpression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
@@ -79,6 +81,10 @@ public <T extends Expression> T composeFieldReferenceWithAxis(final PathExpressi
   public <T extends Expression> T composeFieldValueReference(PathExpression fieldReference,
       Class<T> type) {
 
+    if (MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) {
+      PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID='" + this.translatorOptions.getPrimaryLanguage3LetterCode() + "'");
+      return Expression.instantiate(languageSpecific.script + "/normalize-space(text())", type);
+    }
     if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate(fieldReference.script + "/normalize-space(text())", type);
     }
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index 68548e44..fa487881 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -97,7 +97,7 @@ void testLikePatternCondition_WithNot() {
   @Test
   void testFieldValueComparison_UsingTextFields() {
     assertEquals(
-        "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField/normalize-space(text())",
+        "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField[@languageID='eng']/normalize-space(text())",
         test("ND-Root", "BT-00-Text == BT-00-Text-Multilingual"));
   }
 

From fd72bf2cfd7a3a2d879a64bc0583c69eff9120c1 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Thu, 11 May 2023 14:03:49 +0200
Subject: [PATCH 15/39] [dependencies]: Set version of eforms-core-java to
 1.0.5-SNAPSHOT.

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index e47e45c0..31ae2afa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
     <sdk.antlr4.dir>${project.build.directory}/eforms-sdk/antlr4</sdk.antlr4.dir>
 
     <!-- Versions - eForms -->
-    <version.eforms-core>1.0.4-SNAPSHOT</version.eforms-core>
+    <version.eforms-core>1.0.5-SNAPSHOT</version.eforms-core>
 
     <!-- Versions - Third-party libraries -->
     <version.antlr4>4.9.3</version.antlr4>

From 9f8b5f0fe68a348343be8390c8a8cecd2344f564 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Wed, 17 May 2023 14:03:54 +0200
Subject: [PATCH 16/39] [dependencies]: Set version of eforms-core-java to
 1.0.5-SNAPSHOT.

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index e47e45c0..31ae2afa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
     <sdk.antlr4.dir>${project.build.directory}/eforms-sdk/antlr4</sdk.antlr4.dir>
 
     <!-- Versions - eForms -->
-    <version.eforms-core>1.0.4-SNAPSHOT</version.eforms-core>
+    <version.eforms-core>1.0.5-SNAPSHOT</version.eforms-core>
 
     <!-- Versions - Third-party libraries -->
     <version.antlr4>4.9.3</version.antlr4>

From ec7f5b6d133478a89bb77dc661cd8237b2a0f0c4 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Wed, 17 May 2023 14:12:21 +0200
Subject: [PATCH 17/39] [LocaleHelper]: Imported class LocaleHelper from
 project "eforms-notice-viewer".

---
 .../eu/europa/ted/efx/util/LocaleHelper.java  | 34 +++++++++++++++++++
 .../europa/ted/efx/util/LocaleHelperTest.java | 15 ++++++++
 2 files changed, 49 insertions(+)
 create mode 100644 src/main/java/eu/europa/ted/efx/util/LocaleHelper.java
 create mode 100644 src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java

diff --git a/src/main/java/eu/europa/ted/efx/util/LocaleHelper.java b/src/main/java/eu/europa/ted/efx/util/LocaleHelper.java
new file mode 100644
index 00000000..3bd58581
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/util/LocaleHelper.java
@@ -0,0 +1,34 @@
+package eu.europa.ted.efx.util;
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Locale;
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Utility methods for working with locales
+ */
+public class LocaleHelper {
+  private LocaleHelper() {}
+
+  /**
+   * /** Converts a 2-characters or 3-characters ISO 639 language code into a Locale.
+   *
+   * @param iso639LanguageCode The languace code to be converted
+   * @return A {@link Locale} instance
+   */
+  public static Locale getLocale(final String iso639LanguageCode) {
+    Validate.notBlank(iso639LanguageCode, "Undefined language code");
+
+    final String languageCode = iso639LanguageCode.toLowerCase();
+
+    return Arrays.asList(Locale.getISOLanguages()).stream()
+        .map(Locale::new)
+        .filter(
+            (Locale locale) -> locale.getISO3Language().equals(languageCode)
+                || locale.getLanguage().equals(languageCode))
+        .findFirst()
+        .orElseThrow(() -> new IllegalArgumentException(
+            MessageFormat.format("[{0}] is not a valid ISO 639  code", languageCode)));
+  }
+}
diff --git a/src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java b/src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java
new file mode 100644
index 00000000..e8917ebf
--- /dev/null
+++ b/src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java
@@ -0,0 +1,15 @@
+package eu.europa.ted.efx.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.util.Locale;
+import org.junit.jupiter.api.Test;
+
+class LocaleHelperTest {
+  @Test
+  void testLocaleParsing() {
+    assertEquals(Locale.ENGLISH, LocaleHelper.getLocale("en"));
+    assertEquals(Locale.ENGLISH, LocaleHelper.getLocale("ENG"));
+    assertEquals(Locale.FRENCH, LocaleHelper.getLocale("FR"));
+    assertEquals(Locale.FRENCH, LocaleHelper.getLocale("FRA"));
+  }
+}

From 697984de9d356aebb9fd05f7dc9b160452576e54 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Tue, 23 May 2023 22:38:16 +0200
Subject: [PATCH 18/39] Added automatic language selection for multilingual
 text references.

---
 src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java   | 2 +-
 .../java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java  | 6 ++++--
 .../java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java | 4 ++--
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
index 635a1ee7..26e1f376 100644
--- a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
+++ b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
@@ -61,7 +61,7 @@ public String[] getAllLanguage2LetterCodes() {
     @Override
     public String[] getAllLanguage3LetterCodes() {
         List<String> languages = new ArrayList<>();
-        languages.add(primaryLocale.getLanguage());
+        languages.add(primaryLocale.getISO3Language());
         for (Locale locale : otherLocales) {
             languages.add(locale.getISO3Language());
         }
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 4effe2c4..924cda97 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -82,8 +82,10 @@ public <T extends Expression> T composeFieldValueReference(PathExpression fieldR
       Class<T> type) {
 
     if (MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) {
-      PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID='" + this.translatorOptions.getPrimaryLanguage3LetterCode() + "'");
-      return Expression.instantiate(languageSpecific.script + "/normalize-space(text())", type);
+      String languages = "('" + String.join("','", this.translatorOptions.getAllLanguage3LetterCodes()) + "')";
+      PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__");
+      String script = "(for $__LANG__ in " + languages + " return " + languageSpecific.script + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]";
+      return Expression.instantiate(script, type);
     }
     if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate(fieldReference.script + "/normalize-space(text())", type);
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index fa487881..6292158c 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -97,8 +97,8 @@ void testLikePatternCondition_WithNot() {
   @Test
   void testFieldValueComparison_UsingTextFields() {
     assertEquals(
-        "PathNode/TextField/normalize-space(text()) = PathNode/TextMultilingualField[@languageID='eng']/normalize-space(text())",
-        test("ND-Root", "BT-00-Text == BT-00-Text-Multilingual"));
+        "(for $__LANG__ in ('eng') return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]",
+        test("ND-Root", "BT-00-Text-Multilingual"));
   }
 
   @Test

From 737ebb5eb166f125afde8414875018c8ca2f1e0f Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Sat, 27 May 2023 10:24:02 +0200
Subject: [PATCH 19/39] Remove unused helper class

---
 .../eu/europa/ted/efx/util/LocaleHelper.java  | 34 -------------------
 .../europa/ted/efx/util/LocaleHelperTest.java | 15 --------
 2 files changed, 49 deletions(-)
 delete mode 100644 src/main/java/eu/europa/ted/efx/util/LocaleHelper.java
 delete mode 100644 src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java

diff --git a/src/main/java/eu/europa/ted/efx/util/LocaleHelper.java b/src/main/java/eu/europa/ted/efx/util/LocaleHelper.java
deleted file mode 100644
index 3bd58581..00000000
--- a/src/main/java/eu/europa/ted/efx/util/LocaleHelper.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package eu.europa.ted.efx.util;
-
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Locale;
-import org.apache.commons.lang3.Validate;
-
-/**
- * Utility methods for working with locales
- */
-public class LocaleHelper {
-  private LocaleHelper() {}
-
-  /**
-   * /** Converts a 2-characters or 3-characters ISO 639 language code into a Locale.
-   *
-   * @param iso639LanguageCode The languace code to be converted
-   * @return A {@link Locale} instance
-   */
-  public static Locale getLocale(final String iso639LanguageCode) {
-    Validate.notBlank(iso639LanguageCode, "Undefined language code");
-
-    final String languageCode = iso639LanguageCode.toLowerCase();
-
-    return Arrays.asList(Locale.getISOLanguages()).stream()
-        .map(Locale::new)
-        .filter(
-            (Locale locale) -> locale.getISO3Language().equals(languageCode)
-                || locale.getLanguage().equals(languageCode))
-        .findFirst()
-        .orElseThrow(() -> new IllegalArgumentException(
-            MessageFormat.format("[{0}] is not a valid ISO 639  code", languageCode)));
-  }
-}
diff --git a/src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java b/src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java
deleted file mode 100644
index e8917ebf..00000000
--- a/src/test/java/eu/europa/ted/efx/util/LocaleHelperTest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package eu.europa.ted.efx.util;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import java.util.Locale;
-import org.junit.jupiter.api.Test;
-
-class LocaleHelperTest {
-  @Test
-  void testLocaleParsing() {
-    assertEquals(Locale.ENGLISH, LocaleHelper.getLocale("en"));
-    assertEquals(Locale.ENGLISH, LocaleHelper.getLocale("ENG"));
-    assertEquals(Locale.FRENCH, LocaleHelper.getLocale("FR"));
-    assertEquals(Locale.FRENCH, LocaleHelper.getLocale("FRA"));
-  }
-}

From a686bd10c44ad81ead8d6cbffa67bbc4bd31fbbd Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Sat, 27 May 2023 10:28:15 +0200
Subject: [PATCH 20/39] Fixed a bug in EfxTranslatorOptions

---
 src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
index 635a1ee7..26e1f376 100644
--- a/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
+++ b/src/main/java/eu/europa/ted/efx/EfxTranslatorOptions.java
@@ -61,7 +61,7 @@ public String[] getAllLanguage2LetterCodes() {
     @Override
     public String[] getAllLanguage3LetterCodes() {
         List<String> languages = new ArrayList<>();
-        languages.add(primaryLocale.getLanguage());
+        languages.add(primaryLocale.getISO3Language());
         for (Locale locale : otherLocales) {
             languages.add(locale.getISO3Language());
         }

From a2441252dd6d09e9916077c2491bd48c7937c5d2 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Mon, 29 May 2023 00:16:16 +0200
Subject: [PATCH 21/39] Updated the changelog.

---
 CHANGELOG.md | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 683aea97..d03e4e8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,14 +1,13 @@
-# EFX Toolkit 1.2.0 Release Notes
+# EFX Toolkit 1.3.0 Release Notes
 
 _The EFX Toolkit for Java developers is a library that enables the transpilation of [EFX](https://docs.ted.europa.eu/eforms/latest/efx) expressions and templates to different target languages. It also includes an implementation of an EFX-to-XPath transpiler._
 
 ---
 ## In this release:
 
-- We fixed a bug in the `XPathScriptGenerator` that was causing references to fields of type `measure` (duration) to throw an exception when multiple values where matched by the reference. 
-- We fixed an issue in the `SdkSymbolResolver` that was causing some code labels to be resolved incorrectly. The `SdkSymbolResolver` now correctly looks for the root codelist associated with a field in the codelist metadata provided in the `codelists` folder, instead of relying on the codelist constraint metadata provided in `fields.json`.   
- :warning: _**CAUTION:** If you have implemented your own `SymbolResolver` make sure that your implementation of `getRootCodelistOfField` retrieves the parent codelist information from `codelists/codelists.json` or directly from the `.gc` files in the `codelists` folder of the eForms SDK._
-- We refactored the code to move to the [eForms Core Java](https://github.com/OP-TED/eforms-core-java) library some common entity classes that were not specific to EFX (`SdkEntityFactory`, `SdkField`, `SdkNode`, `SdkCodelist`). We also moved into the EFX Toolkit some reusable classes (`SdkSymbolResolver`, `ComponentFactory`) from the [eForms Notice Viewer](https://github.com/OP-TED/eforms-notice-viewer) sample application. The result of this refactoring is `efx-toolkit-java`-`1.2.0`, `eforms-core-java`-`1.0.0` and `eforms-notice-viewer`-`0.6.0`.  
+- Updated the XPath 2.0 parse, XPathContextualizer and XPathScriptGenerator to correctly translate sequences. 
+- Improved numeric formatting. The EfxTranslator API now includes overloaded methods that permit control of numeric formatting. The existing API has been preserved.
+- Improved handling of multilingual text fields by adding automatic selection of the visualisation language .  
 ---
 
 You can download the latest EFX Toolkit from Maven Central.  
@@ -23,4 +22,4 @@ This version of the EFX Toolkit has a compile-time dependency on the following v
 - eForms SDK 0.7.x
 - eForms SDK 1.x.x
 
-It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.0.0.
+It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.0.5.

From 80f78242d2bdc2ebbe80e643d5a6b3f09597db56 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Tue, 30 May 2023 10:44:20 +0200
Subject: [PATCH 22/39] [build]: Enabled reproducible builds.

---
 pom.xml | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/pom.xml b/pom.xml
index 31ae2afa..560c1e79 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.build.outputTimestamp>2023-05-30T06:25:09Z</project.build.outputTimestamp>
 
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.target>11</maven.compiler.target>
@@ -72,6 +73,7 @@
     <version.dependency.plugin>3.3.0</version.dependency.plugin>
     <version.install.plugin>2.5.2</version.install.plugin>
     <version.jacoco.plugin>0.8.8</version.jacoco.plugin>
+    <version.jar.plugin>3.2.0</version.jar.plugin>
     <version.javadoc.plugin>3.4.0</version.javadoc.plugin>
     <version.pgp.plugin>1.5</version.pgp.plugin>
     <version.source.plugin>3.2.1</version.source.plugin>
@@ -208,6 +210,11 @@
           <artifactId>jacoco-maven-plugin</artifactId>
           <version>${version.jacoco.plugin}</version>
         </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>${version.jar.plugin}</version>
+        </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-javadoc-plugin</artifactId>

From 152a0f25c4acb24cedfa74c0352ab42d1a131db7 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Tue, 30 May 2023 10:47:33 +0200
Subject: [PATCH 23/39] [POM]: Use variable for the Sonatype repository URL.

---
 pom.xml | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 560c1e79..e6e27cea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,11 +39,11 @@
   <distributionManagement>
     <snapshotRepository>
       <id>ossrh</id>
-      <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
+      <url>https://${sonatype.server.url}/content/repositories/snapshots</url>
     </snapshotRepository>
     <repository>
       <id>ossrh</id>
-      <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+      <url>https://${sonatype.server.url}/service/local/staging/deploy/maven2/</url>
     </repository>
   </distributionManagement>
 
@@ -51,6 +51,8 @@
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.build.outputTimestamp>2023-05-30T06:25:09Z</project.build.outputTimestamp>
 
+    <sonatype.server.url>s01.oss.sonatype.org</sonatype.server.url>
+
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.target>11</maven.compiler.target>
 

From efb4024d387ded699142104c2e7902f30ccce90e Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Tue, 30 May 2023 10:51:37 +0200
Subject: [PATCH 24/39] [release]: Use nexus-staging-maven-plugin to automate
 releasing to Maven Central.

---
 pom.xml | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/pom.xml b/pom.xml
index e6e27cea..ada92797 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,6 +78,7 @@
     <version.jar.plugin>3.2.0</version.jar.plugin>
     <version.javadoc.plugin>3.4.0</version.javadoc.plugin>
     <version.pgp.plugin>1.5</version.pgp.plugin>
+    <version.nexus-staging.plugin>1.6.7</version.nexus-staging.plugin>
     <version.source.plugin>3.2.1</version.source.plugin>
     <version.surefire.plugin>3.0.0-M7</version.surefire.plugin> <!-- Versions prior to 3.0.x do not pick up Junit 5 tests correctly. -->
   </properties>
@@ -232,6 +233,11 @@
           <artifactId>maven-source-plugin</artifactId>
           <version>${version.source.plugin}</version>
         </plugin>
+        <plugin>
+          <groupId>org.sonatype.plugins</groupId>
+          <artifactId>nexus-staging-maven-plugin</artifactId>
+          <version>${version.nexus-staging.plugin}</version>
+        </plugin>
       </plugins>
     </pluginManagement>
 
@@ -414,6 +420,16 @@
               </gpgArguments>
             </configuration>
           </plugin>
+          <plugin>
+            <groupId>org.sonatype.plugins</groupId>
+            <artifactId>nexus-staging-maven-plugin</artifactId>
+            <extensions>true</extensions>
+            <configuration>
+              <serverId>ossrh</serverId>
+              <nexusUrl>https://${sonatype.server.url}/</nexusUrl>
+              <autoReleaseAfterClose>true</autoReleaseAfterClose>
+            </configuration>
+          </plugin>
         </plugins>
       </build>
     </profile>

From 2cf0209f887470a40ffafd53cc0fcd97f6cbe020 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Tue, 30 May 2023 10:54:04 +0200
Subject: [PATCH 25/39] [POM]: Removed "repositories" section, instead relying
 on Maven configuration (settings.xml).

---
 .github/workflows/build.yml    |  2 +-
 .github/workflows/publish.yml  |  2 +-
 .github/workflows/settings.xml | 26 ++++++++++++++++++++++++++
 pom.xml                        | 14 --------------
 4 files changed, 28 insertions(+), 16 deletions(-)
 create mode 100644 .github/workflows/settings.xml

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1ea3c903..098a49a0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -18,4 +18,4 @@ jobs:
           java-version: '11'
           distribution: 'adopt'
       - name: Build package
-        run: mvn --batch-mode clean install
+        run: mvn --batch-mode clean install -s .github/workflows/settings.xml
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 7752c8ab..f1359d1b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -34,7 +34,7 @@ jobs:
           server-username: MAVEN_USERNAME
           server-password: MAVEN_PASSWORD
       - name: Publish to the Maven Central Repository
-        run: mvn --batch-mode deploy -Dgpg.passphrase='${{ secrets.GPG_PASSPHRASE }}' -Prelease
+        run: mvn --batch-mode deploy -Dgpg.passphrase='${{ secrets.GPG_PASSPHRASE }}' -Prelease -s .github/workflows/settings.xml
         env:
           MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
           MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
diff --git a/.github/workflows/settings.xml b/.github/workflows/settings.xml
new file mode 100644
index 00000000..d1ca0b35
--- /dev/null
+++ b/.github/workflows/settings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
+  <profiles>
+    <profile>
+      <id>repositories</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <repositories>
+        <repository>
+          <id>ossrh</id>
+          <name>OSSRH Snapshots</name>
+          <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
+          <releases>
+            <enabled>false</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+</settings>
diff --git a/pom.xml b/pom.xml
index ada92797..b3cc553d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -170,20 +170,6 @@
     </dependency>
   </dependencies>
 
-  <repositories>
-    <repository>
-      <id>oss-snapshots</id>
-      <name>OSS Snapshots repository</name>
-      <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
-      <releases>
-        <enabled>false</enabled>
-      </releases>
-      <snapshots>
-        <enabled>true</enabled>
-      </snapshots>
-    </repository>
-  </repositories>
-
   <build>
     <pluginManagement>
       <plugins>

From e14b13b40054226fec711aab3d57b32851a02f56 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Tue, 30 May 2023 11:15:22 +0200
Subject: [PATCH 26/39] [README]: Updated README.md.

---
 README.md | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/README.md b/README.md
index db62b12b..d3ff56db 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,32 @@ You can build this project as usual using Maven.
 
 The build process uses the grammar files provided in the [eForms SDK](https://github.com/OP-TED/eForms-SDK/tree/develop/efx-grammar) to generate a parser, using [ANTLR4](https://www.antlr.org).
 
+In order to be able to use snapshot versions of dependencies, the following should be added to the "profiles" section of the Maven configuration file "settings.xml" (normally under ${HOME}/.m2):
+
+```
+<profile>
+  <id>repositories</id>
+  <activation>
+    <activeByDefault>true</activeByDefault>
+  </activation>
+  <repositories>
+    <repository>
+      <id>ossrh</id>
+      <name>OSSRH Snapshots</name>
+      <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
+      <releases>
+        <enabled>false</enabled>
+      </releases>
+      <snapshots>
+        <enabled>true</enabled>
+      </snapshots>
+    </repository>
+  </repositories>
+</profile>
+```
+
+See ".github/workflows/settings.xml".
+
 ## Testing
 
 Unit tests are available under `src/test/java/`. They show in particular a variety of EFX expressions and the corresponding XPath expression.

From 2aaa951ada89d28659debaf6b81be51845a51a18 Mon Sep 17 00:00:00 2001
From: Evangelos MELETIOU <Evangelos.MELETIOU@ext.publications.europa.eu>
Date: Tue, 30 May 2023 15:47:12 +0200
Subject: [PATCH 27/39] [deployment]: Fixed settings of credentials on
 deployment workflow.

---
 .github/workflows/publish.yml  | 3 ---
 .github/workflows/settings.xml | 8 ++++++++
 README.md                      | 8 ++++++++
 3 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index f1359d1b..6b11035a 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -30,9 +30,6 @@ jobs:
         with:
           java-version: '11'
           distribution: 'adopt'
-          server-id: ossrh
-          server-username: MAVEN_USERNAME
-          server-password: MAVEN_PASSWORD
       - name: Publish to the Maven Central Repository
         run: mvn --batch-mode deploy -Dgpg.passphrase='${{ secrets.GPG_PASSPHRASE }}' -Prelease -s .github/workflows/settings.xml
         env:
diff --git a/.github/workflows/settings.xml b/.github/workflows/settings.xml
index d1ca0b35..a36dfa7d 100644
--- a/.github/workflows/settings.xml
+++ b/.github/workflows/settings.xml
@@ -2,6 +2,14 @@
 <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
+  <servers>
+    <server>
+      <id>ossrh</id>
+      <username>${env.MAVEN_USERNAME}</username>
+      <password>${env.MAVEN_PASSWORD}</password>
+    </server>
+  </servers>
+
   <profiles>
     <profile>
       <id>repositories</id>
diff --git a/README.md b/README.md
index d3ff56db..7e3da5c4 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,14 @@ The build process uses the grammar files provided in the [eForms SDK](https://gi
 In order to be able to use snapshot versions of dependencies, the following should be added to the "profiles" section of the Maven configuration file "settings.xml" (normally under ${HOME}/.m2):
 
 ```
+<servers>
+  <server>
+    <id>ossrh</id>
+    <username>${env.MAVEN_USERNAME}</username>
+    <password>${env.MAVEN_PASSWORD}</password>
+  </server>
+</servers>
+
 <profile>
   <id>repositories</id>
   <activation>

From 2f8914aab940189eba88f3957d41c7ea0cf165cf Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Wed, 31 May 2023 01:08:45 +0200
Subject: [PATCH 28/39] Fixed an issue with MultilingualStringListExpression
 class hierarchy.

---
 src/main/java/eu/europa/ted/efx/model/Expression.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/eu/europa/ted/efx/model/Expression.java b/src/main/java/eu/europa/ted/efx/model/Expression.java
index 5c135c1b..2fd1f569 100644
--- a/src/main/java/eu/europa/ted/efx/model/Expression.java
+++ b/src/main/java/eu/europa/ted/efx/model/Expression.java
@@ -240,7 +240,7 @@ public StringListExpression(final String script) {
     }
   }
 
-  public static class MultilingualStringListExpression extends ListExpression<MultilingualStringExpression> {
+  public static class MultilingualStringListExpression extends StringListExpression {
 
     public MultilingualStringListExpression(final String script) {
       super(script);

From 66316e505873859c7b76b5a3f3837390a8c959f6 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Tue, 13 Jun 2023 00:45:39 +0200
Subject: [PATCH 29/39] Carried over from efx-2, the fix for multilingual text
 field rendering.

---
 .../eu/europa/ted/efx/xpath/XPathContextualizer.java     | 8 ++++++++
 .../eu/europa/ted/efx/xpath/XPathScriptGenerator.java    | 9 +++++----
 .../eu/europa/ted/efx/EfxExpressionTranslatorTest.java   | 2 +-
 3 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
index f3cffa4c..d7d0bf35 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
@@ -81,6 +81,14 @@ public static PathExpression contextualize(final PathExpression contextXpath,
     return getContextualizedXpath(contextSteps, pathSteps);
   }
 
+  public static boolean hasPredicate(final PathExpression xpath, String match) {
+    return hasPredicate(xpath.script, match);
+  }
+  
+  public static boolean hasPredicate(final String xpath, String match) {
+    return getSteps(xpath).stream().anyMatch(s -> s.getPredicateText().contains(match));
+  }
+
   public static PathExpression addPredicate(final PathExpression pathExpression, final String predicate) {
     return new PathExpression(addPredicate(pathExpression.script, predicate));
   }
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
index 924cda97..4be17ecf 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -80,11 +80,12 @@ public <T extends Expression> T composeFieldReferenceWithAxis(final PathExpressi
   @Override
   public <T extends Expression> T composeFieldValueReference(PathExpression fieldReference,
       Class<T> type) {
-
-    if (MultilingualStringExpression.class.isAssignableFrom(type) || MultilingualStringListExpression.class.isAssignableFrom(type)) {
-      String languages = "('" + String.join("','", this.translatorOptions.getAllLanguage3LetterCodes()) + "')";
+    if ((MultilingualStringExpression.class.isAssignableFrom(type)
+        || MultilingualStringListExpression.class.isAssignableFrom(type))
+        && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) {
       PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__");
-      String script = "(for $__LANG__ in " + languages + " return " + languageSpecific.script + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]";
+      String script = "(for $__LANG__ in ted:preferred-languages() return " + languageSpecific.script
+          + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]";
       return Expression.instantiate(script, type);
     }
     if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) {
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index 6292158c..e92ac5d2 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -97,7 +97,7 @@ void testLikePatternCondition_WithNot() {
   @Test
   void testFieldValueComparison_UsingTextFields() {
     assertEquals(
-        "(for $__LANG__ in ('eng') return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]",
+        "(for $__LANG__ in ted:preferred-languages() return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]",
         test("ND-Root", "BT-00-Text-Multilingual"));
   }
 

From e83641c791893b10147c4cad54c1eee2214b2f6e Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Tue, 13 Jun 2023 00:47:30 +0200
Subject: [PATCH 30/39] Updated changelog for 1.3.0

---
 CHANGELOG.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d03e4e8d..16045893 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,9 +5,10 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation
 ---
 ## In this release:
 
-- Updated the XPath 2.0 parse, XPathContextualizer and XPathScriptGenerator to correctly translate sequences. 
+- Updated the XPath 2.0 parser, XPathContextualizer and XPathScriptGenerator to correctly translate sequences. 
 - Improved numeric formatting. The EfxTranslator API now includes overloaded methods that permit control of numeric formatting. The existing API has been preserved.
-- Improved handling of multilingual text fields by adding automatic selection of the visualisation language .  
+- Improved handling of multilingual text fields by adding automatic selection of the visualisation language.
+
 ---
 
 You can download the latest EFX Toolkit from Maven Central.  

From a60b795536fd67084c3a8a6fb89e6927b5ef7efc Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Tue, 13 Jun 2023 20:54:19 +0200
Subject: [PATCH 31/39] Changed the implementation of multilingual text
 filtering to improve XSLT performance.

---
 src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 2 +-
 .../java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
index 4be17ecf..83b3c82f 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -84,7 +84,7 @@ public <T extends Expression> T composeFieldValueReference(PathExpression fieldR
         || MultilingualStringListExpression.class.isAssignableFrom(type))
         && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) {
       PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__");
-      String script = "(for $__LANG__ in ted:preferred-languages() return " + languageSpecific.script
+      String script = "(for $__LANG__ in $PREFERRED_LANGUAGES return " + languageSpecific.script
           + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]";
       return Expression.instantiate(script, type);
     }
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index e92ac5d2..6fea719d 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -97,7 +97,7 @@ void testLikePatternCondition_WithNot() {
   @Test
   void testFieldValueComparison_UsingTextFields() {
     assertEquals(
-        "(for $__LANG__ in ted:preferred-languages() return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]",
+        "(for $__LANG__ in $PREFERRED_LANGUAGES return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]",
         test("ND-Root", "BT-00-Text-Multilingual"));
   }
 

From fb390351ac78e6928ade374ce8ede1c48aa1cdae Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Thu, 15 Jun 2023 00:06:03 +0200
Subject: [PATCH 32/39] Updated pom.xml for release of 1.3.0

---
 pom.xml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index b3cc553d..d8280c33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
 
   <groupId>eu.europa.ted.eforms</groupId>
   <artifactId>efx-toolkit-java</artifactId>
-  <version>1.3.0-SNAPSHOT</version>
+  <version>1.3.0</version>
   <packaging>jar</packaging>
 
   <name>EFX Toolkit for Java</name>
@@ -59,7 +59,7 @@
     <sdk.antlr4.dir>${project.build.directory}/eforms-sdk/antlr4</sdk.antlr4.dir>
 
     <!-- Versions - eForms -->
-    <version.eforms-core>1.0.5-SNAPSHOT</version.eforms-core>
+    <version.eforms-core>1.0.5</version.eforms-core>
 
     <!-- Versions - Third-party libraries -->
     <version.antlr4>4.9.3</version.antlr4>
@@ -246,7 +246,7 @@
             <artifactItem>
               <groupId>eu.europa.ted.eforms</groupId>
               <artifactId>eforms-sdk</artifactId>
-              <version>1.6.0</version>
+              <version>1.7.0</version>
               <type>jar</type>
               <includes>eforms-sdk/efx-grammar/**/*.g4</includes>
               <outputDirectory>${sdk.antlr4.dir}/eu/europa/ted/efx/sdk1</outputDirectory>

From 7e92ead2cf40d1acef9ab424ed5ebbd3b151941e Mon Sep 17 00:00:00 2001
From: Yannis Rousochatzakis <91379+rousso@users.noreply.github.com>
Date: Thu, 15 Jun 2023 00:20:19 +0200
Subject: [PATCH 33/39] Update README.md

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 7e3da5c4..d78dcba6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-**[:memo: Latest Release Notes](CHANGELOG.md)** | **[:package: Latest Release Artifacts](https://search.maven.org/search?q=g:%22eu.europa.ted.eforms%22%20AND%20a:%22efx-toolkit-java%22)**
+**[:memo: Latest Release Notes](CHANGELOG.md)** | **[:package: Latest Release Artifacts](https://central.sonatype.com/artifact/eu.europa.ted.eforms/efx-toolkit-java)**
 
 ---
 # Java toolkit for the eForms Expression Language (EFX)
@@ -81,10 +81,10 @@ The report is available under `target/site/jacoco/`, in HTML, CSV, and XML forma
 
 You can download the latest EFX Toolkit from Maven Central.
 
-[![Maven Central](https://img.shields.io/maven-central/v/eu.europa.ted.eforms/efx-toolkit-java?label=Download%20&style=flat-square)](https://search.maven.org/search?q=g:%22eu.europa.ted.eforms%22%20AND%20a:%22efx-toolkit-java%22)
+[![Maven Central](https://img.shields.io/maven-central/v/eu.europa.ted.eforms/efx-toolkit-java?label=Download%20&style=flat-square)](https://central.sonatype.com/artifact/eu.europa.ted.eforms/efx-toolkit-java)
 
 [^1]: _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 here](LICENSE)._  
-_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._
\ No newline at end of file
+_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._

From 6431f17d0656a1e0700198eba7cd43d9fdba753e Mon Sep 17 00:00:00 2001
From: Yannis Rousochatzakis <91379+rousso@users.noreply.github.com>
Date: Thu, 15 Jun 2023 00:21:08 +0200
Subject: [PATCH 34/39] Update CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16045893..1b17c913 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation
 ---
 
 You can download the latest EFX Toolkit from Maven Central.  
-[![Maven Central](https://img.shields.io/maven-central/v/eu.europa.ted.eforms/efx-toolkit-java?label=Download%20&style=flat-square)](https://search.maven.org/search?q=g:%22eu.europa.ted.eforms%22%20AND%20a:%22efx-toolkit-java%22)
+[![Maven Central](https://img.shields.io/maven-central/v/eu.europa.ted.eforms/efx-toolkit-java?label=Download%20&style=flat-square)](https://central.sonatype.com/artifact/eu.europa.ted.eforms/efx-toolkit-java)
 
 Documentation for the EFX Toolkit is available at: https://docs.ted.europa.eu/eforms/latest/efx-toolkit
 

From ff1af7d7c6eea0fc89c0e57edc66fc230cf94bae Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Thu, 15 Jun 2023 01:07:29 +0200
Subject: [PATCH 35/39] Organized imports

---
 .../java/eu/europa/ted/efx/interfaces/MarkupGenerator.java   | 5 -----
 .../java/eu/europa/ted/efx/interfaces/ScriptGenerator.java   | 1 -
 2 files changed, 6 deletions(-)

diff --git a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
index 926bec38..b944d725 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/MarkupGenerator.java
@@ -14,10 +14,6 @@
 package eu.europa.ted.efx.interfaces;
 
 import java.util.List;
-import java.util.Set;
-
-import org.apache.commons.lang3.tuple.Pair;
-
 import eu.europa.ted.efx.model.Expression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
 import eu.europa.ted.efx.model.Expression.StringExpression;
@@ -72,7 +68,6 @@ public interface MarkupGenerator {
    */
   Markup composeFragmentDefinition(final String name, String number, Markup content);
 
-
   /**
    * Given a fragment name (identifier), and an evaluation context, this method returns the code
    * that invokes (uses) the fragment.
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 931c8c29..c62d03b7 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java
@@ -25,7 +25,6 @@
 import eu.europa.ted.efx.model.Expression.NumericListExpression;
 import eu.europa.ted.efx.model.Expression.PathExpression;
 import eu.europa.ted.efx.model.Expression.StringExpression;
-import eu.europa.ted.efx.model.Expression.StringListExpression;
 import eu.europa.ted.efx.model.Expression.TimeExpression;
 
 /**

From 1e0b873d9104b81e7add8d6c759873ac6665a0c1 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Sat, 17 Jun 2023 02:43:34 +0200
Subject: [PATCH 36/39] Simplified test-multilingual rendering

---
 .../ted/efx/xpath/XPathScriptGenerator.java      |  5 +----
 .../ted/efx/EfxExpressionTranslatorTest.java     | 16 ++++++++++++++--
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
index 83b3c82f..27562586 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -83,10 +83,7 @@ public <T extends Expression> T composeFieldValueReference(PathExpression fieldR
     if ((MultilingualStringExpression.class.isAssignableFrom(type)
         || MultilingualStringListExpression.class.isAssignableFrom(type))
         && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) {
-      PathExpression languageSpecific = XPathContextualizer.addPredicate(fieldReference, "@languageID=$__LANG__");
-      String script = "(for $__LANG__ in $PREFERRED_LANGUAGES return " + languageSpecific.script
-          + "/normalize-space(text()), " + fieldReference.script + "/normalize-space(text()))[1]";
-      return Expression.instantiate(script, type);
+      return Expression.instantiate("efx:preferred-language-text(" + fieldReference.script + ")", type);
     }
     if (StringExpression.class.isAssignableFrom(type) || StringListExpression.class.isAssignableFrom(type)) {
       return Expression.instantiate(fieldReference.script + "/normalize-space(text())", type);
diff --git a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
index 6fea719d..6a95ef79 100644
--- a/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
+++ b/src/test/java/eu/europa/ted/efx/EfxExpressionTranslatorTest.java
@@ -97,8 +97,8 @@ void testLikePatternCondition_WithNot() {
   @Test
   void testFieldValueComparison_UsingTextFields() {
     assertEquals(
-        "(for $__LANG__ in $PREFERRED_LANGUAGES return PathNode/TextMultilingualField[@languageID=$__LANG__]/normalize-space(text()), PathNode/TextMultilingualField/normalize-space(text()))[1]",
-        test("ND-Root", "BT-00-Text-Multilingual"));
+        "PathNode/TextField/normalize-space(text()) = efx:preferred-language-text(PathNode/TextMultilingualField)",
+        test("ND-Root", "BT-00-Text == BT-00-Text-Multilingual"));
   }
 
   @Test
@@ -1076,6 +1076,18 @@ void testFieldReference_WithAxis() {
         test("ND-Root", "ND-Root::preceding::BT-00-Integer"));
   }
 
+    @Test
+  void testMultilingualTextFieldReference() {
+    assertEquals("efx:preferred-language-text(PathNode/TextMultilingualField)",
+        test("ND-Root", "BT-00-Text-Multilingual"));
+  }
+
+  @Test
+  void testMultilingualTextFieldReference_WithLanguagePredicate() {
+    assertEquals("PathNode/TextMultilingualField[./@languageID = 'eng']/normalize-space(text())",
+        test("ND-Root", "BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == 'eng']"));
+  }
+
   /*** Boolean functions ***/
 
   @Test

From 8e60fe51d1606cf0c0ccf0bc7df404b712d6fc41 Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Sat, 17 Jun 2023 03:47:06 +0200
Subject: [PATCH 37/39] Addressed code-review comments.

---
 .../TranslatorDependencyFactory.java          | 32 +++++++++++++++++++
 .../ted/efx/mock/DependencyFactoryMock.java   | 11 +++++++
 2 files changed, 43 insertions(+)

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 d5211d45..975f8a29 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java
@@ -47,13 +47,44 @@ public interface TranslatorDependencyFactory {
    * This method is called by the EFX translator to instantiate the ScriptGenerator it will use to
    * translate EFX expressions to the target script language.
    * 
+   * @deprecated Use {@link #createScriptGenerator(String, TranslatorOptions)} instead.
    * @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.
    * @return An instance of ScriptGenerator to be used by the EFX translator.
    */
+  @Deprecated(since = "1.3.0", forRemoval = true)
+  public ScriptGenerator createScriptGenerator(String sdkVersion);
+
+  /**
+   * Creates a ScriptGenerator instance.
+   * 
+   * This method is called by the EFX translator to instantiate the ScriptGenerator it will use to
+   * translate EFX expressions to the target script language.
+   * 
+   * @param sdkVersion The version of the SDK that contains the version of the EFX grammar that the
+   *        EFX translator will attempt to translate. This is important as it defines the EFX
+   *        language features that ScriptGenerator instance should be able to handle.
+   * @param options The translator options to be used by the ScriptGenerator instance.
+   * @return An instance of ScriptGenerator to be used by the EFX translator.
+   */
   public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options);
 
+  /**
+   * Creates a MarkupGenerator instance.
+   * 
+   * This method is called by the EFX translator to instantiate the MarkupGenerator it will use to
+   * translate EFX templates to the target markup language.
+   * 
+   * @deprecated Use {@link #createMarkupGenerator(String, TranslatorOptions)} instead.
+   * @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.
+   * @return The instance of MarkupGenerator to be used by the EFX translator.
+   */
+  @Deprecated(since = "1.3.0", forRemoval = true)
+  public MarkupGenerator createMarkupGenerator(String sdkVersion);
+
   /**
    * Creates a MarkupGenerator instance.
    * 
@@ -63,6 +94,7 @@ public interface TranslatorDependencyFactory {
    * @param sdkVersion The version of the SDK that contains the version of the EFX grammar that the
    *        EFX translator will attempt to translate. This is important as it defines the EFX
    *        language features that MarkupGenerator instance should be able to handle.
+   * @param options The translator options to be used by the MarkupGenerator instance.
    * @return The instance of MarkupGenerator to be used by the EFX translator.
    */
   public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options);
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 75d1f3cf..48bb43dc 100644
--- a/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java
+++ b/src/test/java/eu/europa/ted/efx/mock/DependencyFactoryMock.java
@@ -2,6 +2,7 @@
 
 import org.antlr.v4.runtime.BaseErrorListener;
 
+import eu.europa.ted.efx.EfxTranslatorOptions;
 import eu.europa.ted.efx.exceptions.ThrowingErrorListener;
 import eu.europa.ted.efx.interfaces.MarkupGenerator;
 import eu.europa.ted.efx.interfaces.ScriptGenerator;
@@ -27,6 +28,11 @@ public SymbolResolver createSymbolResolver(String sdkVersion) {
     return SymbolResolverMock.getInstance(sdkVersion);
   }
 
+  @Override
+  public ScriptGenerator createScriptGenerator(String sdkVersion) {
+    return this.createScriptGenerator(sdkVersion, EfxTranslatorOptions.DEFAULT);
+  }
+
   @Override
   public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options) {
     if (scriptGenerator == null) {
@@ -35,6 +41,11 @@ public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOption
     return this.scriptGenerator;
   }
 
+  @Override
+  public MarkupGenerator createMarkupGenerator(String sdkVersion) {
+    return this.createMarkupGenerator(sdkVersion, EfxTranslatorOptions.DEFAULT);
+  }
+
   @Override
   public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options) {
     if (this.markupGenerator == null) {

From 2e10bb2cdd1fd98a4319cb5f5ec30a2c7c0417b0 Mon Sep 17 00:00:00 2001
From: Bertrand Lorentz <bertrand.lorentz@publications.europa.eu>
Date: Wed, 5 Jul 2023 14:15:54 +0200
Subject: [PATCH 38/39] workflows: Update version of import-gpg action
 (TEDEFO-2357)

Update parameters to use inputs instead of environment variables.

This removes the warnings on the "publish" workflow.
---
 .github/workflows/publish.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 6b11035a..4c5c702d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -21,10 +21,10 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Import GPG Key
-        uses: crazy-max/ghaction-import-gpg@v1
-        env:
-          GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
-          PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+        uses: crazy-max/ghaction-import-gpg@v5
+        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
         with:

From 49d3d28aad3ede5e673a22486440d02db83dca8f Mon Sep 17 00:00:00 2001
From: Ioannis Rousochatzakis <ioannis.rousochatzakis@publications.europa.eu>
Date: Tue, 25 Jul 2023 02:12:47 +0200
Subject: [PATCH 39/39] Adaptations suggested in PR comments.

---
 .../ted/efx/interfaces/TranslatorDependencyFactory.java   | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

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 975f8a29..8efd6349 100644
--- a/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java
+++ b/src/main/java/eu/europa/ted/efx/interfaces/TranslatorDependencyFactory.java
@@ -68,7 +68,9 @@ public interface TranslatorDependencyFactory {
    * @param options The translator options to be used by the ScriptGenerator instance.
    * @return An instance of ScriptGenerator to be used by the EFX translator.
    */
-  public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options);
+  default public ScriptGenerator createScriptGenerator(String sdkVersion, TranslatorOptions options) {
+    return createScriptGenerator(sdkVersion);
+  }
 
   /**
    * Creates a MarkupGenerator instance.
@@ -97,7 +99,9 @@ public interface TranslatorDependencyFactory {
    * @param options The translator options to be used by the MarkupGenerator instance.
    * @return The instance of MarkupGenerator to be used by the EFX translator.
    */
-  public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options);
+  default public MarkupGenerator createMarkupGenerator(String sdkVersion, TranslatorOptions options) {
+    return createMarkupGenerator(sdkVersion);
+  }
 
   /**
    * Creates an error listener instance.