From 05b8091a23bd7dfcca3aa178efec0d5fc242df37 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 13 Feb 2026 22:46:48 +0100 Subject: [PATCH 01/31] TEDEFO-4870 Fix string(number) to use XPath string() instead of format-number() SDK2: Replace format-number(n, '0.##########') with string(n). SDK1: Override to preserve the format-number() behavior. --- .../ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java | 8 ++++++++ .../eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 3 +-- .../ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 11 ++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java index 5ce7c9fb..6f696442 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/xpath/XPathScriptGeneratorV1.java @@ -20,6 +20,8 @@ import eu.europa.ted.efx.interfaces.TranslatorOptions; import eu.europa.ted.efx.model.expressions.Expression; import eu.europa.ted.efx.model.expressions.PathExpression; +import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; +import eu.europa.ted.efx.model.expressions.scalar.StringExpression; import eu.europa.ted.efx.model.types.EfxDataType; import eu.europa.ted.efx.xpath.XPathScriptGenerator; @@ -30,6 +32,12 @@ public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) { super(translatorOptions); } + @Override + public StringExpression composeToStringConversion(NumericExpression number) { + String formatString = this.translatorOptions.getDecimalFormat().adaptFormatString("0.##########"); + return new StringExpression("format-number(" + number.getScript() + ", '" + formatString + "')"); + } + /*** * This method is overridden to workaround a limitation of EFX 1. * 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 8a37d8c0..c1dbc366 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -491,8 +491,7 @@ public StringExpression composeSubstringExtraction(StringExpression text, @Override public StringExpression composeToStringConversion(NumericExpression number) { - String formatString = this.translatorOptions.getDecimalFormat().adaptFormatString("0.##########"); - return new StringExpression("format-number(" + number.getScript() + ", '" + formatString + "')"); + return new StringExpression("string(" + number.getScript() + ")"); } @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 3a6acc49..4777b15c 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -587,7 +587,7 @@ void testStringsFromStringIteration_UsingLiterals() { @Test void testStringsSequenceFromIteration_UsingMultipleIterators() { testExpressionTranslationWithContext( - "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, format-number($y, '0,##########'), 'text'))", + "'a' = (for $x in ('a','b','c'), $y in (1,2), $z in PathNode/IndicatorField return concat($x, string($y), 'text'))", "ND-Root", "'a' in (for text:$x in ('a', 'b', 'c'), number:$y in (1, 2), indicator:$z in BT-00-Indicator return concat($x, string($y), 'text'))"); } @@ -1589,8 +1589,13 @@ void testLowerCaseFunction() { @Test void testToStringFunction() { - testExpressionTranslationWithContext("format-number(123, '0,##########')", "ND-Root", - "string(123)"); + testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); + } + + @Test + void testToStringFunction_WithFieldReference() { + testExpressionTranslationWithContext("string(PathNode/NumberField/number())", "ND-Root", + "string(BT-00-Number)"); } @Test From 381b782e98eebff018818c2ff143296c8587ff45 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 13 Feb 2026 23:19:47 +0100 Subject: [PATCH 02/31] TEDEFO-4871 Add string(boolean) type conversion --- .../eu/europa/ted/efx/interfaces/ScriptGenerator.java | 2 ++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 5 +++++ .../eu/europa/ted/efx/xpath/XPathScriptGenerator.java | 5 +++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 11 +++++++++++ 4 files changed, 23 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 5ab0e444..6b713d7c 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -357,6 +357,8 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public StringExpression composeToStringConversion(NumericExpression number); + public StringExpression composeToStringConversion(BooleanExpression bool); + /** * Returns the target language script that converts the given text to upper case. * diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 6c23495e..9d299376 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1939,6 +1939,11 @@ public void exitToStringFunction(ToStringFunctionContext ctx) { this.stack.push(this.script.composeToStringConversion(this.stack.pop(NumericExpression.class))); } + @Override + public void exitBooleanToStringFunction(BooleanToStringFunctionContext ctx) { + this.stack.push(this.script.composeToStringConversion(this.stack.pop(BooleanExpression.class))); + } + @Override public void exitConcatFunction(ConcatFunctionContext ctx) { if (this.stack.empty() || ctx.stringExpression().isEmpty()) { 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 c1dbc366..db81c624 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -494,6 +494,11 @@ public StringExpression composeToStringConversion(NumericExpression number) { return new StringExpression("string(" + number.getScript() + ")"); } + @Override + public StringExpression composeToStringConversion(BooleanExpression bool) { + return new StringExpression("string(" + bool.getScript() + ")"); + } + @Override public StringExpression composeToUpperCaseConversion(StringExpression text) { return new StringExpression("upper-case(" + text.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 4777b15c..4d9e782d 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1598,6 +1598,17 @@ void testToStringFunction_WithFieldReference() { "string(BT-00-Number)"); } + @Test + void testBooleanToStringFunction() { + testExpressionTranslationWithContext("string(true())", "ND-Root", "string(TRUE)"); + } + + @Test + void testBooleanToStringFunction_WithFieldReference() { + testExpressionTranslationWithContext("string(PathNode/IndicatorField)", "ND-Root", + "string(BT-00-Indicator)"); + } + @Test void testConcatFunction() { testExpressionTranslationWithContext("concat('abc', 'def')", "ND-Root", "concat('abc', 'def')"); From b7525a3c9b659519c6b7b642d522380dae27cad8 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 13 Feb 2026 23:57:32 +0100 Subject: [PATCH 03/31] TEDEFO-4872 Add string(date) type conversion --- .../europa/ted/efx/interfaces/ScriptGenerator.java | 2 ++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 5 +++++ .../europa/ted/efx/xpath/XPathScriptGenerator.java | 5 +++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 12 ++++++++++++ 4 files changed, 24 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 6b713d7c..df7bbaa6 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -359,6 +359,8 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public StringExpression composeToStringConversion(BooleanExpression bool); + public StringExpression composeToStringConversion(DateExpression date); + /** * Returns the target language script that converts the given text to upper case. * diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 9d299376..f2959fb1 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1944,6 +1944,11 @@ public void exitBooleanToStringFunction(BooleanToStringFunctionContext ctx) { this.stack.push(this.script.composeToStringConversion(this.stack.pop(BooleanExpression.class))); } + @Override + public void exitDateToStringFunction(DateToStringFunctionContext ctx) { + this.stack.push(this.script.composeToStringConversion(this.stack.pop(DateExpression.class))); + } + @Override public void exitConcatFunction(ConcatFunctionContext ctx) { if (this.stack.empty() || ctx.stringExpression().isEmpty()) { 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 db81c624..be3c8806 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -499,6 +499,11 @@ public StringExpression composeToStringConversion(BooleanExpression bool) { return new StringExpression("string(" + bool.getScript() + ")"); } + @Override + public StringExpression composeToStringConversion(DateExpression date) { + return new StringExpression("string(" + date.getScript() + ")"); + } + @Override public StringExpression composeToUpperCaseConversion(StringExpression text) { return new StringExpression("upper-case(" + text.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 4d9e782d..501750ad 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1609,6 +1609,18 @@ void testBooleanToStringFunction_WithFieldReference() { "string(BT-00-Indicator)"); } + @Test + void testDateToStringFunction() { + testExpressionTranslationWithContext("string(xs:date('2024-01-15Z'))", "ND-Root", + "string(2024-01-15Z)"); + } + + @Test + void testDateToStringFunction_WithFieldReference() { + testExpressionTranslationWithContext("string(PathNode/StartDateField/xs:date(text()))", "ND-Root", + "string(BT-00-StartDate)"); + } + @Test void testConcatFunction() { testExpressionTranslationWithContext("concat('abc', 'def')", "ND-Root", "concat('abc', 'def')"); From 802dfaa2fd3b2816599dd8f2288de26c5f2d016c Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 00:07:42 +0100 Subject: [PATCH 04/31] TEDEFO-4873 Add string(time) type conversion --- .../europa/ted/efx/interfaces/ScriptGenerator.java | 2 ++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 5 +++++ .../europa/ted/efx/xpath/XPathScriptGenerator.java | 5 +++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 12 ++++++++++++ 4 files changed, 24 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index df7bbaa6..67ad2ecc 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -361,6 +361,8 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public StringExpression composeToStringConversion(DateExpression date); + public StringExpression composeToStringConversion(TimeExpression time); + /** * Returns the target language script that converts the given text to upper case. * diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index f2959fb1..12e9b847 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1949,6 +1949,11 @@ public void exitDateToStringFunction(DateToStringFunctionContext ctx) { this.stack.push(this.script.composeToStringConversion(this.stack.pop(DateExpression.class))); } + @Override + public void exitTimeToStringFunction(TimeToStringFunctionContext ctx) { + this.stack.push(this.script.composeToStringConversion(this.stack.pop(TimeExpression.class))); + } + @Override public void exitConcatFunction(ConcatFunctionContext ctx) { if (this.stack.empty() || ctx.stringExpression().isEmpty()) { 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 be3c8806..50c7da3e 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -504,6 +504,11 @@ public StringExpression composeToStringConversion(DateExpression date) { return new StringExpression("string(" + date.getScript() + ")"); } + @Override + public StringExpression composeToStringConversion(TimeExpression time) { + return new StringExpression("string(" + time.getScript() + ")"); + } + @Override public StringExpression composeToUpperCaseConversion(StringExpression text) { return new StringExpression("upper-case(" + text.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 501750ad..8f476a47 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1621,6 +1621,18 @@ void testDateToStringFunction_WithFieldReference() { "string(BT-00-StartDate)"); } + @Test + void testTimeToStringFunction() { + testExpressionTranslationWithContext("string(xs:time('14:30:00Z'))", "ND-Root", + "string(14:30:00Z)"); + } + + @Test + void testTimeToStringFunction_WithFieldReference() { + testExpressionTranslationWithContext("string(PathNode/StartTimeField/xs:time(text()))", "ND-Root", + "string(BT-00-StartTime)"); + } + @Test void testConcatFunction() { testExpressionTranslationWithContext("concat('abc', 'def')", "ND-Root", "concat('abc', 'def')"); From fbfdba082bcd61e688529a4d7a3ca91e7a31cde8 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 00:15:04 +0100 Subject: [PATCH 05/31] TEDEFO-4874 Add string(duration) type conversion --- .../europa/ted/efx/interfaces/ScriptGenerator.java | 2 ++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 5 +++++ .../europa/ted/efx/xpath/XPathScriptGenerator.java | 5 +++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2Test.java | 13 +++++++++++++ 4 files changed, 25 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 67ad2ecc..8f958dfb 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -363,6 +363,8 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public StringExpression composeToStringConversion(TimeExpression time); + public StringExpression composeToStringConversion(DurationExpression duration); + /** * Returns the target language script that converts the given text to upper case. * diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 12e9b847..74c6f324 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1954,6 +1954,11 @@ public void exitTimeToStringFunction(TimeToStringFunctionContext ctx) { this.stack.push(this.script.composeToStringConversion(this.stack.pop(TimeExpression.class))); } + @Override + public void exitDurationToStringFunction(DurationToStringFunctionContext ctx) { + this.stack.push(this.script.composeToStringConversion(this.stack.pop(DurationExpression.class))); + } + @Override public void exitConcatFunction(ConcatFunctionContext ctx) { if (this.stack.empty() || ctx.stringExpression().isEmpty()) { 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 50c7da3e..6bc08d12 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -509,6 +509,11 @@ public StringExpression composeToStringConversion(TimeExpression time) { return new StringExpression("string(" + time.getScript() + ")"); } + @Override + public StringExpression composeToStringConversion(DurationExpression duration) { + return new StringExpression("string(" + duration.getScript() + ")"); + } + @Override public StringExpression composeToUpperCaseConversion(StringExpression text) { return new StringExpression("upper-case(" + text.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 8f476a47..d469e8f1 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1633,6 +1633,19 @@ void testTimeToStringFunction_WithFieldReference() { "string(BT-00-StartTime)"); } + @Test + void testDurationToStringFunction() { + testExpressionTranslationWithContext("string(xs:dayTimeDuration('P30D'))", "ND-Root", + "string(P30D)"); + } + + @Test + void testDurationToStringFunction_WithFieldReference() { + testExpressionTranslationWithContext( + "string((for $F in PathNode/MeasureField return (if ($F/@unitCode='WEEK') then xs:dayTimeDuration(concat('P', $F/number() * 7, 'D')) else if ($F/@unitCode='DAY') then xs:dayTimeDuration(concat('P', $F/number(), 'D')) else if ($F/@unitCode='YEAR') then xs:yearMonthDuration(concat('P', $F/number(), 'Y')) else if ($F/@unitCode='MONTH') then xs:yearMonthDuration(concat('P', $F/number(), 'M')) else ())))", + "ND-Root", "string(BT-00-Measure)"); + } + @Test void testConcatFunction() { testExpressionTranslationWithContext("concat('abc', 'def')", "ND-Root", "concat('abc', 'def')"); From 4375bd5de8231bf3993d2dabd0ab088e588e6d85 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 01:09:45 +0100 Subject: [PATCH 06/31] TEDEFO-4875 Add number(boolean) and indicator(number) type conversions --- .../ted/efx/interfaces/ScriptGenerator.java | 4 ++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 12 +++++++++- .../ted/efx/xpath/XPathScriptGenerator.java | 10 +++++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 22 +++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) 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 8f958dfb..818eb1fa 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -328,6 +328,8 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public NumericExpression composeToNumberConversion(StringExpression text); + public NumericExpression composeToNumberConversion(BooleanExpression bool); + public NumericExpression composeSumOperation(NumericSequenceExpression list); public NumericExpression composeStringLengthCalculation(StringExpression text); @@ -424,6 +426,8 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public BooleanExpression composeExistsCondition(PathExpression reference); + public BooleanExpression composeToBooleanConversion(NumericExpression number); + /** * Uniqueness check for EFX 1 syntax. *

diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 74c6f324..66a342d4 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1649,6 +1649,11 @@ public void exitNotFunction(NotFunctionContext ctx) { this.stack.push(this.script.composeLogicalNot(this.stack.pop(BooleanExpression.class))); } + @Override + public void exitBooleanFromNumberFunction(BooleanFromNumberFunctionContext ctx) { + this.stack.push(this.script.composeToBooleanConversion(this.stack.pop(NumericExpression.class))); + } + @Override public void exitContainsFunction(ContainsFunctionContext ctx) { final StringExpression needle = this.stack.pop(StringExpression.class); @@ -1902,10 +1907,15 @@ public void exitCountDurationsFunction(CountDurationsFunctionContext ctx) { } @Override - public void exitNumberFunction(NumberFunctionContext ctx) { + public void exitNumberFromStringFunction(NumberFromStringFunctionContext ctx) { this.stack.push(this.script.composeToNumberConversion(this.stack.pop(StringExpression.class))); } + @Override + public void exitNumberFromBooleanFunction(NumberFromBooleanFunctionContext ctx) { + this.stack.push(this.script.composeToNumberConversion(this.stack.pop(BooleanExpression.class))); + } + @Override public void exitSumFunction(SumFunctionContext ctx) { this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericSequenceExpression.class))); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 6bc08d12..8ec5cc02 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -347,6 +347,11 @@ public BooleanExpression composeExistsCondition(PathExpression reference) { return new BooleanExpression(reference.getScript()); } + @Override + public BooleanExpression composeToBooleanConversion(NumericExpression number) { + return new BooleanExpression("boolean(" + number.getScript() + ")"); + } + /** * EFX 1 uniqueness check - kept for backward compatibility. * EFX 2 uses the typed overloads below. @@ -455,6 +460,11 @@ public NumericExpression composeToNumberConversion(StringExpression text) { return new NumericExpression("number(" + text.getScript() + ")"); } + @Override + public NumericExpression composeToNumberConversion(BooleanExpression bool) { + return new NumericExpression("number(" + bool.getScript() + ")"); + } + @Override public NumericExpression composeSumOperation(NumericSequenceExpression nodeSet) { return new NumericExpression("sum(" + nodeSet.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index d469e8f1..a143b869 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1278,6 +1278,17 @@ void testNotFunction() { () -> translateExpressionWithContext("BT-00-Text", "not('text')")); } + @Test + void testBooleanFromNumberFunction() { + testExpressionTranslationWithContext("boolean(0)", "ND-Root", "indicator(0)"); + } + + @Test + void testBooleanFromNumberFunction_WithFieldReference() { + testExpressionTranslationWithContext("boolean(PathNode/NumberField/number())", "ND-Root", + "indicator(BT-00-Number)"); + } + @Test void testContainsFunction() { testExpressionTranslationWithContext( @@ -1541,6 +1552,17 @@ void testNumberFunction() { "ND-Root", "number(BT-00-Text)"); } + @Test + void testNumberFromBooleanFunction() { + testExpressionTranslationWithContext("number(true())", "ND-Root", "number(TRUE)"); + } + + @Test + void testNumberFromBooleanFunction_WithFieldReference() { + testExpressionTranslationWithContext("number(PathNode/IndicatorField)", "ND-Root", + "number(BT-00-Indicator)"); + } + @Test void testSumFunction_UsingFieldReference() { testExpressionTranslationWithContext("sum(PathNode/NumberField/number())", "ND-Root", From d0c566545393a2df7d66c66fb6463119b0aa562e Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 01:47:50 +0100 Subject: [PATCH 07/31] TEDEFO-4870 Rename string() conversion to text() with string as alias --- .../efx/sdk2/EfxExpressionTranslatorV2.java | 2 +- .../sdk2/EfxExpressionTranslatorV2Test.java | 33 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 66a342d4..75171b63 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -1945,7 +1945,7 @@ public void exitSubstringFunction(SubstringFunctionContext ctx) { } @Override - public void exitToStringFunction(ToStringFunctionContext ctx) { + public void exitNumberToStringFunction(NumberToStringFunctionContext ctx) { this.stack.push(this.script.composeToStringConversion(this.stack.pop(NumericExpression.class))); } diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index a143b869..b944d310 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1610,12 +1610,12 @@ void testLowerCaseFunction() { } @Test - void testToStringFunction() { + void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); } @Test - void testToStringFunction_WithFieldReference() { + void testNumberToStringFunction_WithFieldReference() { testExpressionTranslationWithContext("string(PathNode/NumberField/number())", "ND-Root", "string(BT-00-Number)"); } @@ -1668,6 +1668,35 @@ void testDurationToStringFunction_WithFieldReference() { "ND-Root", "string(BT-00-Measure)"); } + // text() variants - verify that 'text' keyword works as alias for 'string' conversion + @Test + void testTextFromNumberFunction() { + testExpressionTranslationWithContext("string(123)", "ND-Root", "text(123)"); + } + + @Test + void testTextFromBooleanFunction() { + testExpressionTranslationWithContext("string(true())", "ND-Root", "text(TRUE)"); + } + + @Test + void testTextFromDateFunction() { + testExpressionTranslationWithContext("string(xs:date('2024-01-15Z'))", "ND-Root", + "text(2024-01-15Z)"); + } + + @Test + void testTextFromTimeFunction() { + testExpressionTranslationWithContext("string(xs:time('14:30:00Z'))", "ND-Root", + "text(14:30:00Z)"); + } + + @Test + void testTextFromDurationFunction() { + testExpressionTranslationWithContext("string(xs:dayTimeDuration('P30D'))", "ND-Root", + "text(P30D)"); + } + @Test void testConcatFunction() { testExpressionTranslationWithContext("concat('abc', 'def')", "ND-Root", "concat('abc', 'def')"); From 225e8fff86889b7a54a75dee22a4bc1c4b4a5c1e Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 15:16:42 +0100 Subject: [PATCH 08/31] TEDEFO-4877 Add sort() function to EFX toolkit --- .../ted/efx/interfaces/ScriptGenerator.java | 2 + .../efx/sdk2/EfxExpressionTranslatorV2.java | 39 +++++++++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 5 ++ .../sdk2/EfxExpressionTranslatorV2Test.java | 48 +++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 818eb1fa..8699f223 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -530,6 +530,8 @@ public T composeIntersectFunction(T listOne, public T composeExceptFunction(T listOne, T listTwo, Class listType); + public T composeSortFunction(T list, Class listType); + public T composeIndexer(SequenceExpression list, NumericExpression index, Class type); diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 75171b63..b9a2e99a 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2243,6 +2243,45 @@ private void exitExceptFunction(Class listType // #endregion Except --------------------------------------------------------- + // #region Sort -------------------------------------------------------------- + + @Override + public void exitStringSortFunction(StringSortFunctionContext ctx) { + exitSortFunction(StringSequenceExpression.class); + } + + @Override + public void exitBooleanSortFunction(BooleanSortFunctionContext ctx) { + exitSortFunction(BooleanSequenceExpression.class); + } + + @Override + public void exitNumericSortFunction(NumericSortFunctionContext ctx) { + exitSortFunction(NumericSequenceExpression.class); + } + + @Override + public void exitDateSortFunction(DateSortFunctionContext ctx) { + exitSortFunction(DateSequenceExpression.class); + } + + @Override + public void exitTimeSortFunction(TimeSortFunctionContext ctx) { + exitSortFunction(TimeSequenceExpression.class); + } + + @Override + public void exitDurationSortFunction(DurationSortFunctionContext ctx) { + exitSortFunction(DurationSequenceExpression.class); + } + + private void exitSortFunction(Class listType) { + final T list = this.stack.pop(listType); + this.stack.push(this.script.composeSortFunction(list, listType)); + } + + // #endregion Sort ----------------------------------------------------------- + // #endregion Sequence Functions -------------------------------------------- // #region Helpers ---------------------------------------------------------- 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 8ec5cc02..969d2cb2 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -661,6 +661,11 @@ public T composeExceptFunction(T listOne, T listT return Expression.instantiate("distinct-values(for $L1 in " + listOne.getScript() + " return if (every $L2 in " + listTwo.getScript() + " satisfies $L1 != $L2) then $L1 else ())", listType); } + @Override + public T composeSortFunction(T list, Class listType) { + return Expression.instantiate("sort(" + list.getScript() + ")", listType); + } + //#endregion Duration functions --------------------------------------------- @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index b944d310..1ea51233 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -2002,6 +2002,54 @@ void testExceptFunction_WithTypeMismatch() { // #endregion: Except + // #region: Sort + + @Test + void testSortFunction_WithStringSequences() { + testExpressionTranslationWithContext("sort(('banana','apple','cherry'))", "ND-Root", + "sort(('banana', 'apple', 'cherry'))"); + } + + @Test + void testSortFunction_WithNumberSequences() { + testExpressionTranslationWithContext("sort((3,1,2))", "ND-Root", + "sort((3, 1, 2))"); + } + + @Test + void testSortFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "sort((xs:date('2022-01-01Z'),xs:date('2018-01-01Z'),xs:date('2020-01-01Z')))", + "ND-Root", "sort((2022-01-01Z, 2018-01-01Z, 2020-01-01Z))"); + } + + @Test + void testSortFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "sort((xs:time('14:00:00Z'),xs:time('12:00:00Z'),xs:time('13:00:00Z')))", + "ND-Root", "sort((14:00:00Z, 12:00:00Z, 13:00:00Z))"); + } + + @Test + void testSortFunction_WithDurationSequences() { + testExpressionTranslationWithContext("sort((xs:dayTimeDuration('P5D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')))", + "ND-Root", "sort((P5D, P2D, P1W))"); + } + + @Test + void testSortFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("sort((true(),false(),true()))", + "ND-Root", "sort((TRUE, FALSE, TRUE))"); + } + + @Test + void testSortFunction_WithFieldReferences() { + testExpressionTranslationWithContext("sort(PathNode/TextField/normalize-space(text()))", "ND-Root", + "sort(BT-00-Text)"); + } + + // #endregion: Sort + // #region: Compare sequences @Test From 1463ffc61dc5426f1569793aaf09e8dfe3b12563 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 15:32:30 +0100 Subject: [PATCH 09/31] TEDEFO-4878 Add reverse() function to EFX toolkit --- .../ted/efx/interfaces/ScriptGenerator.java | 2 + .../efx/sdk2/EfxExpressionTranslatorV2.java | 39 ++++++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 5 ++ .../sdk2/EfxExpressionTranslatorV2Test.java | 62 +++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 8699f223..0e7e1181 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -532,6 +532,8 @@ public T composeExceptFunction(T listOne, public T composeSortFunction(T list, Class listType); + public T composeReverseFunction(T list, Class listType); + public T composeIndexer(SequenceExpression list, NumericExpression index, Class type); diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index b9a2e99a..a5e7dd51 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2282,6 +2282,45 @@ private void exitSortFunction(Class listType) // #endregion Sort ----------------------------------------------------------- + // #region Reverse ---------------------------------------------------------- + + @Override + public void exitStringReverseFunction(StringReverseFunctionContext ctx) { + exitReverseFunction(StringSequenceExpression.class); + } + + @Override + public void exitBooleanReverseFunction(BooleanReverseFunctionContext ctx) { + exitReverseFunction(BooleanSequenceExpression.class); + } + + @Override + public void exitNumericReverseFunction(NumericReverseFunctionContext ctx) { + exitReverseFunction(NumericSequenceExpression.class); + } + + @Override + public void exitDateReverseFunction(DateReverseFunctionContext ctx) { + exitReverseFunction(DateSequenceExpression.class); + } + + @Override + public void exitTimeReverseFunction(TimeReverseFunctionContext ctx) { + exitReverseFunction(TimeSequenceExpression.class); + } + + @Override + public void exitDurationReverseFunction(DurationReverseFunctionContext ctx) { + exitReverseFunction(DurationSequenceExpression.class); + } + + private void exitReverseFunction(Class listType) { + final T list = this.stack.pop(listType); + this.stack.push(this.script.composeReverseFunction(list, listType)); + } + + // #endregion Reverse -------------------------------------------------------- + // #endregion Sequence Functions -------------------------------------------- // #region Helpers ---------------------------------------------------------- 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 969d2cb2..445fadba 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -666,6 +666,11 @@ public T composeSortFunction(T list, Class lis return Expression.instantiate("sort(" + list.getScript() + ")", listType); } + @Override + public T composeReverseFunction(T list, Class listType) { + return Expression.instantiate("reverse(" + list.getScript() + ")", listType); + } + //#endregion Duration functions --------------------------------------------- @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 1ea51233..45a68e2b 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -2048,8 +2048,70 @@ void testSortFunction_WithFieldReferences() { "sort(BT-00-Text)"); } + @Test + void testSortFunction_WithRepeatableFieldReference() { + testExpressionTranslationWithContext( + "sort(PathNode/RepeatableTextField/normalize-space(text()))", "ND-Root", + "sort(BT-00-Repeatable-Text)"); + } + // #endregion: Sort + // #region: Reverse + + @Test + void testReverseFunction_WithStringSequences() { + testExpressionTranslationWithContext("reverse(('banana','apple','cherry'))", "ND-Root", + "reverse(('banana', 'apple', 'cherry'))"); + } + + @Test + void testReverseFunction_WithNumberSequences() { + testExpressionTranslationWithContext("reverse((3,1,2))", "ND-Root", + "reverse((3, 1, 2))"); + } + + @Test + void testReverseFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "reverse((xs:date('2022-01-01Z'),xs:date('2018-01-01Z'),xs:date('2020-01-01Z')))", + "ND-Root", "reverse((2022-01-01Z, 2018-01-01Z, 2020-01-01Z))"); + } + + @Test + void testReverseFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "reverse((xs:time('14:00:00Z'),xs:time('12:00:00Z'),xs:time('13:00:00Z')))", + "ND-Root", "reverse((14:00:00Z, 12:00:00Z, 13:00:00Z))"); + } + + @Test + void testReverseFunction_WithDurationSequences() { + testExpressionTranslationWithContext("reverse((xs:dayTimeDuration('P5D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P7D')))", + "ND-Root", "reverse((P5D, P2D, P1W))"); + } + + @Test + void testReverseFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("reverse((true(),false(),true()))", + "ND-Root", "reverse((TRUE, FALSE, TRUE))"); + } + + @Test + void testReverseFunction_WithFieldReferences() { + testExpressionTranslationWithContext("reverse(PathNode/TextField/normalize-space(text()))", "ND-Root", + "reverse(BT-00-Text)"); + } + + @Test + void testReverseFunction_WithRepeatableFieldReference() { + testExpressionTranslationWithContext( + "reverse(PathNode/RepeatableTextField/normalize-space(text()))", "ND-Root", + "reverse(BT-00-Repeatable-Text)"); + } + + // #endregion: Reverse + // #region: Compare sequences @Test From aa8427a17bac7baad4319e4f49702f18e5694722 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 16:06:59 +0100 Subject: [PATCH 10/31] TEDEFO-4879 Add subsequence() function to EFX toolkit --- .../ted/efx/interfaces/ScriptGenerator.java | 6 +++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 47 +++++++++++++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 16 +++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 43 +++++++++++++++++ 4 files changed, 112 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 0e7e1181..988cd982 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -534,6 +534,12 @@ public T composeExceptFunction(T listOne, public T composeReverseFunction(T list, Class listType); + public T composeSubsequenceFunction(T list, + NumericExpression start, Class listType); + + public T composeSubsequenceFunction(T list, + NumericExpression start, NumericExpression length, Class listType); + public T composeIndexer(SequenceExpression list, NumericExpression index, Class type); diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index a5e7dd51..a14838dd 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2321,6 +2321,53 @@ private void exitReverseFunction(Class listTyp // #endregion Reverse -------------------------------------------------------- + // #region Subsequence ------------------------------------------------------ + + @Override + public void exitStringSubsequenceFunction(StringSubsequenceFunctionContext ctx) { + exitSubsequenceFunction(ctx.length != null, StringSequenceExpression.class); + } + + @Override + public void exitBooleanSubsequenceFunction(BooleanSubsequenceFunctionContext ctx) { + exitSubsequenceFunction(ctx.length != null, BooleanSequenceExpression.class); + } + + @Override + public void exitNumericSubsequenceFunction(NumericSubsequenceFunctionContext ctx) { + exitSubsequenceFunction(ctx.length != null, NumericSequenceExpression.class); + } + + @Override + public void exitDateSubsequenceFunction(DateSubsequenceFunctionContext ctx) { + exitSubsequenceFunction(ctx.length != null, DateSequenceExpression.class); + } + + @Override + public void exitTimeSubsequenceFunction(TimeSubsequenceFunctionContext ctx) { + exitSubsequenceFunction(ctx.length != null, TimeSequenceExpression.class); + } + + @Override + public void exitDurationSubsequenceFunction(DurationSubsequenceFunctionContext ctx) { + exitSubsequenceFunction(ctx.length != null, DurationSequenceExpression.class); + } + + private void exitSubsequenceFunction(boolean hasLength, + Class listType) { + final NumericExpression length = + hasLength ? this.stack.pop(NumericExpression.class) : null; + final NumericExpression start = this.stack.pop(NumericExpression.class); + final T list = this.stack.pop(listType); + if (length != null) { + this.stack.push(this.script.composeSubsequenceFunction(list, start, length, listType)); + } else { + this.stack.push(this.script.composeSubsequenceFunction(list, start, listType)); + } + } + + // #endregion Subsequence ---------------------------------------------------- + // #endregion Sequence Functions -------------------------------------------- // #region Helpers ---------------------------------------------------------- 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 445fadba..d237b4f3 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -671,6 +671,22 @@ public T composeReverseFunction(T list, Class return Expression.instantiate("reverse(" + list.getScript() + ")", listType); } + @Override + public T composeSubsequenceFunction(T list, + NumericExpression start, Class listType) { + return Expression.instantiate( + "subsequence(" + list.getScript() + ", " + start.getScript() + ")", listType); + } + + @Override + public T composeSubsequenceFunction(T list, + NumericExpression start, NumericExpression length, Class listType) { + return Expression.instantiate( + "subsequence(" + list.getScript() + ", " + start.getScript() + ", " + length.getScript() + + ")", + listType); + } + //#endregion Duration functions --------------------------------------------- @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 45a68e2b..275809ee 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -2112,6 +2112,49 @@ void testReverseFunction_WithRepeatableFieldReference() { // #endregion: Reverse + // #region: Subsequence + + @Test + void testSubsequenceFunction_WithStringSequences() { + testExpressionTranslationWithContext("subsequence(('a','b','c','d'), 2)", "ND-Root", + "subsequence(('a', 'b', 'c', 'd'), 2)"); + } + + @Test + void testSubsequenceFunction_WithStringSequences_AndLength() { + testExpressionTranslationWithContext("subsequence(('a','b','c','d'), 2, 2)", "ND-Root", + "subsequence(('a', 'b', 'c', 'd'), 2, 2)"); + } + + @Test + void testSubsequenceFunction_WithNumberSequences() { + testExpressionTranslationWithContext("subsequence((10,20,30,40), 2, 2)", "ND-Root", + "subsequence((10, 20, 30, 40), 2, 2)"); + } + + @Test + void testSubsequenceFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "subsequence((xs:date('2022-01-01Z'),xs:date('2023-01-01Z'),xs:date('2024-01-01Z')), 1, 2)", + "ND-Root", "subsequence((2022-01-01Z, 2023-01-01Z, 2024-01-01Z), 1, 2)"); + } + + @Test + void testSubsequenceFunction_WithRepeatableFieldReference() { + testExpressionTranslationWithContext( + "subsequence(PathNode/RepeatableTextField/normalize-space(text()), 2)", "ND-Root", + "subsequence(BT-00-Repeatable-Text, 2)"); + } + + @Test + void testSubsequenceFunction_WithRepeatableFieldReference_AndLength() { + testExpressionTranslationWithContext( + "subsequence(PathNode/RepeatableTextField/normalize-space(text()), 1, 3)", "ND-Root", + "subsequence(BT-00-Repeatable-Text, 1, 3)"); + } + + // #endregion: Subsequence + // #region: Compare sequences @Test From 195ab2d9c1090d5fd0aa032f63095d72c1667a4c Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sat, 14 Feb 2026 16:34:49 +0100 Subject: [PATCH 11/31] TEDEFO-4788 Add index-of() function to EFX toolkit --- .../ted/efx/interfaces/ScriptGenerator.java | 3 ++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 41 +++++++++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 7 +++ .../sdk2/EfxExpressionTranslatorV2Test.java | 50 +++++++++++++++++++ 4 files changed, 101 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 988cd982..289945cd 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -540,6 +540,9 @@ public T composeSubsequenceFunction(T list, public T composeSubsequenceFunction(T list, NumericExpression start, NumericExpression length, Class listType); + public NumericSequenceExpression composeIndexOfFunction(SequenceExpression list, + ScalarExpression value); + public T composeIndexer(SequenceExpression list, NumericExpression index, Class type); diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index a14838dd..91903177 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2368,6 +2368,47 @@ private void exitSubsequenceFunction(boolean hasL // #endregion Subsequence ---------------------------------------------------- + // #region Index-of ---------------------------------------------------------- + + @Override + public void exitIndexOfStringFunction(IndexOfStringFunctionContext ctx) { + exitIndexOfFunction(StringSequenceExpression.class, StringExpression.class); + } + + @Override + public void exitIndexOfBooleanFunction(IndexOfBooleanFunctionContext ctx) { + exitIndexOfFunction(BooleanSequenceExpression.class, BooleanExpression.class); + } + + @Override + public void exitIndexOfNumericFunction(IndexOfNumericFunctionContext ctx) { + exitIndexOfFunction(NumericSequenceExpression.class, NumericExpression.class); + } + + @Override + public void exitIndexOfDateFunction(IndexOfDateFunctionContext ctx) { + exitIndexOfFunction(DateSequenceExpression.class, DateExpression.class); + } + + @Override + public void exitIndexOfTimeFunction(IndexOfTimeFunctionContext ctx) { + exitIndexOfFunction(TimeSequenceExpression.class, TimeExpression.class); + } + + @Override + public void exitIndexOfDurationFunction(IndexOfDurationFunctionContext ctx) { + exitIndexOfFunction(DurationSequenceExpression.class, DurationExpression.class); + } + + private void exitIndexOfFunction( + Class listType, Class valueType) { + final S value = this.stack.pop(valueType); + final T list = this.stack.pop(listType); + this.stack.push(this.script.composeIndexOfFunction(list, value)); + } + + // #endregion Index-of ------------------------------------------------------- + // #endregion Sequence Functions -------------------------------------------- // #region Helpers ---------------------------------------------------------- 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 d237b4f3..aa6cf4f2 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -687,6 +687,13 @@ public T composeSubsequenceFunction(T list, listType); } + @Override + public NumericSequenceExpression composeIndexOfFunction(SequenceExpression list, + ScalarExpression value) { + return new NumericSequenceExpression( + "index-of(" + list.getScript() + ", " + value.getScript() + ")"); + } + //#endregion Duration functions --------------------------------------------- @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 275809ee..530c1228 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -2155,6 +2155,56 @@ void testSubsequenceFunction_WithRepeatableFieldReference_AndLength() { // #endregion: Subsequence + // #region: Index-of + + @Test + void testIndexOfFunction_WithStringSequences() { + testExpressionTranslationWithContext("index-of(('a','b','c','b'), 'b')", "ND-Root", + "index-of(('a', 'b', 'c', 'b'), 'b')"); + } + + @Test + void testIndexOfFunction_WithNumberSequences() { + testExpressionTranslationWithContext("index-of((10,20,30,20), 20)", "ND-Root", + "index-of((10, 20, 30, 20), 20)"); + } + + @Test + void testIndexOfFunction_WithDateSequences() { + testExpressionTranslationWithContext( + "index-of((xs:date('2022-01-01Z'),xs:date('2023-01-01Z'),xs:date('2022-01-01Z')), xs:date('2022-01-01Z'))", + "ND-Root", "index-of((2022-01-01Z, 2023-01-01Z, 2022-01-01Z), 2022-01-01Z)"); + } + + @Test + void testIndexOfFunction_WithBooleanSequences() { + testExpressionTranslationWithContext("index-of((true(),false(),true()), true())", + "ND-Root", "index-of((TRUE, FALSE, TRUE), TRUE)"); + } + + @Test + void testIndexOfFunction_WithTimeSequences() { + testExpressionTranslationWithContext( + "index-of((xs:time('14:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')), xs:time('14:00:00Z'))", + "ND-Root", "index-of((14:00:00Z, 12:00:00Z, 14:00:00Z), 14:00:00Z)"); + } + + @Test + void testIndexOfFunction_WithDurationSequences() { + testExpressionTranslationWithContext( + "index-of((xs:dayTimeDuration('P5D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')), xs:dayTimeDuration('P5D'))", + "ND-Root", "index-of((P5D, P2D, P5D), P5D)"); + } + + @Test + void testIndexOfFunction_WithRepeatableFieldReference() { + testExpressionTranslationWithContext( + "index-of(PathNode/RepeatableTextField/normalize-space(text()), 'hello')", "ND-Root", + "index-of(BT-00-Repeatable-Text, 'hello')"); + } + + // #endregion: Index-of + // #region: Compare sequences @Test From 724adcc8bd410b3f87d555a3e81cab746b39fd3c Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 00:18:50 +0100 Subject: [PATCH 12/31] TEDEFO-4883 Add sequence is [not] empty and string empty() function --- .../ted/efx/interfaces/ScriptGenerator.java | 2 + .../efx/sdk2/EfxExpressionTranslatorV2.java | 51 +++++++++- .../ted/efx/xpath/XPathScriptGenerator.java | 5 + .../sdk2/EfxExpressionTranslatorV2Test.java | 99 ++++++++++++++++--- .../testAssertAndReport_Simple/input.efx | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-3.sch | 2 +- .../dynamic/validation-stage-1a-4.sch | 2 +- .../dynamic/validation-stage-1a-5.sch | 2 +- .../dynamic/validation-stage-1a-E1.sch | 2 +- .../dynamic/validation-stage-1a-E2.sch | 2 +- .../dynamic/validation-stage-1a-X01.sch | 2 +- .../testInClause_AllNoticeTypes/input.efx | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1a-3.sch | 2 +- .../static/validation-stage-1a-4.sch | 2 +- .../static/validation-stage-1a-5.sch | 2 +- .../static/validation-stage-1a-E1.sch | 2 +- .../static/validation-stage-1a-E2.sch | 2 +- .../static/validation-stage-1a-X01.sch | 2 +- .../dynamic/validation-stage-1-1.sch | 2 +- .../dynamic/validation-stage-1-3.sch | 2 +- .../testInClause_PhaseGeneration/input.efx | 2 +- .../static/validation-stage-1-1.sch | 2 +- .../static/validation-stage-1-3.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-2a-1.sch | 2 +- .../dynamic/validation-stage-2a-2.sch | 2 +- .../input.efx | 6 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-2a-1.sch | 2 +- .../static/validation-stage-2a-2.sch | 2 +- .../testOutput_FromSampleRulesFile/input.efx | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../input.efx | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1b-1.sch | 2 +- .../testVariable_StageLevel/input.efx | 4 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1b-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../testWhen_WithOtherwise/input.efx | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../testWithClause_ContextVariable/input.efx | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 2 +- .../dynamic/validation-stage-1a-2.sch | 2 +- .../input.efx | 2 +- .../static/validation-stage-1a-1.sch | 2 +- .../static/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1a-1.sch | 14 +-- .../dynamic/validation-stage-1a-2.sch | 2 +- .../dynamic/validation-stage-1b-1.sch | 2 +- .../input.efx | 18 ++-- .../static/validation-stage-1a-1.sch | 14 +-- .../static/validation-stage-1a-2.sch | 2 +- .../static/validation-stage-1b-1.sch | 2 +- 67 files changed, 228 insertions(+), 101 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 289945cd..89589413 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -467,6 +467,8 @@ public BooleanExpression composeUniqueValueCondition(DurationExpression needle, public BooleanExpression composeSequenceEqualFunction(SequenceExpression one, SequenceExpression two); + public BooleanExpression composeEmptySequenceCondition(SequenceExpression sequence); + // #endregion Boolean Functions -------------------------------------------- // #region Date Functions --------------------------------------------------- diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 91903177..01d04d2f 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -448,10 +448,9 @@ public void exitDurationComparison(DurationComparisonContext ctx) { // #region Boolean expressions - Conditions -------------------------------- @Override - public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { + public void exitStringEmptyFunction(EfxParser.StringEmptyFunctionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); - String operator = ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; - this.stack.push(this.script.composeComparisonOperation(expression, operator, + this.stack.push(this.script.composeComparisonOperation(expression, "==", this.script.getStringLiteralFromUnquotedString(""))); } @@ -543,6 +542,52 @@ public void exitDurationUniqueValueCondition(EfxParser.DurationUniqueValueCondit } } + @Override + public void exitStringSequenceEmptinessCondition( + EfxParser.StringSequenceEmptinessConditionContext ctx) { + exitSequenceEmptinessCondition(StringSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitBooleanSequenceEmptinessCondition( + EfxParser.BooleanSequenceEmptinessConditionContext ctx) { + exitSequenceEmptinessCondition(BooleanSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitNumericSequenceEmptinessCondition( + EfxParser.NumericSequenceEmptinessConditionContext ctx) { + exitSequenceEmptinessCondition(NumericSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitDateSequenceEmptinessCondition( + EfxParser.DateSequenceEmptinessConditionContext ctx) { + exitSequenceEmptinessCondition(DateSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitTimeSequenceEmptinessCondition( + EfxParser.TimeSequenceEmptinessConditionContext ctx) { + exitSequenceEmptinessCondition(TimeSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitDurationSequenceEmptinessCondition( + EfxParser.DurationSequenceEmptinessConditionContext ctx) { + exitSequenceEmptinessCondition(DurationSequenceExpression.class, ctx.modifier); + } + + private void exitSequenceEmptinessCondition( + Class sequenceType, org.antlr.v4.runtime.Token modifier) { + final T sequence = this.stack.pop(sequenceType); + BooleanExpression condition = this.script.composeEmptySequenceCondition(sequence); + if (modifier != null && modifier.getText().equals(NOT_MODIFIER)) { + condition = this.script.composeLogicalNot(condition); + } + this.stack.push(condition); + } + @Override public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index aa6cf4f2..1af49244 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -446,6 +446,11 @@ public BooleanExpression composeSequenceEqualFunction(SequenceExpression one, return new BooleanExpression("deep-equal(sort(" + one.getScript() + "), sort(" + two.getScript() + "))"); } + @Override + public BooleanExpression composeEmptySequenceCondition(SequenceExpression sequence) { + return new BooleanExpression("empty(" + sequence.getScript() + ")"); + } + //#endregion Boolean functions ---------------------------------------------- //#region Numeric functions ------------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 530c1228..93d35e47 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -52,18 +52,6 @@ void testInListCondition() { "'x' not in ('a', 'b', 'c')"); } - @Test - void testEmptinessCondition() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = ''", - "ND-Root", "BT-00-Text is empty"); - } - - @Test - void testEmptinessCondition_WithNot() { - testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) != ''", - "ND-Root", "BT-00-Text is not empty"); - } - @Test void testPresenceCondition() { testExpressionTranslationWithContext("PathNode/TextField", "ND-Root", "BT-00-Text is present"); @@ -2255,6 +2243,93 @@ void testSequenceEqualFunction_WithFieldReferences() { "sequence-equal(BT-00-Text, BT-00-Text)"); } + // #endregion: Compare sequences + + // #region: Sequence emptiness + + @Test + void testSequenceEmptiness_WithNonRepeatableField() { + // Field references always go through sequence emptiness, regardless of repeatability. + testExpressionTranslationWithContext("empty(PathNode/TextField/normalize-space(text()))", + "ND-Root", "BT-00-Text is empty"); + } + + @Test + void testSequenceEmptiness_WithStringSequence() { + testExpressionTranslationWithContext("empty(('a','b','c'))", "ND-Root", + "('a', 'b', 'c') is empty"); + } + + @Test + void testSequenceEmptiness_WithStringSequence_Negated() { + testExpressionTranslationWithContext("not(empty(('a','b','c')))", "ND-Root", + "('a', 'b', 'c') is not empty"); + } + + @Test + void testSequenceEmptiness_WithNumericSequence() { + testExpressionTranslationWithContext("empty((1,2,3))", "ND-Root", + "(1, 2, 3) is empty"); + } + + @Test + void testSequenceEmptiness_WithBooleanSequence() { + testExpressionTranslationWithContext("empty((true(),false()))", "ND-Root", + "(TRUE, FALSE) is empty"); + } + + @Test + void testSequenceEmptiness_WithDateSequence() { + testExpressionTranslationWithContext( + "empty((xs:date('2024-01-01Z'),xs:date('2024-12-31Z')))", "ND-Root", + "(2024-01-01Z, 2024-12-31Z) is empty"); + } + + @Test + void testSequenceEmptiness_WithTimeSequence() { + testExpressionTranslationWithContext( + "empty((xs:time('12:00:00Z'),xs:time('13:00:00Z')))", "ND-Root", + "(12:00:00Z, 13:00:00Z) is empty"); + } + + @Test + void testSequenceEmptiness_WithDurationSequence() { + testExpressionTranslationWithContext( + "empty((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y')))", "ND-Root", + "(P1Y, P2Y) is empty"); + } + + @Test + void testSequenceEmptiness_WithRepeatableFieldReference() { + testExpressionTranslationWithContext( + "empty(PathNode/RepeatableTextField/normalize-space(text()))", "ND-Root", + "BT-00-Repeatable-Text is empty"); + } + + @Test + void testSequenceEmptiness_WithRepeatableFieldReference_Negated() { + testExpressionTranslationWithContext( + "not(empty(PathNode/RepeatableTextField/normalize-space(text())))", "ND-Root", + "BT-00-Repeatable-Text is not empty"); + } + + // #endregion: Sequence emptiness + + // #region: String empty function + + @Test + void testStringEmptyFunction() { + testExpressionTranslationWithContext("'hello' = ''", "ND-Root", "empty('hello')"); + } + + @Test + void testStringEmptyFunction_WithFieldReference() { + testExpressionTranslationWithContext("PathNode/TextField/normalize-space(text()) = ''", + "ND-Root", "empty(BT-00-Text)"); + } + + // #endregion: String empty function + @Test void testParameterizedExpression_WithStringParameter() { testExpressionTranslation("'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testAssertAndReport_Simple/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testAssertAndReport_Simple/input.efx index a1090ec3..fe66b4ea 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testAssertAndReport_Simple/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testAssertAndReport_Simple/input.efx @@ -8,6 +8,6 @@ WITH BT-00-Text AS ERROR R-K7P-M2Q FOR BT-00-Text IN 1 - REPORT BT-00-Text is empty + REPORT empty(BT-00-Text) AS WARNING R-X3F-N8W FOR BT-00-Text IN 1 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-1.sch index 8188422e..c1aed84a 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-2.sch index a52ec7c3..15f14dec 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-3.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-3.sch index 3d0fd20e..b98a033d 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-3.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-3.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-4.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-4.sch index c00bb50b..c5e3e5f5 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-4.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-4.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-5.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-5.sch index 8df534af..c23456f7 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-5.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-5.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E1.sch index 66319591..aa6a5dec 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E1.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E2.sch index fa15b55a..380cc4b3 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-E2.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-X01.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-X01.sch index 6854b7c9..2b030c92 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-X01.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/dynamic/validation-stage-1a-X01.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/input.efx index 5dcbfbb5..eb251650 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/input.efx @@ -8,6 +8,6 @@ WITH BT-00-Text AS ERROR R-K7P-M2Q FOR BT-00-Text IN * - ASSERT BT-00-Text is not empty + ASSERT not(empty(BT-00-Text)) AS WARNING R-X3F-N8W FOR BT-00-Text IN ANY diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-1.sch index 8188422e..c1aed84a 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-2.sch index a52ec7c3..15f14dec 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-3.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-3.sch index 3d0fd20e..b98a033d 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-3.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-3.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-4.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-4.sch index c00bb50b..c5e3e5f5 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-4.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-4.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-5.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-5.sch index 8df534af..c23456f7 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-5.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-5.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E1.sch index 66319591..aa6a5dec 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E1.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E2.sch index fa15b55a..380cc4b3 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-E2.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-X01.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-X01.sch index 6854b7c9..2b030c92 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-X01.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_AllNoticeTypes/static/validation-stage-1a-X01.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-1.sch index b84fd2a4..8d12ef68 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-1.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-3.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-3.sch index 9a303ee6..d6c2d6f5 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-3.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/dynamic/validation-stage-1-3.sch @@ -1,6 +1,6 @@ - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/input.efx index d8dc180e..6719b0bd 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/input.efx @@ -24,7 +24,7 @@ WITH BT-00-Text AS ERROR R-K7P-M2Q FOR BT-00-Text IN 1, 2 - ASSERT BT-00-Text is not empty + ASSERT not(empty(BT-00-Text)) AS WARNING R-X3F-N8W FOR BT-00-Text IN 1, 3 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-1.sch index b84fd2a4..8d12ef68 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-1.sch @@ -2,6 +2,6 @@ rule|text|R-K7P-M2Q - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-3.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-3.sch index 9a303ee6..d6c2d6f5 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-3.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testInClause_PhaseGeneration/static/validation-stage-1-3.sch @@ -1,6 +1,6 @@ - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-1.sch index a86e043e..bf9f8744 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-1.sch @@ -12,7 +12,7 @@ rule|text|R-Z8H-A3X - rule|text|R-F5V-T6B + rule|text|R-F5V-T6B rule|text|R-W1D-J2Y rule|text|R-Q7G-E4Z diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-2.sch index 0010b570..17875378 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-1a-2.sch @@ -12,7 +12,7 @@ rule|text|R-Z8H-A3X - rule|text|R-F5V-T6B + rule|text|R-F5V-T6B rule|text|R-W1D-J2Y rule|text|R-Q7G-E4Z diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-1.sch index 5eed45e1..4305445b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-1.sch @@ -2,7 +2,7 @@ rule|text|R-M3C-U8N - rule|text|R-S9L-R5K + rule|text|R-S9L-R5K rule|text|R-N6P-I2F diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-2.sch index ca07e512..73e0aaa2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/dynamic/validation-stage-2a-2.sch @@ -2,7 +2,7 @@ rule|text|R-M3C-U8N - rule|text|R-S9L-R5K + rule|text|R-S9L-R5K rule|text|R-N6P-I2F diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/input.efx index 93851c89..722ca645 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/input.efx @@ -9,7 +9,7 @@ WITH BT-00-Text ASSERT BT-00-Text is present AS ERROR R-K7P-M2Q FOR BT-00-Text IN 1, 2 - REPORT BT-00-Text is empty + REPORT empty(BT-00-Text) AS WARNING R-X3F-N8W FOR BT-00-Text IN 1, 2 WHEN BT-00-Text == 'open' @@ -36,7 +36,7 @@ WITH ND-SubNode WITH BT-00-Number WHEN BT-00-Number > 0 - REPORT BT-00-Text is not empty + REPORT not(empty(BT-00-Text)) AS INFO R-F5V-T6B FOR BT-00-Text IN 1, 2 WHEN BT-00-Number < 100 @@ -53,7 +53,7 @@ WITH BT-00-Indicator REPORT BT-00-Indicator is present AS INFO R-M3C-U8N FOR BT-00-Indicator IN 1, 2 - ASSERT BT-00-Text is not empty + ASSERT not(empty(BT-00-Text)) AS ERROR R-S9L-R5K FOR BT-00-Text IN 1, 2 WHEN BT-00-Indicator == TRUE diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-1.sch index a86e043e..bf9f8744 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-1.sch @@ -12,7 +12,7 @@ rule|text|R-Z8H-A3X - rule|text|R-F5V-T6B + rule|text|R-F5V-T6B rule|text|R-W1D-J2Y rule|text|R-Q7G-E4Z diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-2.sch index 0010b570..17875378 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-1a-2.sch @@ -12,7 +12,7 @@ rule|text|R-Z8H-A3X - rule|text|R-F5V-T6B + rule|text|R-F5V-T6B rule|text|R-W1D-J2Y rule|text|R-Q7G-E4Z diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-1.sch index 5eed45e1..4305445b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-1.sch @@ -2,7 +2,7 @@ rule|text|R-M3C-U8N - rule|text|R-S9L-R5K + rule|text|R-S9L-R5K rule|text|R-N6P-I2F diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-2.sch index ca07e512..73e0aaa2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_ComprehensiveMixedRules/static/validation-stage-2a-2.sch @@ -2,7 +2,7 @@ rule|text|R-M3C-U8N - rule|text|R-S9L-R5K + rule|text|R-S9L-R5K rule|text|R-N6P-I2F diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_FromSampleRulesFile/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_FromSampleRulesFile/input.efx index cd4e4844..498c79f0 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_FromSampleRulesFile/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testOutput_FromSampleRulesFile/input.efx @@ -54,7 +54,7 @@ LET text : $rootText = BT-00-Text; // Complex WHEN condition with variable WITH ND-SubNode -WHEN $rootText is not empty +WHEN not(empty($rootText)) ASSERT BT-00-Indicator is present AS ERROR R-F5V-T6B FOR BT-00-Indicator IN *; diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-1.sch index e1878f3c..8855a6ed 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-2.sch index 769a2f5b..985bcb13 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/dynamic/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/input.efx index 2f0d9ce1..1111ee6b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/input.efx @@ -8,6 +8,6 @@ LET text : $noticeType = "16"; ---- STAGE 1a ---- WITH ND-Root - ASSERT $sdkVersion is not empty + ASSERT not(empty($sdkVersion)) AS ERROR R-K7P-M2Q FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-1.sch index e1878f3c..8855a6ed 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-1.sch @@ -1,6 +1,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-2.sch index 769a2f5b..985bcb13 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_Global_AppearsBeforeIncludes/static/validation-stage-1a-2.sch @@ -1,6 +1,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1a-1.sch index d3235262..f8e573e9 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1b-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1b-1.sch index 9c10e96d..798823e2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1b-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/dynamic/validation-stage-1b-1.sch @@ -2,6 +2,6 @@ - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/input.efx index e39d8fb4..f5a52623 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/input.efx @@ -1,11 +1,11 @@ ---- STAGE 1a ---- WITH text : $stageVar = "first", BT-00-Text -ASSERT $stageVar is not empty +ASSERT not(empty($stageVar)) AS ERROR R-K7P-M2Q FOR BT-00-Text IN 1; ---- STAGE 1b ---- WITH text : $stageVar = "second", BT-00-Text -ASSERT $stageVar is not empty +ASSERT not(empty($stageVar)) AS ERROR R-X3F-N8W FOR BT-00-Text IN 1; \ No newline at end of file diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1a-1.sch index d3235262..f8e573e9 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1b-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1b-1.sch index 9c10e96d..798823e2 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1b-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testVariable_StageLevel/static/validation-stage-1b-1.sch @@ -2,6 +2,6 @@ - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/dynamic/validation-stage-1a-2.sch index e3457f4a..5b842b1a 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/dynamic/validation-stage-1a-2.sch @@ -3,6 +3,6 @@ rule|text|R-X3F-N8W rule|text|R-H9T-V5L - rule|text|R-B6J-C4R + rule|text|R-B6J-C4R diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/input.efx index e1a93042..d533058b 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/input.efx @@ -21,6 +21,6 @@ WITH BT-00-Text AS WARNING R-H9T-V5L FOR BT-00-Text IN 2 - OTHERWISE REPORT BT-00-Text is not empty + OTHERWISE REPORT not(empty(BT-00-Text)) AS INFO R-B6J-C4R FOR BT-00-Text IN 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/static/validation-stage-1a-2.sch index e3457f4a..5b842b1a 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWhen_WithOtherwise/static/validation-stage-1a-2.sch @@ -3,6 +3,6 @@ rule|text|R-X3F-N8W rule|text|R-H9T-V5L - rule|text|R-B6J-C4R + rule|text|R-B6J-C4R diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-1.sch index 135940d9..3bf3891f 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-2.sch index e221483a..846841da 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/dynamic/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/input.efx index edf7d629..50ed456f 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/input.efx @@ -5,6 +5,6 @@ ---- STAGE 1a ---- WITH context : $ctx = BT-00-Text - ASSERT $ctx is not empty + ASSERT not(empty($ctx)) AS ERROR R-K7P-M2Q FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-1.sch index 135940d9..3bf3891f 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-2.sch index e221483a..846841da 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariable/static/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch index 945ab7d3..21875594 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch index 21908528..4c98abf7 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/dynamic/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx index 92f1ca00..87f30e2e 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/input.efx @@ -5,6 +5,6 @@ ---- STAGE 1a ---- WITH context : $ctx = ND-Root - ASSERT $ctx::BT-00-Text is not empty + ASSERT not(empty($ctx::BT-00-Text)) AS ERROR R-CTX-001 FOR BT-00-Text IN 1, 2 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch index 945ab7d3..21875594 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-1.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch index 21908528..4c98abf7 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_ContextVariableOverride/static/validation-stage-1a-2.sch @@ -2,6 +2,6 @@ - rule|text|R-CTX-001 + rule|text|R-CTX-001 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-1.sch index c86e81e6..5c0079ce 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-1.sch @@ -9,30 +9,30 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q - rule|text|R-Y2N-G7S + rule|text|R-Y2N-G7S - rule|text|R-F5V-T6B + rule|text|R-F5V-T6B - rule|text|R-M3C-U8N + rule|text|R-M3C-U8N - rule|text|R-V4T-O7J + rule|text|R-V4T-O7J - rule|text|R-G2M-X6D + rule|text|R-G2M-X6D - rule|text|R-C1V-Z4H + rule|text|R-C1V-Z4H diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-2.sch index c2626ea8..219d94cb 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1a-2.sch @@ -9,7 +9,7 @@ - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W rule|text|R-D4K-P9M diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1b-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1b-1.sch index f5f07bc7..e8446a85 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1b-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/dynamic/validation-stage-1b-1.sch @@ -2,6 +2,6 @@ - rule|text|R-E3F-L2G + rule|text|R-E3F-L2G diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/input.efx b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/input.efx index 8ce5b661..2db111d8 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/input.efx +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/input.efx @@ -11,17 +11,17 @@ LET text : $stageVar2 = "S2"; // Case 1: No variables (baseline) - uses stage variable WITH BT-00-Text - ASSERT $stageVar1 is not empty + ASSERT not(empty($stageVar1)) AS ERROR R-K7P-M2Q FOR BT-00-Text IN 1 - ASSERT BT-00-Text is not empty + ASSERT not(empty(BT-00-Text)) AS ERROR R-X3F-N8W FOR BT-00-Text IN 2 // Case 2: Single variable before context (pattern-level) WITH text : $before1 = "b1", BT-00-Number - ASSERT $before1 is not empty + ASSERT not(empty($before1)) AS ERROR R-Y2N-G7S FOR BT-00-Number IN 1 @@ -31,7 +31,7 @@ WITH text : $before1 = "b1", BT-00-Number // Case 3: Multiple variables before context (pattern-level) WITH text : $preA = "A", text : $preB = "B", BT-00-Indicator - ASSERT $preA is not empty and $preB is not empty + ASSERT not(empty($preA)) and not(empty($preB)) AS ERROR R-F5V-T6B FOR BT-00-Indicator IN 1 @@ -41,7 +41,7 @@ WITH text : $preA = "A", text : $preB = "B", BT-00-Indicator // Case 4: Single variable after context (rule-level) - uses stage variable WITH BT-00-Text, text : $after1 = "a1" - ASSERT $stageVar2 is not empty and $after1 is not empty + ASSERT not(empty($stageVar2)) and not(empty($after1)) AS ERROR R-M3C-U8N FOR BT-00-Text IN 1 @@ -51,7 +51,7 @@ WITH BT-00-Text, text : $after1 = "a1" // Case 5: Multiple variables after context (rule-level) WITH BT-00-Number, text : $postX = "X", text : $postY = "Y" - ASSERT $postX is not empty and $postY is not empty + ASSERT not(empty($postX)) and not(empty($postY)) AS ERROR R-V4T-O7J FOR BT-00-Number IN 1 @@ -61,7 +61,7 @@ WITH BT-00-Number, text : $postX = "X", text : $postY = "Y" // Case 6: One before, one after (mixed pattern/rule level) WITH text : $left = "L", BT-00-Indicator, text : $right = "R" - ASSERT $left is not empty and $right is not empty + ASSERT not(empty($left)) and not(empty($right)) AS ERROR R-G2M-X6D FOR BT-00-Indicator IN 1 @@ -71,7 +71,7 @@ WITH text : $left = "L", BT-00-Indicator, text : $right = "R" // Case 7: Multiple before and after (full mixed) - uses both stage variables WITH text : $p1 = "P1", text : $p2 = "P2", BT-00-Text, text : $r1 = "R1", text : $r2 = "R2" - ASSERT $stageVar1 is not empty and $stageVar2 is not empty and $p1 is not empty + ASSERT not(empty($stageVar1)) and not(empty($stageVar2)) and not(empty($p1)) AS ERROR R-C1V-Z4H FOR BT-00-Text IN 1 @@ -86,7 +86,7 @@ LET text : $stage1bVar = "1B"; // Simple rule using stage 1b variable WITH BT-00-Text - ASSERT $stage1bVar is not empty + ASSERT not(empty($stage1bVar)) AS ERROR R-E3F-L2G FOR BT-00-Text IN 1 diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-1.sch index c86e81e6..5c0079ce 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-1.sch @@ -9,30 +9,30 @@ - rule|text|R-K7P-M2Q + rule|text|R-K7P-M2Q - rule|text|R-Y2N-G7S + rule|text|R-Y2N-G7S - rule|text|R-F5V-T6B + rule|text|R-F5V-T6B - rule|text|R-M3C-U8N + rule|text|R-M3C-U8N - rule|text|R-V4T-O7J + rule|text|R-V4T-O7J - rule|text|R-G2M-X6D + rule|text|R-G2M-X6D - rule|text|R-C1V-Z4H + rule|text|R-C1V-Z4H diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-2.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-2.sch index c2626ea8..219d94cb 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-2.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1a-2.sch @@ -9,7 +9,7 @@ - rule|text|R-X3F-N8W + rule|text|R-X3F-N8W rule|text|R-D4K-P9M diff --git a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1b-1.sch b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1b-1.sch index f5f07bc7..e8446a85 100644 --- a/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1b-1.sch +++ b/src/test/resources/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2Test/testWithClause_VariablePositioning/static/validation-stage-1b-1.sch @@ -2,6 +2,6 @@ - rule|text|R-E3F-L2G + rule|text|R-E3F-L2G From 53d3fbf4400fb7a80b3807b42131b263c0950030 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 01:19:21 +0100 Subject: [PATCH 13/31] TEDEFO-4880 Add sequence has [no] duplicates and clean up imports --- .../ted/efx/interfaces/ScriptGenerator.java | 2 + .../efx/sdk1/EfxExpressionTranslatorV1.java | 36 +++--- .../ted/efx/sdk1/EfxTemplateTranslatorV1.java | 18 +-- .../efx/sdk2/EfxExpressionTranslatorV2.java | 120 ++++++++++++------ .../ted/efx/sdk2/EfxRulesTranslatorV2.java | 73 ++++------- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 78 +----------- .../ted/efx/xpath/XPathScriptGenerator.java | 6 + .../sdk2/EfxExpressionTranslatorV2Test.java | 80 ++++++++++++ 8 files changed, 219 insertions(+), 194 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 89589413..4c6b6e02 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -469,6 +469,8 @@ public BooleanExpression composeSequenceEqualFunction(SequenceExpression one, public BooleanExpression composeEmptySequenceCondition(SequenceExpression sequence); + public BooleanExpression composeIsDistinctCondition(SequenceExpression sequence); + // #endregion Boolean Functions -------------------------------------------- // #region Date Functions --------------------------------------------------- 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 1484dd70..894b5afc 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java @@ -315,20 +315,20 @@ public void exitSingleExpression(SingleExpressionContext ctx) { @Override public void exitParenthesizedBooleanExpression( - EfxParser.ParenthesizedBooleanExpressionContext ctx) { + ParenthesizedBooleanExpressionContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(BooleanExpression.class), BooleanExpression.class)); } @Override - public void exitLogicalAndCondition(EfxParser.LogicalAndConditionContext ctx) { + public void exitLogicalAndCondition(LogicalAndConditionContext ctx) { BooleanExpression right = this.stack.pop(BooleanExpression.class); BooleanExpression left = this.stack.pop(BooleanExpression.class); this.stack.push(this.script.composeLogicalAnd(left, right)); } @Override - public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { + public void exitLogicalOrCondition(LogicalOrConditionContext ctx) { BooleanExpression right = this.stack.pop(BooleanExpression.class); BooleanExpression left = this.stack.pop(BooleanExpression.class); this.stack.push(this.script.composeLogicalOr(left, right)); @@ -393,7 +393,7 @@ public void exitDurationComparison(DurationComparisonContext ctx) { // #region Boolean expressions - Conditions -------------------------------- @Override - public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { + public void exitEmptinessCondition(EmptinessConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); String operator = ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER) ? "!=" : "=="; this.stack.push(this.script.composeComparisonOperation(expression, operator, @@ -401,7 +401,7 @@ public void exitEmptinessCondition(EfxParser.EmptinessConditionContext ctx) { } @Override - public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { + public void exitPresenceCondition(PresenceConditionContext ctx) { PathExpression reference = this.stack.pop(PathExpression.class); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { this.stack.push(this.script.composeLogicalNot(this.script.composeExistsCondition(reference))); @@ -411,7 +411,7 @@ public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { } @Override - public void exitUniqueValueCondition(EfxParser.UniqueValueConditionContext ctx) { + public void exitUniqueValueCondition(UniqueValueConditionContext ctx) { PathExpression haystack = this.stack.pop(PathExpression.class); PathExpression needle = this.stack.pop(haystack.getClass()); @@ -424,7 +424,7 @@ public void exitUniqueValueCondition(EfxParser.UniqueValueConditionContext ctx) } @Override - public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { + public void exitLikePatternCondition(LikePatternConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); BooleanExpression condition = this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { @@ -438,7 +438,7 @@ public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) // #region Boolean expressions - List membership conditions ----------------- @Override - public void exitStringInListCondition(EfxParser.StringInListConditionContext ctx) { + public void exitStringInListCondition(StringInListConditionContext ctx) { this.exitInListCondition(ctx.modifier, StringExpression.class, StringSequenceExpression.class); } @@ -501,14 +501,14 @@ public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { // #region Numeric expressions ---------------------------------------------- @Override - public void exitAdditionExpression(EfxParser.AdditionExpressionContext ctx) { + public void exitAdditionExpression(AdditionExpressionContext ctx) { NumericExpression right = this.stack.pop(NumericExpression.class); NumericExpression left = this.stack.pop(NumericExpression.class); this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); } @Override - public void exitMultiplicationExpression(EfxParser.MultiplicationExpressionContext ctx) { + public void exitMultiplicationExpression(MultiplicationExpressionContext ctx) { NumericExpression right = this.stack.pop(NumericExpression.class); NumericExpression left = this.stack.pop(NumericExpression.class); this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); @@ -930,7 +930,7 @@ public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { } @Override - public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx) { + public void exitSimpleFieldReference(SimpleFieldReferenceContext ctx) { this.stack.push( symbols.getRelativePathOfField(ctx.FieldId().getText(), this.efxContext.symbol())); } @@ -943,14 +943,14 @@ public void enterAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { } @Override - public void exitAbsoluteFieldReference(EfxParser.AbsoluteFieldReferenceContext ctx) { + public void exitAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { if (ctx.Slash() != null) { this.efxContext.pop(); } } @Override - public void enterAbsoluteNodeReference(EfxParser.AbsoluteNodeReferenceContext ctx) { + public void enterAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { if (ctx.Slash() != null) { this.efxContext.push(null); } @@ -976,7 +976,7 @@ public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx } @Override - public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicateContext ctx) { + public void exitFieldReferenceWithPredicate(FieldReferenceWithPredicateContext ctx) { if (ctx.predicate() != null) { BooleanExpression predicate = this.stack.pop(BooleanExpression.class); PathExpression fieldReference = this.stack.pop(PathExpression.class); @@ -992,7 +992,7 @@ public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicat * @param ctx The predicate context */ @Override - public void enterPredicate(EfxParser.PredicateContext ctx) { + public void enterPredicate(PredicateContext ctx) { var parent = ctx.getParent(); if (parent instanceof NodeReferenceWithPredicateContext) { final String nodeId = getNodeId((NodeReferenceWithPredicateContext) parent); @@ -1009,7 +1009,7 @@ public void enterPredicate(EfxParser.PredicateContext ctx) { * After the predicate is parsed we need to switch back to the previous context. */ @Override - public void exitPredicate(EfxParser.PredicateContext ctx) { + public void exitPredicate(PredicateContext ctx) { this.efxContext.pop(); } @@ -1026,7 +1026,7 @@ public void exitFieldReferenceWithAxis(FieldReferenceWithAxisContext ctx) { // #region External References ---------------------------------------------- @Override - public void exitNoticeReference(EfxParser.NoticeReferenceContext ctx) { + public void exitNoticeReference(NoticeReferenceContext ctx) { this.stack.push(this.script.composeExternalReference(this.stack.pop(StringExpression.class))); } @@ -1039,7 +1039,7 @@ public void enterFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext } @Override - public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNoticeContext ctx) { + public void exitFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext ctx) { if (ctx.noticeReference() != null) { PathExpression field = this.stack.pop(PathExpression.class); PathExpression notice = this.stack.pop(PathExpression.class); 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 bad88c44..1d21346b 100644 --- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java +++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java @@ -56,23 +56,7 @@ import eu.europa.ted.efx.model.types.EfxDataType; import eu.europa.ted.efx.model.variables.Variable; import eu.europa.ted.efx.model.variables.Variables; -import eu.europa.ted.efx.sdk1.EfxParser.AssetIdContext; -import eu.europa.ted.efx.sdk1.EfxParser.AssetTypeContext; -import eu.europa.ted.efx.sdk1.EfxParser.ContextDeclarationBlockContext; -import eu.europa.ted.efx.sdk1.EfxParser.ExpressionTemplateContext; -import eu.europa.ted.efx.sdk1.EfxParser.LabelTemplateContext; -import eu.europa.ted.efx.sdk1.EfxParser.LabelTypeContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandBtLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandFieldValueReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandIndirectLabelReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk1.EfxParser.ShorthandLabelReferenceFromContextContext; -import eu.europa.ted.efx.sdk1.EfxParser.StandardExpressionBlockContext; -import eu.europa.ted.efx.sdk1.EfxParser.StandardLabelReferenceContext; -import eu.europa.ted.efx.sdk1.EfxParser.TemplateFileContext; -import eu.europa.ted.efx.sdk1.EfxParser.TemplateLineContext; -import eu.europa.ted.efx.sdk1.EfxParser.TextTemplateContext; +import eu.europa.ted.efx.sdk1.EfxParser.*; /** * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV1} to provide additional diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 01d04d2f..01fb6292 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -105,6 +105,8 @@ public class EfxExpressionTranslatorV2 extends EfxBaseListener private static final String NOT_MODIFIER = EfxLexer.VOCABULARY.getLiteralName(EfxLexer.Not).replaceAll("^'|'$", ""); + private static final String NO_MODIFIER = + EfxLexer.VOCABULARY.getLiteralName(EfxLexer.No).replaceAll("^'|'$", ""); private static final String BEGIN_EXPRESSION_BLOCK = "{"; private static final String END_EXPRESSION_BLOCK = "}"; @@ -237,7 +239,7 @@ protected String getFieldId(LinkedFieldReferenceContext ctx) { return this.getLinkedFieldId(baseFieldId, ctx.linkedFieldProperty()); } - protected String getFieldId(EfxParser.FieldMentionContext ctx) { + protected String getFieldId(FieldMentionContext ctx) { if (ctx == null) { return null; } @@ -380,20 +382,20 @@ public void exitSingleExpression(SingleExpressionContext ctx) { @Override public void exitParenthesizedBooleanExpression( - EfxParser.ParenthesizedBooleanExpressionContext ctx) { + ParenthesizedBooleanExpressionContext ctx) { this.stack.push(this.script.composeParenthesizedExpression( this.stack.pop(BooleanExpression.class), BooleanExpression.class)); } @Override - public void exitLogicalAndCondition(EfxParser.LogicalAndConditionContext ctx) { + public void exitLogicalAndCondition(LogicalAndConditionContext ctx) { BooleanExpression right = this.stack.pop(BooleanExpression.class); BooleanExpression left = this.stack.pop(BooleanExpression.class); this.stack.push(this.script.composeLogicalAnd(left, right)); } @Override - public void exitLogicalOrCondition(EfxParser.LogicalOrConditionContext ctx) { + public void exitLogicalOrCondition(LogicalOrConditionContext ctx) { BooleanExpression right = this.stack.pop(BooleanExpression.class); BooleanExpression left = this.stack.pop(BooleanExpression.class); this.stack.push(this.script.composeLogicalOr(left, right)); @@ -448,14 +450,14 @@ public void exitDurationComparison(DurationComparisonContext ctx) { // #region Boolean expressions - Conditions -------------------------------- @Override - public void exitStringEmptyFunction(EfxParser.StringEmptyFunctionContext ctx) { + public void exitStringEmptyFunction(StringEmptyFunctionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); this.stack.push(this.script.composeComparisonOperation(expression, "==", this.script.getStringLiteralFromUnquotedString(""))); } @Override - public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { + public void exitPresenceCondition(PresenceConditionContext ctx) { PathExpression reference = this.stack.pop(PathExpression.class); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { this.stack.push(this.script.composeLogicalNot(this.script.composeExistsCondition(reference))); @@ -465,7 +467,7 @@ public void exitPresenceCondition(EfxParser.PresenceConditionContext ctx) { } @Override - public void exitStringUniqueValueCondition(EfxParser.StringUniqueValueConditionContext ctx) { + public void exitStringUniqueValueCondition(StringUniqueValueConditionContext ctx) { StringSequenceExpression haystack = this.stack.pop(StringSequenceExpression.class); StringExpression needle = this.stack.pop(StringExpression.class); @@ -478,7 +480,7 @@ public void exitStringUniqueValueCondition(EfxParser.StringUniqueValueConditionC } @Override - public void exitNumericUniqueValueCondition(EfxParser.NumericUniqueValueConditionContext ctx) { + public void exitNumericUniqueValueCondition(NumericUniqueValueConditionContext ctx) { NumericSequenceExpression haystack = this.stack.pop(NumericSequenceExpression.class); NumericExpression needle = this.stack.pop(NumericExpression.class); @@ -491,7 +493,7 @@ public void exitNumericUniqueValueCondition(EfxParser.NumericUniqueValueConditio } @Override - public void exitBooleanUniqueValueCondition(EfxParser.BooleanUniqueValueConditionContext ctx) { + public void exitBooleanUniqueValueCondition(BooleanUniqueValueConditionContext ctx) { BooleanSequenceExpression haystack = this.stack.pop(BooleanSequenceExpression.class); BooleanExpression needle = this.stack.pop(BooleanExpression.class); @@ -504,7 +506,7 @@ public void exitBooleanUniqueValueCondition(EfxParser.BooleanUniqueValueConditio } @Override - public void exitDateUniqueValueCondition(EfxParser.DateUniqueValueConditionContext ctx) { + public void exitDateUniqueValueCondition(DateUniqueValueConditionContext ctx) { DateSequenceExpression haystack = this.stack.pop(DateSequenceExpression.class); DateExpression needle = this.stack.pop(DateExpression.class); @@ -517,7 +519,7 @@ public void exitDateUniqueValueCondition(EfxParser.DateUniqueValueConditionConte } @Override - public void exitTimeUniqueValueCondition(EfxParser.TimeUniqueValueConditionContext ctx) { + public void exitTimeUniqueValueCondition(TimeUniqueValueConditionContext ctx) { TimeSequenceExpression haystack = this.stack.pop(TimeSequenceExpression.class); TimeExpression needle = this.stack.pop(TimeExpression.class); @@ -530,7 +532,7 @@ public void exitTimeUniqueValueCondition(EfxParser.TimeUniqueValueConditionConte } @Override - public void exitDurationUniqueValueCondition(EfxParser.DurationUniqueValueConditionContext ctx) { + public void exitDurationUniqueValueCondition(DurationUniqueValueConditionContext ctx) { DurationSequenceExpression haystack = this.stack.pop(DurationSequenceExpression.class); DurationExpression needle = this.stack.pop(DurationExpression.class); @@ -544,42 +546,42 @@ public void exitDurationUniqueValueCondition(EfxParser.DurationUniqueValueCondit @Override public void exitStringSequenceEmptinessCondition( - EfxParser.StringSequenceEmptinessConditionContext ctx) { + StringSequenceEmptinessConditionContext ctx) { exitSequenceEmptinessCondition(StringSequenceExpression.class, ctx.modifier); } @Override public void exitBooleanSequenceEmptinessCondition( - EfxParser.BooleanSequenceEmptinessConditionContext ctx) { + BooleanSequenceEmptinessConditionContext ctx) { exitSequenceEmptinessCondition(BooleanSequenceExpression.class, ctx.modifier); } @Override public void exitNumericSequenceEmptinessCondition( - EfxParser.NumericSequenceEmptinessConditionContext ctx) { + NumericSequenceEmptinessConditionContext ctx) { exitSequenceEmptinessCondition(NumericSequenceExpression.class, ctx.modifier); } @Override public void exitDateSequenceEmptinessCondition( - EfxParser.DateSequenceEmptinessConditionContext ctx) { + DateSequenceEmptinessConditionContext ctx) { exitSequenceEmptinessCondition(DateSequenceExpression.class, ctx.modifier); } @Override public void exitTimeSequenceEmptinessCondition( - EfxParser.TimeSequenceEmptinessConditionContext ctx) { + TimeSequenceEmptinessConditionContext ctx) { exitSequenceEmptinessCondition(TimeSequenceExpression.class, ctx.modifier); } @Override public void exitDurationSequenceEmptinessCondition( - EfxParser.DurationSequenceEmptinessConditionContext ctx) { + DurationSequenceEmptinessConditionContext ctx) { exitSequenceEmptinessCondition(DurationSequenceExpression.class, ctx.modifier); } private void exitSequenceEmptinessCondition( - Class sequenceType, org.antlr.v4.runtime.Token modifier) { + Class sequenceType, Token modifier) { final T sequence = this.stack.pop(sequenceType); BooleanExpression condition = this.script.composeEmptySequenceCondition(sequence); if (modifier != null && modifier.getText().equals(NOT_MODIFIER)) { @@ -589,7 +591,53 @@ private void exitSequenceEmptinessCondition( } @Override - public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) { + public void exitStringSequenceDistinctCondition( + StringSequenceDistinctConditionContext ctx) { + exitSequenceDistinctCondition(StringSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitBooleanSequenceDistinctCondition( + BooleanSequenceDistinctConditionContext ctx) { + exitSequenceDistinctCondition(BooleanSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitNumericSequenceDistinctCondition( + NumericSequenceDistinctConditionContext ctx) { + exitSequenceDistinctCondition(NumericSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitDateSequenceDistinctCondition( + DateSequenceDistinctConditionContext ctx) { + exitSequenceDistinctCondition(DateSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitTimeSequenceDistinctCondition( + TimeSequenceDistinctConditionContext ctx) { + exitSequenceDistinctCondition(TimeSequenceExpression.class, ctx.modifier); + } + + @Override + public void exitDurationSequenceDistinctCondition( + DurationSequenceDistinctConditionContext ctx) { + exitSequenceDistinctCondition(DurationSequenceExpression.class, ctx.modifier); + } + + private void exitSequenceDistinctCondition( + Class sequenceType, Token modifier) { + final T sequence = this.stack.pop(sequenceType); + BooleanExpression condition = this.script.composeIsDistinctCondition(sequence); + if (modifier == null) { + condition = this.script.composeLogicalNot(condition); + } + this.stack.push(condition); + } + + @Override + public void exitLikePatternCondition(LikePatternConditionContext ctx) { StringExpression expression = this.stack.pop(StringExpression.class); BooleanExpression condition = this.script.composePatternMatchCondition(expression, ctx.pattern.getText()); if (ctx.modifier != null && ctx.modifier.getText().equals(NOT_MODIFIER)) { @@ -603,7 +651,7 @@ public void exitLikePatternCondition(EfxParser.LikePatternConditionContext ctx) // #region Boolean expressions - List membership conditions ----------------- @Override - public void exitStringInListCondition(EfxParser.StringInListConditionContext ctx) { + public void exitStringInListCondition(StringInListConditionContext ctx) { this.exitInListCondition(ctx.modifier, StringExpression.class, StringSequenceExpression.class); } @@ -674,14 +722,14 @@ public void exitQuantifiedExpression(QuantifiedExpressionContext ctx) { // #region Numeric expressions ---------------------------------------------- @Override - public void exitAdditionExpression(EfxParser.AdditionExpressionContext ctx) { + public void exitAdditionExpression(AdditionExpressionContext ctx) { NumericExpression right = this.stack.pop(NumericExpression.class); NumericExpression left = this.stack.pop(NumericExpression.class); this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); } @Override - public void exitMultiplicationExpression(EfxParser.MultiplicationExpressionContext ctx) { + public void exitMultiplicationExpression(MultiplicationExpressionContext ctx) { NumericExpression right = this.stack.pop(NumericExpression.class); NumericExpression left = this.stack.pop(NumericExpression.class); this.stack.push(this.script.composeNumericOperation(left, ctx.operator.getText(), right)); @@ -1175,7 +1223,7 @@ public void exitSimpleNodeReference(SimpleNodeReferenceContext ctx) { } @Override - public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx) { + public void exitSimpleFieldReference(SimpleFieldReferenceContext ctx) { this.stack.push( symbols.getRelativePathOfField(ctx.fieldId.getText(), this.efxContext.symbol())); } @@ -1198,14 +1246,14 @@ public void enterAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { } @Override - public void exitAbsoluteFieldReference(EfxParser.AbsoluteFieldReferenceContext ctx) { + public void exitAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { if (ctx.Slash() != null) { this.efxContext.pop(); } } @Override - public void enterAbsoluteNodeReference(EfxParser.AbsoluteNodeReferenceContext ctx) { + public void enterAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { if (ctx.Slash() != null) { this.efxContext.push(null); } @@ -1231,7 +1279,7 @@ public void exitNodeReferenceWithPredicate(NodeReferenceWithPredicateContext ctx } @Override - public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicateContext ctx) { + public void exitFieldReferenceWithPredicate(FieldReferenceWithPredicateContext ctx) { if (ctx.predicate() != null) { BooleanExpression predicate = this.stack.pop(BooleanExpression.class); PathExpression fieldReference = this.stack.pop(PathExpression.class); @@ -1247,7 +1295,7 @@ public void exitFieldReferenceWithPredicate(EfxParser.FieldReferenceWithPredicat * @param ctx The predicate context */ @Override - public void enterPredicate(EfxParser.PredicateContext ctx) { + public void enterPredicate(PredicateContext ctx) { var parent = ctx.getParent(); if (parent instanceof NodeReferenceWithPredicateContext) { final String nodeId = getNodeId((NodeReferenceWithPredicateContext) parent); @@ -1264,7 +1312,7 @@ public void enterPredicate(EfxParser.PredicateContext ctx) { * After the predicate is parsed we need to switch back to the previous context. */ @Override - public void exitPredicate(EfxParser.PredicateContext ctx) { + public void exitPredicate(PredicateContext ctx) { this.efxContext.pop(); } @@ -1281,7 +1329,7 @@ public void exitFieldReferenceWithAxis(FieldReferenceWithAxisContext ctx) { // #region External References ---------------------------------------------- @Override - public void exitNoticeReference(EfxParser.NoticeReferenceContext ctx) { + public void exitNoticeReference(NoticeReferenceContext ctx) { this.stack.push(this.script.composeExternalReference(this.stack.pop(StringExpression.class))); } @@ -1294,7 +1342,7 @@ public void enterFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext } @Override - public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNoticeContext ctx) { + public void exitFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext ctx) { if (ctx.noticeReference() != null) { PathExpression field = this.stack.pop(PathExpression.class); PathExpression notice = this.stack.pop(PathExpression.class); @@ -2609,7 +2657,7 @@ public void exitSingleExpression(SingleExpressionContext ctx) { } @Override - public void enterPredicate(EfxParser.PredicateContext ctx) { + public void enterPredicate(PredicateContext ctx) { var parent = ctx.getParent(); if (parent instanceof NodeReferenceWithPredicateContext) { final String nodeId = getNodeId((NodeReferenceWithPredicateContext) parent); @@ -2623,7 +2671,7 @@ public void enterPredicate(EfxParser.PredicateContext ctx) { } @Override - public void exitPredicate(EfxParser.PredicateContext ctx) { + public void exitPredicate(PredicateContext ctx) { this.efxContext.pop(); } @@ -2635,14 +2683,14 @@ public void enterAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { } @Override - public void exitAbsoluteFieldReference(EfxParser.AbsoluteFieldReferenceContext ctx) { + public void exitAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) { if (ctx.Slash() != null) { this.efxContext.pop(); } } @Override - public void enterAbsoluteNodeReference(EfxParser.AbsoluteNodeReferenceContext ctx) { + public void enterAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) { if (ctx.Slash() != null) { this.efxContext.push(null); } @@ -2663,7 +2711,7 @@ public void enterFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext } @Override - public void exitFieldReferenceInOtherNotice(EfxParser.FieldReferenceInOtherNoticeContext ctx) { + public void exitFieldReferenceInOtherNotice(FieldReferenceInOtherNoticeContext ctx) { if (ctx.noticeReference() != null) { this.efxContext.pop(); } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2.java index d2a2a318..8f9b0bcc 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxRulesTranslatorV2.java @@ -64,26 +64,7 @@ import eu.europa.ted.efx.model.rules.ValidationStage; import eu.europa.ted.efx.model.types.FieldTypes; import eu.europa.ted.efx.model.variables.Variable; -import eu.europa.ted.efx.sdk2.EfxParser.AnyNoticeTypesContext; -import eu.europa.ted.efx.sdk2.EfxParser.AsClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.AssertClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.ConditionalRuleContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.FallbackRuleContext; -import eu.europa.ted.efx.sdk2.EfxParser.ForClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.GlobalVariableDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.InClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.OtherwiseAssertClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.OtherwiseReportClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.StageVariableDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.ReportClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.RuleSetContext; -import eu.europa.ted.efx.sdk2.EfxParser.SimpleRuleContext; -import eu.europa.ted.efx.sdk2.EfxParser.ValidationStageContext; -import eu.europa.ted.efx.sdk2.EfxParser.VariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.WhenClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.WithClauseContext; +import eu.europa.ted.efx.sdk2.EfxParser.*; /** * EFX Rules translator for SDK version 2. @@ -219,37 +200,37 @@ private void exitVariableInitializer(String variableName, @Override public void exitStringVariableInitializer( - EfxParser.StringVariableInitializerContext ctx) { + StringVariableInitializerContext ctx) { this.exitVariableInitializer(ctx.variableName.getText(), StringExpression.class); } @Override public void exitBooleanVariableInitializer( - EfxParser.BooleanVariableInitializerContext ctx) { + BooleanVariableInitializerContext ctx) { this.exitVariableInitializer(ctx.variableName.getText(), BooleanExpression.class); } @Override public void exitNumericVariableInitializer( - EfxParser.NumericVariableInitializerContext ctx) { + NumericVariableInitializerContext ctx) { this.exitVariableInitializer(ctx.variableName.getText(), NumericExpression.class); } @Override public void exitDateVariableInitializer( - EfxParser.DateVariableInitializerContext ctx) { + DateVariableInitializerContext ctx) { this.exitVariableInitializer(ctx.variableName.getText(), DateExpression.class); } @Override public void exitTimeVariableInitializer( - EfxParser.TimeVariableInitializerContext ctx) { + TimeVariableInitializerContext ctx) { this.exitVariableInitializer(ctx.variableName.getText(), TimeExpression.class); } @Override public void exitDurationVariableInitializer( - EfxParser.DurationVariableInitializerContext ctx) { + DurationVariableInitializerContext ctx) { this.exitVariableInitializer(ctx.variableName.getText(), DurationExpression.class); } @@ -269,37 +250,37 @@ private void exitSequenceVariableInitializer(String variableName, @Override public void exitStringSequenceVariableInitializer( - EfxParser.StringSequenceVariableInitializerContext ctx) { + StringSequenceVariableInitializerContext ctx) { this.exitSequenceVariableInitializer(ctx.variableName.getText(), StringSequenceExpression.class); } @Override public void exitBooleanSequenceVariableInitializer( - EfxParser.BooleanSequenceVariableInitializerContext ctx) { + BooleanSequenceVariableInitializerContext ctx) { this.exitSequenceVariableInitializer(ctx.variableName.getText(), BooleanSequenceExpression.class); } @Override public void exitNumericSequenceVariableInitializer( - EfxParser.NumericSequenceVariableInitializerContext ctx) { + NumericSequenceVariableInitializerContext ctx) { this.exitSequenceVariableInitializer(ctx.variableName.getText(), NumericSequenceExpression.class); } @Override public void exitDateSequenceVariableInitializer( - EfxParser.DateSequenceVariableInitializerContext ctx) { + DateSequenceVariableInitializerContext ctx) { this.exitSequenceVariableInitializer(ctx.variableName.getText(), DateSequenceExpression.class); } @Override public void exitTimeSequenceVariableInitializer( - EfxParser.TimeSequenceVariableInitializerContext ctx) { + TimeSequenceVariableInitializerContext ctx) { this.exitSequenceVariableInitializer(ctx.variableName.getText(), TimeSequenceExpression.class); } @Override public void exitDurationSequenceVariableInitializer( - EfxParser.DurationSequenceVariableInitializerContext ctx) { + DurationSequenceVariableInitializerContext ctx) { this.exitSequenceVariableInitializer(ctx.variableName.getText(), DurationSequenceExpression.class); } @@ -699,7 +680,7 @@ public void exitGlobalVariableDeclaration(GlobalVariableDeclarationContext ctx) */ @Override public void exitStageVariableDeclaration( - EfxParser.StageVariableDeclarationContext ctx) { + StageVariableDeclarationContext ctx) { if (!this.stack.empty()) { Variable variable = this.stack.pop(Variable.class); this.stack.declareIdentifier(variable); @@ -797,7 +778,7 @@ public void exitRuleSet(RuleSetContext ctx) { */ @Override public void exitVariableInitializer( - EfxParser.VariableInitializerContext ctx) { + VariableInitializerContext ctx) { Variable variable = this.stack.pop(Variable.class); this.stack.declareIdentifier(variable); } @@ -811,37 +792,37 @@ public void exitVariableInitializer( * These create simple Variable objects with empty expressions just for type tracking. */ @Override - public void exitStringVariableInitializer(EfxParser.StringVariableInitializerContext ctx) { + public void exitStringVariableInitializer(StringVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), StringExpression.empty(), StringExpression.empty())); } @Override - public void exitBooleanVariableInitializer(EfxParser.BooleanVariableInitializerContext ctx) { + public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), BooleanExpression.empty(), BooleanExpression.empty())); } @Override - public void exitNumericVariableInitializer(EfxParser.NumericVariableInitializerContext ctx) { + public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), NumericExpression.empty(), NumericExpression.empty())); } @Override - public void exitDateVariableInitializer(EfxParser.DateVariableInitializerContext ctx) { + public void exitDateVariableInitializer(DateVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), DateExpression.empty(), DateExpression.empty())); } @Override - public void exitTimeVariableInitializer(EfxParser.TimeVariableInitializerContext ctx) { + public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), TimeExpression.empty(), TimeExpression.empty())); } @Override - public void exitDurationVariableInitializer(EfxParser.DurationVariableInitializerContext ctx) { + public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), DurationExpression.empty(), DurationExpression.empty())); } @@ -849,37 +830,37 @@ public void exitDurationVariableInitializer(EfxParser.DurationVariableInitialize // Sequence variable initializers for type tracking @Override - public void exitStringSequenceVariableInitializer(EfxParser.StringSequenceVariableInitializerContext ctx) { + public void exitStringSequenceVariableInitializer(StringSequenceVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), new StringSequenceExpression(""), new StringSequenceExpression(""))); } @Override - public void exitBooleanSequenceVariableInitializer(EfxParser.BooleanSequenceVariableInitializerContext ctx) { + public void exitBooleanSequenceVariableInitializer(BooleanSequenceVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), new BooleanSequenceExpression(""), new BooleanSequenceExpression(""))); } @Override - public void exitNumericSequenceVariableInitializer(EfxParser.NumericSequenceVariableInitializerContext ctx) { + public void exitNumericSequenceVariableInitializer(NumericSequenceVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), new NumericSequenceExpression(""), new NumericSequenceExpression(""))); } @Override - public void exitDateSequenceVariableInitializer(EfxParser.DateSequenceVariableInitializerContext ctx) { + public void exitDateSequenceVariableInitializer(DateSequenceVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), new DateSequenceExpression(""), new DateSequenceExpression(""))); } @Override - public void exitTimeSequenceVariableInitializer(EfxParser.TimeSequenceVariableInitializerContext ctx) { + public void exitTimeSequenceVariableInitializer(TimeSequenceVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), new TimeSequenceExpression(""), new TimeSequenceExpression(""))); } @Override - public void exitDurationSequenceVariableInitializer(EfxParser.DurationSequenceVariableInitializerContext ctx) { + public void exitDurationSequenceVariableInitializer(DurationSequenceVariableInitializerContext ctx) { this.stack.push(new Variable(ctx.variableName.getText(), new DurationSequenceExpression(""), new DurationSequenceExpression(""))); } diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java index 6080bb4e..a9216d2f 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -90,83 +90,7 @@ import eu.europa.ted.efx.model.variables.Template; import eu.europa.ted.efx.model.variables.Variable; import eu.europa.ted.efx.model.variables.Variables; -import eu.europa.ted.efx.sdk2.EfxParser.AssetIdContext; -import eu.europa.ted.efx.sdk2.EfxParser.AssetTypeContext; -import eu.europa.ted.efx.sdk2.EfxParser.BooleanFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.BooleanParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.BooleanSequenceParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.BooleanSequenceFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.BooleanSequenceVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.BooleanVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.ChooseTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.ComputedLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationBlockContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.ContextVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.DateFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DateParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DateSequenceParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DateSequenceFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DateSequenceVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.DateVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.DictionaryDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DictionaryIndexClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.DictionaryKeyClauseContext; -import eu.europa.ted.efx.sdk2.EfxParser.DurationFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DurationParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DurationSequenceParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DurationSequenceFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.DurationSequenceVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.DurationVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.ExpressionTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.VariableDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.IndentationContext; -import eu.europa.ted.efx.sdk2.EfxParser.InvokeTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.LabelTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.LabelTypeContext; -import eu.europa.ted.efx.sdk2.EfxParser.LinkedExpressionBlockContext; -import eu.europa.ted.efx.sdk2.EfxParser.LinkedExpressionTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.LinkedLabelBlockContext; -import eu.europa.ted.efx.sdk2.EfxParser.LinkedLabelTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.LinkedTextBlockContext; -import eu.europa.ted.efx.sdk2.EfxParser.LinkedTextTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.NavigationSectionContext; -import eu.europa.ted.efx.sdk2.EfxParser.NumericFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.NumericParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.NumericSequenceParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.NumericSequenceFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.NumericSequenceVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.NumericVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.SecondaryTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandBtLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandFieldLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandFieldValueReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandIndirectLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandIndirectLabelReferenceFromContextFieldContext; -import eu.europa.ted.efx.sdk2.EfxParser.ShorthandLabelReferenceFromContextContext; -import eu.europa.ted.efx.sdk2.EfxParser.StandardExpressionBlockContext; -import eu.europa.ted.efx.sdk2.EfxParser.StandardLabelReferenceContext; -import eu.europa.ted.efx.sdk2.EfxParser.StringFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.StringParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.StringSequenceParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.StringSequenceFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.StringSequenceVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.StringVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.SummarySectionContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateDefinitionContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateFileContext; -import eu.europa.ted.efx.sdk2.EfxParser.TemplateLineContext; -import eu.europa.ted.efx.sdk2.EfxParser.VariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.TextTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.TimeFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.TimeParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.TimeSequenceParameterDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.TimeSequenceFunctionDeclarationContext; -import eu.europa.ted.efx.sdk2.EfxParser.TimeSequenceVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.TimeVariableInitializerContext; -import eu.europa.ted.efx.sdk2.EfxParser.WhenDisplayTemplateContext; -import eu.europa.ted.efx.sdk2.EfxParser.WhenInvokeTemplateContext; +import eu.europa.ted.efx.sdk2.EfxParser.*; /** * The EfxTemplateTranslator extends the {@link EfxExpressionTranslatorV2} to provide additional 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 1af49244..ee414399 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -451,6 +451,12 @@ public BooleanExpression composeEmptySequenceCondition(SequenceExpression sequen return new BooleanExpression("empty(" + sequence.getScript() + ")"); } + @Override + public BooleanExpression composeIsDistinctCondition(SequenceExpression sequence) { + return new BooleanExpression( + "count(" + sequence.getScript() + ") = count(distinct-values(" + sequence.getScript() + "))"); + } + //#endregion Boolean functions ---------------------------------------------- //#region Numeric functions ------------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 93d35e47..d867b77b 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -2330,6 +2330,86 @@ void testStringEmptyFunction_WithFieldReference() { // #endregion: String empty function + // #region: Sequence duplicates + + @Test + void testSequenceDuplicates_WithNonRepeatableField() { + // Field references always go through sequence duplicates, regardless of repeatability. + testExpressionTranslationWithContext( + "not(count(PathNode/TextField/normalize-space(text())) = count(distinct-values(PathNode/TextField/normalize-space(text()))))", + "ND-Root", "BT-00-Text has duplicates"); + } + + @Test + void testSequenceDuplicates_WithStringSequence() { + testExpressionTranslationWithContext( + "not(count(('a','b','a')) = count(distinct-values(('a','b','a'))))", "ND-Root", + "('a', 'b', 'a') has duplicates"); + } + + @Test + void testSequenceDuplicates_WithStringSequence_Negated() { + testExpressionTranslationWithContext( + "count(('a','b','c')) = count(distinct-values(('a','b','c')))", "ND-Root", + "('a', 'b', 'c') has no duplicates"); + } + + @Test + void testSequenceDuplicates_WithNumericSequence() { + testExpressionTranslationWithContext( + "not(count((1,2,3)) = count(distinct-values((1,2,3))))", "ND-Root", + "(1, 2, 3) has duplicates"); + } + + @Test + void testSequenceDuplicates_WithBooleanSequence() { + testExpressionTranslationWithContext( + "not(count((true(),false())) = count(distinct-values((true(),false()))))", "ND-Root", + "(TRUE, FALSE) has duplicates"); + } + + @Test + void testSequenceDuplicates_WithDateSequence() { + testExpressionTranslationWithContext( + "not(count((xs:date('2024-01-01Z'),xs:date('2024-12-31Z'))) = count(distinct-values((xs:date('2024-01-01Z'),xs:date('2024-12-31Z')))))", + "ND-Root", + "(2024-01-01Z, 2024-12-31Z) has duplicates"); + } + + @Test + void testSequenceDuplicates_WithTimeSequence() { + testExpressionTranslationWithContext( + "not(count((xs:time('12:00:00Z'),xs:time('13:00:00Z'))) = count(distinct-values((xs:time('12:00:00Z'),xs:time('13:00:00Z')))))", + "ND-Root", + "(12:00:00Z, 13:00:00Z) has duplicates"); + } + + @Test + void testSequenceDuplicates_WithDurationSequence() { + testExpressionTranslationWithContext( + "not(count((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y'))) = count(distinct-values((xs:yearMonthDuration('P1Y'),xs:yearMonthDuration('P2Y')))))", + "ND-Root", + "(P1Y, P2Y) has duplicates"); + } + + @Test + void testSequenceDuplicates_WithRepeatableFieldReference() { + testExpressionTranslationWithContext( + "not(count(PathNode/RepeatableTextField/normalize-space(text())) = count(distinct-values(PathNode/RepeatableTextField/normalize-space(text()))))", + "ND-Root", + "BT-00-Repeatable-Text has duplicates"); + } + + @Test + void testSequenceDuplicates_WithRepeatableFieldReference_Negated() { + testExpressionTranslationWithContext( + "count(PathNode/RepeatableTextField/normalize-space(text())) = count(distinct-values(PathNode/RepeatableTextField/normalize-space(text())))", + "ND-Root", + "BT-00-Repeatable-Text has no duplicates"); + } + + // #endregion: Sequence duplicates + @Test void testParameterizedExpression_WithStringParameter() { testExpressionTranslation("'hello' = 'world'", "{ND-Root, text:$p1, text:$p2} ${$p1 == $p2}", From 91090461d676cf2798f0570472c0017b4d712f55 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 01:34:21 +0100 Subject: [PATCH 14/31] Add javadoc to sequence-related ScriptGenerator methods --- .../ted/efx/interfaces/ScriptGenerator.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 4c6b6e02..e8c4f296 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -324,6 +324,12 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, // #region Numeric Functions ------------------------------------------------ + /** + * Returns the target language script that counts the number of elements in a sequence. + * + * @param list The sequence whose elements are to be counted. + * @return A numeric expression representing the count. + */ public NumericExpression composeCountOperation(final SequenceExpression list); public NumericExpression composeToNumberConversion(StringExpression text); @@ -464,11 +470,33 @@ public BooleanExpression composeUniqueValueCondition(TimeExpression needle, public BooleanExpression composeUniqueValueCondition(DurationExpression needle, DurationSequenceExpression haystack); + /** + * Returns the target language script that checks whether two sequences contain the same + * elements, regardless of order. + * + * @param one The first sequence. + * @param two The second sequence. + * @return A boolean expression that is true when the two sequences are equal. + */ public BooleanExpression composeSequenceEqualFunction(SequenceExpression one, SequenceExpression two); + /** + * Returns the target language script that checks whether a sequence is empty + * (contains no elements). + * + * @param sequence The sequence to check. + * @return A boolean expression that is true when the sequence is empty. + */ public BooleanExpression composeEmptySequenceCondition(SequenceExpression sequence); + /** + * Returns the target language script that checks whether all values in a sequence are + * distinct (i.e. the sequence has no duplicate values). + * + * @param sequence The sequence to check. + * @return A boolean expression that is true when all values are distinct. + */ public BooleanExpression composeIsDistinctCondition(SequenceExpression sequence); // #endregion Boolean Functions -------------------------------------------- @@ -522,31 +550,125 @@ public DurationExpression composeSubtraction(final DurationExpression left, // #region Sequence Functions -------------------------------------------- + /** + * Returns the target language script that removes duplicate values from a sequence, + * preserving only distinct values. + * + * @param The type of the sequence expression. + * @param list The sequence to remove duplicates from. + * @param listType The class of the sequence expression type. + * @return A sequence containing only the distinct values. + */ public T composeDistinctValuesFunction( T list, Class listType); + /** + * Returns the target language script that computes the union of two sequences + * (all values from both, with duplicates removed). + * + * @param The type of the sequence expression. + * @param listOne The first sequence. + * @param listTwo The second sequence. + * @param listType The class of the sequence expression type. + * @return A sequence containing the union of both sequences. + */ public T composeUnionFunction(T listOne, T listTwo, Class listType); + /** + * Returns the target language script that computes the intersection of two sequences + * (only values present in both). + * + * @param The type of the sequence expression. + * @param listOne The first sequence. + * @param listTwo The second sequence. + * @param listType The class of the sequence expression type. + * @return A sequence containing only the values present in both sequences. + */ public T composeIntersectFunction(T listOne, T listTwo, Class listType); + /** + * Returns the target language script that computes the difference of two sequences + * (values in the first sequence that are not in the second). + * + * @param The type of the sequence expression. + * @param listOne The first sequence. + * @param listTwo The second sequence. + * @param listType The class of the sequence expression type. + * @return A sequence containing values from the first sequence not present in the second. + */ public T composeExceptFunction(T listOne, T listTwo, Class listType); + /** + * Returns the target language script that sorts a sequence in ascending order. + * + * @param The type of the sequence expression. + * @param list The sequence to sort. + * @param listType The class of the sequence expression type. + * @return A sorted sequence. + */ public T composeSortFunction(T list, Class listType); + /** + * Returns the target language script that reverses the order of elements in a sequence. + * + * @param The type of the sequence expression. + * @param list The sequence to reverse. + * @param listType The class of the sequence expression type. + * @return A reversed sequence. + */ public T composeReverseFunction(T list, Class listType); + /** + * Returns the target language script that extracts a contiguous subsequence starting + * at the given position, through the end of the sequence. + * + * @param The type of the sequence expression. + * @param list The source sequence. + * @param start The 1-based starting position. + * @param listType The class of the sequence expression type. + * @return A subsequence from the starting position to the end. + */ public T composeSubsequenceFunction(T list, NumericExpression start, Class listType); + /** + * Returns the target language script that extracts a contiguous subsequence of the + * given length, starting at the given position. + * + * @param The type of the sequence expression. + * @param list The source sequence. + * @param start The 1-based starting position. + * @param length The maximum number of elements to extract. + * @param listType The class of the sequence expression type. + * @return A subsequence of the given length starting at the given position. + */ public T composeSubsequenceFunction(T list, NumericExpression start, NumericExpression length, Class listType); + /** + * Returns the target language script that finds the positions of a value within a sequence. + * Returns a numeric sequence containing the 1-based positions of all occurrences. + * + * @param list The sequence to search in. + * @param value The value to search for. + * @return A numeric sequence containing the positions of all occurrences. + */ public NumericSequenceExpression composeIndexOfFunction(SequenceExpression list, ScalarExpression value); + /** + * Returns the target language script that retrieves the element at a given position + * in a sequence. + * + * @param The scalar type of the elements in the sequence. + * @param list The sequence to index into. + * @param index The 1-based position of the element to retrieve. + * @param type The class of the scalar expression type. + * @return The element at the given position. + */ public T composeIndexer(SequenceExpression list, NumericExpression index, Class type); From 79a03de85b0ab3c06d9415a4b2f69aaf000df51d Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 01:39:31 +0100 Subject: [PATCH 15/31] Add javadoc to type conversion ScriptGenerator methods --- .../ted/efx/interfaces/ScriptGenerator.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 818eb1fa..956c3da6 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -326,8 +326,21 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public NumericExpression composeCountOperation(final SequenceExpression list); + /** + * Returns the target language script that converts a string to a number. + * + * @param text The string expression to convert. + * @return A numeric expression representing the converted value. + */ public NumericExpression composeToNumberConversion(StringExpression text); + /** + * Returns the target language script that converts a boolean to a number. + * Typically {@code TRUE} maps to {@code 1} and {@code FALSE} maps to {@code 0}. + * + * @param bool The boolean expression to convert. + * @return A numeric expression representing the converted value. + */ public NumericExpression composeToNumberConversion(BooleanExpression bool); public NumericExpression composeSumOperation(NumericSequenceExpression list); @@ -357,14 +370,44 @@ public StringExpression composeSubstringExtraction(StringExpression text, public StringExpression composeSubstringExtraction(StringExpression text, NumericExpression start, NumericExpression length); + /** + * Returns the target language script that converts a number to its string representation. + * + * @param number The numeric expression to convert. + * @return A string expression representing the converted value. + */ public StringExpression composeToStringConversion(NumericExpression number); + /** + * Returns the target language script that converts a boolean to its string representation. + * + * @param bool The boolean expression to convert. + * @return A string expression representing the converted value. + */ public StringExpression composeToStringConversion(BooleanExpression bool); + /** + * Returns the target language script that converts a date to its string representation. + * + * @param date The date expression to convert. + * @return A string expression representing the converted value. + */ public StringExpression composeToStringConversion(DateExpression date); + /** + * Returns the target language script that converts a time to its string representation. + * + * @param time The time expression to convert. + * @return A string expression representing the converted value. + */ public StringExpression composeToStringConversion(TimeExpression time); + /** + * Returns the target language script that converts a duration to its string representation. + * + * @param duration The duration expression to convert. + * @return A string expression representing the converted value. + */ public StringExpression composeToStringConversion(DurationExpression duration); /** @@ -426,6 +469,13 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri public BooleanExpression composeExistsCondition(PathExpression reference); + /** + * Returns the target language script that converts a number to a boolean. + * Typically {@code 0} maps to {@code FALSE} and any non-zero value maps to {@code TRUE}. + * + * @param number The numeric expression to convert. + * @return A boolean expression representing the converted value. + */ public BooleanExpression composeToBooleanConversion(NumericExpression number); /** @@ -471,6 +521,12 @@ public BooleanExpression composeSequenceEqualFunction(SequenceExpression one, // #region Date Functions --------------------------------------------------- + /** + * Returns the target language script that converts a string to a date. + * + * @param pop The string expression to convert. + * @return A date expression representing the converted value. + */ public DateExpression composeToDateConversion(StringExpression pop); public DateExpression composeAddition(final DateExpression date, @@ -490,14 +546,34 @@ public DateExpression composeSubtraction(final DateExpression date, // #region Time Functions --------------------------------------------------- + /** + * Returns the target language script that converts a string to a time. + * + * @param pop The string expression to convert. + * @return A time expression representing the converted value. + */ public TimeExpression composeToTimeConversion(StringExpression pop); // #endregion Time Functions ------------------------------------------------ // #region Duration Functions ----------------------------------------------- + /** + * Returns the target language script that converts a string to a day-time duration + * (e.g. {@code "P3DT4H"} for 3 days and 4 hours). + * + * @param text The string expression to convert. + * @return A duration expression representing the converted value. + */ public DurationExpression composeToDayTimeDurationConversion(StringExpression text); + /** + * Returns the target language script that converts a string to a year-month duration + * (e.g. {@code "P2Y3M"} for 2 years and 3 months). + * + * @param text The string expression to convert. + * @return A duration expression representing the converted value. + */ public DurationExpression composeToYearMonthDurationConversion(StringExpression text); public DurationExpression composeSubtraction(DateExpression startDate, DateExpression endDate); From d82e2b16c89836d8a71c640afd19d994534c2ef2 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 02:52:22 +0100 Subject: [PATCH 16/31] TEDEFO-4788 Change index-of to return first occurrence only --- .../europa/ted/efx/interfaces/ScriptGenerator.java | 9 +++++---- .../europa/ted/efx/xpath/XPathScriptGenerator.java | 6 +++--- .../efx/sdk2/EfxExpressionTranslatorV2Test.java | 14 +++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index be6031d0..804a8b19 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -725,14 +725,15 @@ public T composeSubsequenceFunction(T list, NumericExpression start, NumericExpression length, Class listType); /** - * Returns the target language script that finds the positions of a value within a sequence. - * Returns a numeric sequence containing the 1-based positions of all occurrences. + * Returns the target language script that finds the 1-based position of the first occurrence of a + * value within a sequence. Returns 0 if the value is not found. * * @param list The sequence to search in. * @param value The value to search for. - * @return A numeric sequence containing the positions of all occurrences. + * @return A numeric expression with the 1-based position of the first occurrence, or 0 if not + * found. */ - public NumericSequenceExpression composeIndexOfFunction(SequenceExpression list, + public NumericExpression composeIndexOfFunction(SequenceExpression list, ScalarExpression value); /** 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 ee414399..901335b2 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -699,10 +699,10 @@ public T composeSubsequenceFunction(T list, } @Override - public NumericSequenceExpression composeIndexOfFunction(SequenceExpression list, + public NumericExpression composeIndexOfFunction(SequenceExpression list, ScalarExpression value) { - return new NumericSequenceExpression( - "index-of(" + list.getScript() + ", " + value.getScript() + ")"); + return new NumericExpression( + "index-of(" + list.getScript() + ", " + value.getScript() + ")[1]"); } //#endregion Duration functions --------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index d867b77b..cf57df07 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -2147,47 +2147,47 @@ void testSubsequenceFunction_WithRepeatableFieldReference_AndLength() { @Test void testIndexOfFunction_WithStringSequences() { - testExpressionTranslationWithContext("index-of(('a','b','c','b'), 'b')", "ND-Root", + testExpressionTranslationWithContext("index-of(('a','b','c','b'), 'b')[1]", "ND-Root", "index-of(('a', 'b', 'c', 'b'), 'b')"); } @Test void testIndexOfFunction_WithNumberSequences() { - testExpressionTranslationWithContext("index-of((10,20,30,20), 20)", "ND-Root", + testExpressionTranslationWithContext("index-of((10,20,30,20), 20)[1]", "ND-Root", "index-of((10, 20, 30, 20), 20)"); } @Test void testIndexOfFunction_WithDateSequences() { testExpressionTranslationWithContext( - "index-of((xs:date('2022-01-01Z'),xs:date('2023-01-01Z'),xs:date('2022-01-01Z')), xs:date('2022-01-01Z'))", + "index-of((xs:date('2022-01-01Z'),xs:date('2023-01-01Z'),xs:date('2022-01-01Z')), xs:date('2022-01-01Z'))[1]", "ND-Root", "index-of((2022-01-01Z, 2023-01-01Z, 2022-01-01Z), 2022-01-01Z)"); } @Test void testIndexOfFunction_WithBooleanSequences() { - testExpressionTranslationWithContext("index-of((true(),false(),true()), true())", + testExpressionTranslationWithContext("index-of((true(),false(),true()), true())[1]", "ND-Root", "index-of((TRUE, FALSE, TRUE), TRUE)"); } @Test void testIndexOfFunction_WithTimeSequences() { testExpressionTranslationWithContext( - "index-of((xs:time('14:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')), xs:time('14:00:00Z'))", + "index-of((xs:time('14:00:00Z'),xs:time('12:00:00Z'),xs:time('14:00:00Z')), xs:time('14:00:00Z'))[1]", "ND-Root", "index-of((14:00:00Z, 12:00:00Z, 14:00:00Z), 14:00:00Z)"); } @Test void testIndexOfFunction_WithDurationSequences() { testExpressionTranslationWithContext( - "index-of((xs:dayTimeDuration('P5D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')), xs:dayTimeDuration('P5D'))", + "index-of((xs:dayTimeDuration('P5D'),xs:dayTimeDuration('P2D'),xs:dayTimeDuration('P5D')), xs:dayTimeDuration('P5D'))[1]", "ND-Root", "index-of((P5D, P2D, P5D), P5D)"); } @Test void testIndexOfFunction_WithRepeatableFieldReference() { testExpressionTranslationWithContext( - "index-of(PathNode/RepeatableTextField/normalize-space(text()), 'hello')", "ND-Root", + "index-of(PathNode/RepeatableTextField/normalize-space(text()), 'hello')[1]", "ND-Root", "index-of(BT-00-Repeatable-Text, 'hello')"); } From 967e782a7a9b6e5d5be4176ab5d2f997f625fce3 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 12:20:12 +0100 Subject: [PATCH 17/31] TEDEFO-4885 Implement normalize-space() function --- .../ted/efx/interfaces/ScriptGenerator.java | 9 +++++++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 5 +++++ .../ted/efx/xpath/XPathScriptGenerator.java | 5 +++++ .../efx/sdk2/EfxExpressionTranslatorV2Test.java | 15 ++++++++++++++- 4 files changed, 33 insertions(+), 1 deletion(-) 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 804a8b19..31410838 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -438,6 +438,15 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri */ public StringExpression composeToLowerCaseConversion(StringExpression text); + /** + * Returns the target language script that strips leading/trailing whitespace and collapses + * internal whitespace sequences to a single space. + * + * @param text The text to normalize. + * @return The target language script that normalizes whitespace in the text. + */ + public StringExpression composeNormalizeSpaceFunction(StringExpression text); + /** * Gets the target language script that retrieves the preferred language ID * out of the languages available in the given field. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 01fb6292..30acc1ce 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2095,6 +2095,11 @@ public void exitLowerCaseFunction(LowerCaseFunctionContext ctx) { this.stack.push(this.script.composeToLowerCaseConversion(this.stack.pop(StringExpression.class))); } + @Override + public void exitNormalizeSpaceFunction(NormalizeSpaceFunctionContext ctx) { + this.stack.push(this.script.composeNormalizeSpaceFunction(this.stack.pop(StringExpression.class))); + } + @Override public void exitStringJoinFunction(StringJoinFunctionContext ctx) { final StringExpression separator = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 901335b2..79f4d4d7 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -545,6 +545,11 @@ public StringExpression composeToLowerCaseConversion(StringExpression text) { return new StringExpression("lower-case(" + text.getScript() + ")"); } + @Override + public StringExpression composeNormalizeSpaceFunction(StringExpression text) { + return new StringExpression("normalize-space(" + text.getScript() + ")"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index cf57df07..61c39856 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1593,10 +1593,23 @@ void testUpperCaseFunction() { @Test void testLowerCaseFunction() { testExpressionTranslation( - "lower-case(PathNode/TextField/normalize-space(text()))", + "lower-case(PathNode/TextField/normalize-space(text()))", "{ND-Root} ${lower-case(BT-00-Text)}"); } + @Test + void testNormalizeSpaceFunction() { + testExpressionTranslation( + "normalize-space(PathNode/TextField/normalize-space(text()))", + "{ND-Root} ${normalize-space(BT-00-Text)}"); + } + + @Test + void testNormalizeSpaceFunction_WithLiteral() { + testExpressionTranslationWithContext("normalize-space(' hello world ')", "ND-Root", + "normalize-space(' hello world ')"); + } + @Test void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); From e883263052d4a5ca88dd19fc9142db89dd91c009 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 12:32:35 +0100 Subject: [PATCH 18/31] TEDEFO-4888 Implement trim, trim-left, trim-right functions --- .../ted/efx/interfaces/ScriptGenerator.java | 24 ++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 15 +++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 16 +++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 45 +++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 31410838..f0a7ea32 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -447,6 +447,30 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri */ public StringExpression composeNormalizeSpaceFunction(StringExpression text); + /** + * Returns the target language script that removes leading and trailing whitespace from the text. + * + * @param text The text to trim. + * @return The target language script that trims whitespace from both ends. + */ + public StringExpression composeTrimFunction(StringExpression text); + + /** + * Returns the target language script that removes leading whitespace from the text. + * + * @param text The text to trim. + * @return The target language script that trims leading whitespace. + */ + public StringExpression composeTrimLeftFunction(StringExpression text); + + /** + * Returns the target language script that removes trailing whitespace from the text. + * + * @param text The text to trim. + * @return The target language script that trims trailing whitespace. + */ + public StringExpression composeTrimRightFunction(StringExpression text); + /** * Gets the target language script that retrieves the preferred language ID * out of the languages available in the given field. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 30acc1ce..c5857d41 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2100,6 +2100,21 @@ public void exitNormalizeSpaceFunction(NormalizeSpaceFunctionContext ctx) { this.stack.push(this.script.composeNormalizeSpaceFunction(this.stack.pop(StringExpression.class))); } + @Override + public void exitTrimFunction(TrimFunctionContext ctx) { + this.stack.push(this.script.composeTrimFunction(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitTrimLeftFunction(TrimLeftFunctionContext ctx) { + this.stack.push(this.script.composeTrimLeftFunction(this.stack.pop(StringExpression.class))); + } + + @Override + public void exitTrimRightFunction(TrimRightFunctionContext ctx) { + this.stack.push(this.script.composeTrimRightFunction(this.stack.pop(StringExpression.class))); + } + @Override public void exitStringJoinFunction(StringJoinFunctionContext ctx) { final StringExpression separator = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 79f4d4d7..4743ec65 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -550,6 +550,22 @@ public StringExpression composeNormalizeSpaceFunction(StringExpression text) { return new StringExpression("normalize-space(" + text.getScript() + ")"); } + @Override + public StringExpression composeTrimFunction(StringExpression text) { + return new StringExpression( + "replace(replace(" + text.getScript() + ", '^\\s+', ''), '\\s+$', '')"); + } + + @Override + public StringExpression composeTrimLeftFunction(StringExpression text) { + return new StringExpression("replace(" + text.getScript() + ", '^\\s+', '')"); + } + + @Override + public StringExpression composeTrimRightFunction(StringExpression text) { + return new StringExpression("replace(" + text.getScript() + ", '\\s+$', '')"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 61c39856..bd30d106 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1610,6 +1610,51 @@ void testNormalizeSpaceFunction_WithLiteral() { "normalize-space(' hello world ')"); } + @Test + void testTrimFunction() { + testExpressionTranslation( + "replace(replace(PathNode/TextField/normalize-space(text()), '^\\s+', ''), '\\s+$', '')", + "{ND-Root} ${trim(BT-00-Text)}"); + } + + @Test + void testTrimFunction_WithLiteral() { + testExpressionTranslationWithContext("replace(replace(' hello ', '^\\s+', ''), '\\s+$', '')", + "ND-Root", "trim(' hello ')"); + } + + @Test + void testTrimLeftFunction() { + testExpressionTranslation( + "replace(PathNode/TextField/normalize-space(text()), '^\\s+', '')", + "{ND-Root} ${trim-left(BT-00-Text)}"); + } + + @Test + void testTrimLeftFunction_WithLiteral() { + testExpressionTranslationWithContext("replace(' hello ', '^\\s+', '')", "ND-Root", + "trim-left(' hello ')"); + } + + @Test + void testTrimRightFunction() { + testExpressionTranslation( + "replace(PathNode/TextField/normalize-space(text()), '\\s+$', '')", + "{ND-Root} ${trim-right(BT-00-Text)}"); + } + + @Test + void testTrimRightFunction_WithLiteral() { + testExpressionTranslationWithContext("replace(' hello ', '\\s+$', '')", "ND-Root", + "trim-right(' hello ')"); + } + + @Test + void testTrimFunction_WithRepeatableField_Throws() { + assertThrows(ParseCancellationException.class, + () -> translateExpressionWithContext("ND-Root", "trim(BT-00-Repeatable-Text)")); + } + @Test void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); From 21b75e80e532c6ac4c5784829320b735ced4f5f3 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 13:01:46 +0100 Subject: [PATCH 19/31] TEDEFO-4889 Implement pad-left and pad-right functions --- .../ted/efx/interfaces/ScriptGenerator.java | 26 +++++++++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 16 +++++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 22 +++++++++++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 28 +++++++++++++++++++ 4 files changed, 92 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index f0a7ea32..5d917d0b 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -471,6 +471,32 @@ public StringExpression composeSubstringExtraction(StringExpression text, Numeri */ public StringExpression composeTrimRightFunction(StringExpression text); + /** + * Returns the target language script that pads the text on the left with the given character + * until it reaches the specified length. If the text is already at least the specified length, + * it is returned unchanged. + * + * @param text The text to pad. + * @param length The desired minimum length. + * @param padChar The character to pad with. + * @return The target language script that left-pads the text. + */ + public StringExpression composePadLeftFunction(StringExpression text, NumericExpression length, + StringExpression padChar); + + /** + * Returns the target language script that pads the text on the right with the given character + * until it reaches the specified length. If the text is already at least the specified length, + * it is returned unchanged. + * + * @param text The text to pad. + * @param length The desired minimum length. + * @param padChar The character to pad with. + * @return The target language script that right-pads the text. + */ + public StringExpression composePadRightFunction(StringExpression text, NumericExpression length, + StringExpression padChar); + /** * Gets the target language script that retrieves the preferred language ID * out of the languages available in the given field. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index c5857d41..a64e9d2c 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2115,6 +2115,22 @@ public void exitTrimRightFunction(TrimRightFunctionContext ctx) { this.stack.push(this.script.composeTrimRightFunction(this.stack.pop(StringExpression.class))); } + @Override + public void exitPadLeftFunction(PadLeftFunctionContext ctx) { + final StringExpression padChar = this.stack.pop(StringExpression.class); + final NumericExpression length = this.stack.pop(NumericExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composePadLeftFunction(text, length, padChar)); + } + + @Override + public void exitPadRightFunction(PadRightFunctionContext ctx) { + final StringExpression padChar = this.stack.pop(StringExpression.class); + final NumericExpression length = this.stack.pop(NumericExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composePadRightFunction(text, length, padChar)); + } + @Override public void exitStringJoinFunction(StringJoinFunctionContext ctx) { final StringExpression separator = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 4743ec65..269e5ea4 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -566,6 +566,28 @@ public StringExpression composeTrimRightFunction(StringExpression text) { return new StringExpression("replace(" + text.getScript() + ", '\\s+$', '')"); } + @Override + public StringExpression composePadLeftFunction(StringExpression text, NumericExpression length, + StringExpression padChar) { + String len = length.getScript(); + String ch = padChar.getScript(); + String padding = "string-join(for $__i in 1 to " + len + " return " + ch + ", '')"; + return new StringExpression("(for $__s in " + text.getScript() + + " return concat(substring(" + padding + ", 1, " + len + + " - string-length($__s)), $__s))"); + } + + @Override + public StringExpression composePadRightFunction(StringExpression text, NumericExpression length, + StringExpression padChar) { + String len = length.getScript(); + String ch = padChar.getScript(); + String padding = "string-join(for $__i in 1 to " + len + " return " + ch + ", '')"; + return new StringExpression("(for $__s in " + text.getScript() + + " return concat($__s, substring(" + padding + ", 1, " + len + + " - string-length($__s))))"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index bd30d106..cdd608dc 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1655,6 +1655,34 @@ void testTrimFunction_WithRepeatableField_Throws() { () -> translateExpressionWithContext("ND-Root", "trim(BT-00-Repeatable-Text)")); } + @Test + void testPadLeftFunction() { + testExpressionTranslationWithContext( + "(for $__s in '42' return concat(substring(string-join(for $__i in 1 to 5 return '0', ''), 1, 5 - string-length($__s)), $__s))", + "ND-Root", "pad-left('42', 5, '0')"); + } + + @Test + void testPadLeftFunction_WithFieldReference() { + testExpressionTranslation( + "(for $__s in PathNode/TextField/normalize-space(text()) return concat(substring(string-join(for $__i in 1 to 10 return ' ', ''), 1, 10 - string-length($__s)), $__s))", + "{ND-Root} ${pad-left(BT-00-Text, 10, ' ')}"); + } + + @Test + void testPadRightFunction() { + testExpressionTranslationWithContext( + "(for $__s in '42' return concat($__s, substring(string-join(for $__i in 1 to 5 return '0', ''), 1, 5 - string-length($__s))))", + "ND-Root", "pad-right('42', 5, '0')"); + } + + @Test + void testPadRightFunction_WithFieldReference() { + testExpressionTranslation( + "(for $__s in PathNode/TextField/normalize-space(text()) return concat($__s, substring(string-join(for $__i in 1 to 10 return ' ', ''), 1, 10 - string-length($__s))))", + "{ND-Root} ${pad-right(BT-00-Text, 10, ' ')}"); + } + @Test void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); From f61b8cfb746946286a96a38635fd4e4f2c9d609d Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 13:15:09 +0100 Subject: [PATCH 20/31] TEDEFO-4892 Implement repeat() function --- .../europa/ted/efx/interfaces/ScriptGenerator.java | 9 +++++++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 7 +++++++ .../europa/ted/efx/xpath/XPathScriptGenerator.java | 7 +++++++ .../efx/sdk2/EfxExpressionTranslatorV2Test.java | 14 ++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 5d917d0b..2825c38e 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -497,6 +497,15 @@ public StringExpression composePadLeftFunction(StringExpression text, NumericExp public StringExpression composePadRightFunction(StringExpression text, NumericExpression length, StringExpression padChar); + /** + * Returns the target language script that repeats the text the specified number of times. + * + * @param text The text to repeat. + * @param count The number of repetitions. + * @return The target language script that repeats the text. + */ + public StringExpression composeRepeatFunction(StringExpression text, NumericExpression count); + /** * Gets the target language script that retrieves the preferred language ID * out of the languages available in the given field. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index a64e9d2c..9585c358 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2131,6 +2131,13 @@ public void exitPadRightFunction(PadRightFunctionContext ctx) { this.stack.push(this.script.composePadRightFunction(text, length, padChar)); } + @Override + public void exitRepeatFunction(RepeatFunctionContext ctx) { + final NumericExpression count = this.stack.pop(NumericExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeRepeatFunction(text, count)); + } + @Override public void exitStringJoinFunction(StringJoinFunctionContext ctx) { final StringExpression separator = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 269e5ea4..8b55aa6d 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -588,6 +588,13 @@ public StringExpression composePadRightFunction(StringExpression text, NumericEx + " - string-length($__s))))"); } + @Override + public StringExpression composeRepeatFunction(StringExpression text, NumericExpression count) { + return new StringExpression("(for $__s in " + text.getScript() + + " return string-join(for $__i in 1 to " + count.getScript() + + " return $__s, ''))"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index cdd608dc..461df6e3 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1683,6 +1683,20 @@ void testPadRightFunction_WithFieldReference() { "{ND-Root} ${pad-right(BT-00-Text, 10, ' ')}"); } + @Test + void testRepeatFunction() { + testExpressionTranslationWithContext( + "(for $__s in 'abc' return string-join(for $__i in 1 to 3 return $__s, ''))", + "ND-Root", "repeat('abc', 3)"); + } + + @Test + void testRepeatFunction_WithFieldReference() { + testExpressionTranslation( + "(for $__s in PathNode/TextField/normalize-space(text()) return string-join(for $__i in 1 to 4 return $__s, ''))", + "{ND-Root} ${repeat(BT-00-Text, 4)}"); + } + @Test void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); From bcbc91eb29a67bc8e982b85f5db35da6b6f9092d Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 13:22:52 +0100 Subject: [PATCH 21/31] TEDEFO-4890 Implement split() function with literal delimiter --- .../ted/efx/interfaces/ScriptGenerator.java | 11 ++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 7 +++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 10 +++++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 21 +++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 2825c38e..bedf18bb 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -804,6 +804,17 @@ public T composeSubsequenceFunction(T list, public NumericExpression composeIndexOfFunction(SequenceExpression list, ScalarExpression value); + /** + * Returns the target language script that splits a string into a sequence of substrings + * using the given literal delimiter. + * + * @param text The text to split. + * @param delimiter The literal delimiter to split on. + * @return A string sequence expression with the split parts. + */ + public StringSequenceExpression composeSplitFunction(StringExpression text, + StringExpression delimiter); + /** * Returns the target language script that retrieves the element at a given position * in a sequence. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 9585c358..f641c275 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2464,6 +2464,13 @@ public void exitStringSubsequenceFunction(StringSubsequenceFunctionContext ctx) exitSubsequenceFunction(ctx.length != null, StringSequenceExpression.class); } + @Override + public void exitSplitFunction(SplitFunctionContext ctx) { + final StringExpression delimiter = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeSplitFunction(text, delimiter)); + } + @Override public void exitBooleanSubsequenceFunction(BooleanSubsequenceFunctionContext ctx) { exitSubsequenceFunction(ctx.length != null, BooleanSequenceExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 8b55aa6d..f7fbd229 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -755,6 +755,16 @@ public NumericExpression composeIndexOfFunction(SequenceExpression list, "index-of(" + list.getScript() + ", " + value.getScript() + ")[1]"); } + @Override + public StringSequenceExpression composeSplitFunction(StringExpression text, + StringExpression delimiter) { + // XPath's tokenize() uses regex, so we escape regex metacharacters in the delimiter + // to ensure literal matching. + String escapedDelim = + "replace(" + delimiter.getScript() + ", '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1')"; + return new StringSequenceExpression("tokenize(" + text.getScript() + ", " + escapedDelim + ")"); + } + //#endregion Duration functions --------------------------------------------- @Override diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 461df6e3..67045a28 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1697,6 +1697,27 @@ void testRepeatFunction_WithFieldReference() { "{ND-Root} ${repeat(BT-00-Text, 4)}"); } + @Test + void testSplitFunction() { + testExpressionTranslationWithContext( + "tokenize('a,b,c', replace(',', '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'))", + "ND-Root", "split('a,b,c', ',')"); + } + + @Test + void testSplitFunction_WithFieldReference() { + testExpressionTranslation( + "tokenize(PathNode/TextField/normalize-space(text()), replace(';', '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'))", + "{ND-Root} ${split(BT-00-Text, ';')}"); + } + + @Test + void testSplitFunction_WithRegexMetacharDelimiter() { + testExpressionTranslationWithContext( + "tokenize('a.b.c', replace('.', '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'))", + "ND-Root", "split('a.b.c', '.')"); + } + @Test void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); From 0a03272394cc473caaf0bd7269141957fce467be Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 13:26:52 +0100 Subject: [PATCH 22/31] TEDEFO-4891 Implement substring-before and substring-after functions --- .../ted/efx/interfaces/ScriptGenerator.java | 22 ++++++++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 14 ++++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 14 ++++++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 26 +++++++++++++++++++ 4 files changed, 76 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index bedf18bb..e99f1658 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -376,6 +376,28 @@ public StringExpression composeSubstringExtraction(StringExpression text, public StringExpression composeSubstringExtraction(StringExpression text, NumericExpression start, NumericExpression length); + /** + * Returns the target language script that extracts the part of the text before the first + * occurrence of the delimiter. Returns an empty string if the delimiter is not found. + * + * @param text The text to search in. + * @param delimiter The delimiter to search for. + * @return The target language script for the substring before the delimiter. + */ + public StringExpression composeSubstringBeforeFunction(StringExpression text, + StringExpression delimiter); + + /** + * Returns the target language script that extracts the part of the text after the first + * occurrence of the delimiter. Returns an empty string if the delimiter is not found. + * + * @param text The text to search in. + * @param delimiter The delimiter to search for. + * @return The target language script for the substring after the delimiter. + */ + public StringExpression composeSubstringAfterFunction(StringExpression text, + StringExpression delimiter); + /** * Returns the target language script that converts a number to its string representation. * diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index f641c275..2799aaa3 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2037,6 +2037,20 @@ public void exitSubstringFunction(SubstringFunctionContext ctx) { } } + @Override + public void exitSubstringBeforeFunction(SubstringBeforeFunctionContext ctx) { + final StringExpression delimiter = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeSubstringBeforeFunction(text, delimiter)); + } + + @Override + public void exitSubstringAfterFunction(SubstringAfterFunctionContext ctx) { + final StringExpression delimiter = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeSubstringAfterFunction(text, delimiter)); + } + @Override public void exitNumberToStringFunction(NumberToStringFunctionContext ctx) { this.stack.push(this.script.composeToStringConversion(this.stack.pop(NumericExpression.class))); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index f7fbd229..6085b615 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -510,6 +510,20 @@ public StringExpression composeSubstringExtraction(StringExpression text, return new StringExpression("substring(" + text.getScript() + ", " + start.getScript() + ")"); } + @Override + public StringExpression composeSubstringBeforeFunction(StringExpression text, + StringExpression delimiter) { + return new StringExpression( + "substring-before(" + text.getScript() + ", " + delimiter.getScript() + ")"); + } + + @Override + public StringExpression composeSubstringAfterFunction(StringExpression text, + StringExpression delimiter) { + return new StringExpression( + "substring-after(" + text.getScript() + ", " + delimiter.getScript() + ")"); + } + @Override public StringExpression composeToStringConversion(NumericExpression number) { return new StringExpression("string(" + number.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 67045a28..ec2e77d8 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1718,6 +1718,32 @@ void testSplitFunction_WithRegexMetacharDelimiter() { "ND-Root", "split('a.b.c', '.')"); } + @Test + void testSubstringBeforeFunction() { + testExpressionTranslationWithContext("substring-before('hello-world', '-')", "ND-Root", + "substring-before('hello-world', '-')"); + } + + @Test + void testSubstringBeforeFunction_WithFieldReference() { + testExpressionTranslation( + "substring-before(PathNode/TextField/normalize-space(text()), '-')", + "{ND-Root} ${substring-before(BT-00-Text, '-')}"); + } + + @Test + void testSubstringAfterFunction() { + testExpressionTranslationWithContext("substring-after('hello-world', '-')", "ND-Root", + "substring-after('hello-world', '-')"); + } + + @Test + void testSubstringAfterFunction_WithFieldReference() { + testExpressionTranslation( + "substring-after(PathNode/TextField/normalize-space(text()), '-')", + "{ND-Root} ${substring-after(BT-00-Text, '-')}"); + } + @Test void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); From 057c5705f0e3646f2290656cea0edeb8dd705a8c Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 13:34:30 +0100 Subject: [PATCH 23/31] TEDEFO-4895 Implement index-of-substring() function --- .../europa/ted/efx/interfaces/ScriptGenerator.java | 11 +++++++++++ .../ted/efx/sdk2/EfxExpressionTranslatorV2.java | 7 +++++++ .../europa/ted/efx/xpath/XPathScriptGenerator.java | 8 ++++++++ .../efx/sdk2/EfxExpressionTranslatorV2Test.java | 14 ++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index e99f1658..543d20fc 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -837,6 +837,17 @@ public NumericExpression composeIndexOfFunction(SequenceExpression list, public StringSequenceExpression composeSplitFunction(StringExpression text, StringExpression delimiter); + /** + * Returns the target language script that finds the 1-based position of the first occurrence + * of a substring within a string. Returns 0 if the substring is not found. + * + * @param text The text to search in. + * @param substring The substring to search for. + * @return A numeric expression with the 1-based position, or 0 if not found. + */ + public NumericExpression composeIndexOfSubstringFunction(StringExpression text, + StringExpression substring); + /** * Returns the target language script that retrieves the element at a given position * in a sequence. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 2799aaa3..a9673539 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2532,6 +2532,13 @@ public void exitIndexOfStringFunction(IndexOfStringFunctionContext ctx) { exitIndexOfFunction(StringSequenceExpression.class, StringExpression.class); } + @Override + public void exitIndexOfSubstringFunction(IndexOfSubstringFunctionContext ctx) { + final StringExpression substring = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeIndexOfSubstringFunction(text, substring)); + } + @Override public void exitIndexOfBooleanFunction(IndexOfBooleanFunctionContext ctx) { exitIndexOfFunction(BooleanSequenceExpression.class, BooleanExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 6085b615..00311ee3 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -769,6 +769,14 @@ public NumericExpression composeIndexOfFunction(SequenceExpression list, "index-of(" + list.getScript() + ", " + value.getScript() + ")[1]"); } + @Override + public NumericExpression composeIndexOfSubstringFunction(StringExpression text, + StringExpression substring) { + return new NumericExpression( + "(for $__s in " + text.getScript() + ", $__sub in " + substring.getScript() + + " return if (contains($__s, $__sub)) then string-length(substring-before($__s, $__sub)) + 1 else 0)"); + } + @Override public StringSequenceExpression composeSplitFunction(StringExpression text, StringExpression delimiter) { diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index ec2e77d8..093388c6 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1744,6 +1744,20 @@ void testSubstringAfterFunction_WithFieldReference() { "{ND-Root} ${substring-after(BT-00-Text, '-')}"); } + @Test + void testIndexOfSubstringFunction() { + testExpressionTranslationWithContext( + "(for $__s in 'hello world', $__sub in 'world' return if (contains($__s, $__sub)) then string-length(substring-before($__s, $__sub)) + 1 else 0)", + "ND-Root", "index-of-substring('hello world', 'world')"); + } + + @Test + void testIndexOfSubstringFunction_WithFieldReference() { + testExpressionTranslation( + "(for $__s in PathNode/TextField/normalize-space(text()), $__sub in '-' return if (contains($__s, $__sub)) then string-length(substring-before($__s, $__sub)) + 1 else 0)", + "{ND-Root} ${index-of-substring(BT-00-Text, '-')}"); + } + @Test void testNumberToStringFunction() { testExpressionTranslationWithContext("string(123)", "ND-Root", "string(123)"); From 3a0ac14d3683ffb38e5da34c4de584f0b24ac0d9 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 14:21:35 +0100 Subject: [PATCH 24/31] TEDEFO-4886 Implement replace and replace-regex functions --- .../ted/efx/interfaces/ScriptGenerator.java | 24 +++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 16 +++++ .../ted/efx/xpath/XPathScriptGenerator.java | 23 +++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 63 +++++++++++++++++++ 4 files changed, 126 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 543d20fc..7dbaa6df 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -528,6 +528,30 @@ public StringExpression composePadRightFunction(StringExpression text, NumericEx */ public StringExpression composeRepeatFunction(StringExpression text, NumericExpression count); + /** + * Returns the target language script that replaces all occurrences of a literal search string + * with the replacement string. + * + * @param text The text to search in. + * @param search The literal string to search for. + * @param replacement The replacement string. + * @return The target language script that performs literal replacement. + */ + public StringExpression composeReplaceFunction(StringExpression text, StringExpression search, + StringExpression replacement); + + /** + * Returns the target language script that replaces all matches of a regular expression pattern + * with the replacement string. The pattern uses the EFX regex profile. + * + * @param text The text to search in. + * @param pattern The regex pattern to match. + * @param replacement The replacement string (may use capture group references). + * @return The target language script that performs regex replacement. + */ + public StringExpression composeReplaceRegexFunction(StringExpression text, + StringExpression pattern, StringExpression replacement); + /** * Gets the target language script that retrieves the preferred language ID * out of the languages available in the given field. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index a9673539..62e79550 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2152,6 +2152,22 @@ public void exitRepeatFunction(RepeatFunctionContext ctx) { this.stack.push(this.script.composeRepeatFunction(text, count)); } + @Override + public void exitReplaceFunction(ReplaceFunctionContext ctx) { + final StringExpression replacement = this.stack.pop(StringExpression.class); + final StringExpression search = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeReplaceFunction(text, search, replacement)); + } + + @Override + public void exitReplaceRegexFunction(ReplaceRegexFunctionContext ctx) { + final StringExpression replacement = this.stack.pop(StringExpression.class); + final StringExpression pattern = this.stack.pop(StringExpression.class); + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeReplaceRegexFunction(text, pattern, replacement)); + } + @Override public void exitStringJoinFunction(StringJoinFunctionContext ctx) { final StringExpression separator = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 00311ee3..c254a8ab 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -609,6 +609,29 @@ public StringExpression composeRepeatFunction(StringExpression text, NumericExpr + " return $__s, ''))"); } + @Override + public StringExpression composeReplaceFunction(StringExpression text, StringExpression search, + StringExpression replacement) { + // Escape regex metacharacters in the search string for literal matching + String escapedSearch = "replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1')"; + // Escape replacement-string semantics: \ must become \\, $ must become \$ + // (order matters: escape backslashes first, then dollars) + String escapedReplacement = "replace(replace(" + replacement.getScript() + + ", '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')"; + // Bind text and search to avoid double evaluation; guard against empty search at runtime + return new StringExpression("(for $__s in " + search.getScript() + ", $__t in " + + text.getScript() + " return if ($__s = '') then $__t else replace($__t, " + + escapedSearch + ", " + escapedReplacement + "))"); + } + + @Override + public StringExpression composeReplaceRegexFunction(StringExpression text, + StringExpression pattern, StringExpression replacement) { + return new StringExpression( + "replace(" + text.getScript() + ", " + pattern.getScript() + ", " + + replacement.getScript() + ")"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 093388c6..f3e8c94d 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1697,6 +1697,69 @@ void testRepeatFunction_WithFieldReference() { "{ND-Root} ${repeat(BT-00-Text, 4)}"); } + @Test + void testReplaceFunction() { + testExpressionTranslationWithContext( + "(for $__s in 'world', $__t in 'hello world' return if ($__s = '') then $__t else replace($__t, replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'), replace(replace('there', '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')))", + "ND-Root", "replace('hello world', 'world', 'there')"); + } + + @Test + void testReplaceFunction_WithFieldReference() { + testExpressionTranslation( + "(for $__s in '-', $__t in PathNode/TextField/normalize-space(text()) return if ($__s = '') then $__t else replace($__t, replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'), replace(replace('_', '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')))", + "{ND-Root} ${replace(BT-00-Text, '-', '_')}"); + } + + @Test + void testReplaceFunction_WithRegexMetacharSearch() { + testExpressionTranslationWithContext( + "(for $__s in '.', $__t in 'a.b.c' return if ($__s = '') then $__t else replace($__t, replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'), replace(replace('-', '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')))", + "ND-Root", "replace('a.b.c', '.', '-')"); + } + + @Test + void testReplaceFunction_WithDollarInSearch() { + testExpressionTranslationWithContext( + "(for $__s in '$', $__t in 'a$b' return if ($__s = '') then $__t else replace($__t, replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'), replace(replace('X', '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')))", + "ND-Root", "replace('a$b', '$', 'X')"); + } + + @Test + void testReplaceFunction_WithDollarInReplacement() { + testExpressionTranslationWithContext( + "(for $__s in 'b', $__t in 'ab' return if ($__s = '') then $__t else replace($__t, replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'), replace(replace('$1', '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')))", + "ND-Root", "replace('ab', 'b', '$1')"); + } + + @Test + void testReplaceFunction_WithBackslashInReplacement() { + testExpressionTranslationWithContext( + "(for $__s in 'b', $__t in 'ab' return if ($__s = '') then $__t else replace($__t, replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'), replace(replace('\\\\', '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')))", + "ND-Root", "replace('ab', 'b', '\\\\')"); + } + + @Test + void testReplaceFunction_WithEmptySearch() { + testExpressionTranslationWithContext( + "(for $__s in '', $__t in 'text' return if ($__s = '') then $__t else replace($__t, replace($__s, '([.\\\\?*+{}\\[\\]()^$|])', '\\\\$1'), replace(replace('x', '\\\\', '\\\\\\\\'), '\\$', '\\\\\\$')))", + "ND-Root", "replace('text', '', 'x')"); + } + + @Test + void testReplaceRegexFunction() { + testExpressionTranslationWithContext( + "replace('hello 123 world', '[0-9]+', 'NUM')", + "ND-Root", "replace-regex('hello 123 world', '[0-9]+', 'NUM')"); + } + + @Test + void testReplaceRegexFunction_WithFieldReference() { + testExpressionTranslation( + "replace(PathNode/TextField/normalize-space(text()), '\\s+', ' ')", + "{ND-Root} ${replace-regex(BT-00-Text, '\\s+', ' ')}"); + } + @Test void testSplitFunction() { testExpressionTranslationWithContext( From 3544df66f6feb53a0c420a6b5b6342bc9d44076b Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 14:47:17 +0100 Subject: [PATCH 25/31] TEDEFO-4893 Implement url-encode() function --- .../ted/efx/interfaces/ScriptGenerator.java | 8 ++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 6 ++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 5 +++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 20 +++++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 7dbaa6df..c8da88ac 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -552,6 +552,14 @@ public StringExpression composeReplaceFunction(StringExpression text, StringExpr public StringExpression composeReplaceRegexFunction(StringExpression text, StringExpression pattern, StringExpression replacement); + /** + * Composes a URL-encoding function call in the target language. + * + * @param text The string to URL-encode. + * @return The target language script that URL-encodes the string. + */ + public StringExpression composeUrlEncodeFunction(StringExpression text); + /** * Gets the target language script that retrieves the preferred language ID * out of the languages available in the given field. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 62e79550..58d72aab 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2168,6 +2168,12 @@ public void exitReplaceRegexFunction(ReplaceRegexFunctionContext ctx) { this.stack.push(this.script.composeReplaceRegexFunction(text, pattern, replacement)); } + @Override + public void exitUrlEncodeFunction(UrlEncodeFunctionContext ctx) { + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeUrlEncodeFunction(text)); + } + @Override public void exitStringJoinFunction(StringJoinFunctionContext ctx) { final StringExpression separator = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index c254a8ab..a1fa1eac 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -632,6 +632,11 @@ public StringExpression composeReplaceRegexFunction(StringExpression text, + replacement.getScript() + ")"); } + @Override + public StringExpression composeUrlEncodeFunction(StringExpression text) { + return new StringExpression("encode-for-uri(" + text.getScript() + ")"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index f3e8c94d..cc5318e8 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1760,6 +1760,26 @@ void testReplaceRegexFunction_WithFieldReference() { "{ND-Root} ${replace-regex(BT-00-Text, '\\s+', ' ')}"); } + @Test + void testUrlEncodeFunction() { + testExpressionTranslationWithContext( + "encode-for-uri('hello world')", + "ND-Root", "url-encode('hello world')"); + } + + @Test + void testUrlEncodeFunction_WithFieldReference() { + testExpressionTranslation( + "encode-for-uri(PathNode/TextField/normalize-space(text()))", + "{ND-Root} ${url-encode(BT-00-Text)}"); + } + + @Test + void testUrlEncodeFunction_WithRepeatableField_Throws() { + assertThrows(ParseCancellationException.class, + () -> translateExpression("{ND-Root} ${url-encode(ND-Root)}")); + } + @Test void testSplitFunction() { testExpressionTranslationWithContext( From b05422efb1fc1080e08450f45ae92c1904dc6d79 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 14:51:57 +0100 Subject: [PATCH 26/31] TEDEFO-4894 Implement capitalize-first() function --- .../ted/efx/interfaces/ScriptGenerator.java | 9 +++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 6 ++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 7 +++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 20 +++++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index c8da88ac..7bad67e3 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -560,6 +560,15 @@ public StringExpression composeReplaceRegexFunction(StringExpression text, */ public StringExpression composeUrlEncodeFunction(StringExpression text); + /** + * Composes a capitalize-first function call in the target language. + * Converts the first character of the string to upper case. + * + * @param text The string whose first character to capitalize. + * @return The target language script that capitalizes the first character. + */ + public StringExpression composeCapitalizeFirstFunction(StringExpression text); + /** * Gets the target language script that retrieves the preferred language ID * out of the languages available in the given field. diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 58d72aab..4f7feee9 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2174,6 +2174,12 @@ public void exitUrlEncodeFunction(UrlEncodeFunctionContext ctx) { this.stack.push(this.script.composeUrlEncodeFunction(text)); } + @Override + public void exitCapitalizeFirstFunction(CapitalizeFirstFunctionContext ctx) { + final StringExpression text = this.stack.pop(StringExpression.class); + this.stack.push(this.script.composeCapitalizeFirstFunction(text)); + } + @Override public void exitStringJoinFunction(StringJoinFunctionContext ctx) { final StringExpression separator = this.stack.pop(StringExpression.class); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index a1fa1eac..114773b4 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -637,6 +637,13 @@ public StringExpression composeUrlEncodeFunction(StringExpression text) { return new StringExpression("encode-for-uri(" + text.getScript() + ")"); } + @Override + public StringExpression composeCapitalizeFirstFunction(StringExpression text) { + return new StringExpression( + "(for $__s in " + text.getScript() + + " return concat(upper-case(substring($__s, 1, 1)), substring($__s, 2)))"); + } + @Override public StringExpression composeStringConcatenation(List list) { return new StringExpression( diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index cc5318e8..ec6d54a1 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1780,6 +1780,26 @@ void testUrlEncodeFunction_WithRepeatableField_Throws() { () -> translateExpression("{ND-Root} ${url-encode(ND-Root)}")); } + @Test + void testCapitalizeFirstFunction() { + testExpressionTranslationWithContext( + "(for $__s in 'hello' return concat(upper-case(substring($__s, 1, 1)), substring($__s, 2)))", + "ND-Root", "capitalize-first('hello')"); + } + + @Test + void testCapitalizeFirstFunction_WithFieldReference() { + testExpressionTranslation( + "(for $__s in PathNode/TextField/normalize-space(text()) return concat(upper-case(substring($__s, 1, 1)), substring($__s, 2)))", + "{ND-Root} ${capitalize-first(BT-00-Text)}"); + } + + @Test + void testCapitalizeFirstFunction_WithRepeatableField_Throws() { + assertThrows(ParseCancellationException.class, + () -> translateExpression("{ND-Root} ${capitalize-first(ND-Root)}")); + } + @Test void testSplitFunction() { testExpressionTranslationWithContext( From a16d44692ea38ae5fd3aa042c87ea06cdd0867e2 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 16:18:35 +0100 Subject: [PATCH 27/31] TEDEFO-4897 Add scalar math functions (absolute, round, round-down, round-up) --- .../ted/efx/interfaces/ScriptGenerator.java | 32 +++++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 20 +++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 20 +++++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 45 +++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 7bad67e3..ee0eb567 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -353,6 +353,38 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public NumericExpression composeStringLengthCalculation(StringExpression text); + /** + * Returns the target language script that computes the absolute value of a number. + * + * @param number The numeric expression whose absolute value is to be computed. + * @return A numeric expression representing the absolute value. + */ + public NumericExpression composeAbsFunction(NumericExpression number); + + /** + * Returns the target language script that rounds a number to the nearest integer. + * + * @param number The numeric expression to round. + * @return A numeric expression representing the rounded value. + */ + public NumericExpression composeRoundFunction(NumericExpression number); + + /** + * Returns the target language script that rounds a number down (towards negative infinity). + * + * @param number The numeric expression to round down. + * @return A numeric expression representing the rounded-down value. + */ + public NumericExpression composeFloorFunction(NumericExpression number); + + /** + * Returns the target language script that rounds a number up (towards positive infinity). + * + * @param number The numeric expression to round up. + * @return A numeric expression representing the rounded-up value. + */ + public NumericExpression composeCeilingFunction(NumericExpression number); + // #endregion Numeric Functions ------------------------------------------- // #region String Functions ----------------------------------------------- diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 4f7feee9..d40a457a 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2020,6 +2020,26 @@ public void exitStringLengthFunction(StringLengthFunctionContext ctx) { .push(this.script.composeStringLengthCalculation(this.stack.pop(StringExpression.class))); } + @Override + public void exitAbsoluteFunction(AbsoluteFunctionContext ctx) { + this.stack.push(this.script.composeAbsFunction(this.stack.pop(NumericExpression.class))); + } + + @Override + public void exitRoundFunction(RoundFunctionContext ctx) { + this.stack.push(this.script.composeRoundFunction(this.stack.pop(NumericExpression.class))); + } + + @Override + public void exitRoundDownFunction(RoundDownFunctionContext ctx) { + this.stack.push(this.script.composeFloorFunction(this.stack.pop(NumericExpression.class))); + } + + @Override + public void exitRoundUpFunction(RoundUpFunctionContext ctx) { + this.stack.push(this.script.composeCeilingFunction(this.stack.pop(NumericExpression.class))); + } + // #endregion Numeric functions --------------------------------------------- // #region String functions ------------------------------------------------- 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 114773b4..5b6b2097 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -486,6 +486,26 @@ public NumericExpression composeStringLengthCalculation(StringExpression text) { return new NumericExpression("string-length(" + text.getScript() + ")"); } + @Override + public NumericExpression composeAbsFunction(NumericExpression number) { + return new NumericExpression("abs(" + number.getScript() + ")"); + } + + @Override + public NumericExpression composeRoundFunction(NumericExpression number) { + return new NumericExpression("round(" + number.getScript() + ")"); + } + + @Override + public NumericExpression composeFloorFunction(NumericExpression number) { + return new NumericExpression("floor(" + number.getScript() + ")"); + } + + @Override + public NumericExpression composeCeilingFunction(NumericExpression number) { + return new NumericExpression("ceiling(" + number.getScript() + ")"); + } + @Override public NumericExpression composeNumericOperation(NumericExpression leftOperand, String operator, NumericExpression rightOperand) { diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index ec6d54a1..9ea57e37 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1570,6 +1570,51 @@ void testStringLengthFunction() { "string-length(BT-00-Text)"); } + @Test + void testAbsoluteFunction() { + testExpressionTranslationWithContext("abs(-5)", "ND-Root", "absolute(-5)"); + } + + @Test + void testAbsoluteFunction_WithFieldReference() { + testExpressionTranslationWithContext("abs(PathNode/NumberField/number())", "ND-Root", + "absolute(BT-00-Number)"); + } + + @Test + void testRoundFunction() { + testExpressionTranslationWithContext("round(3.7)", "ND-Root", "round(3.7)"); + } + + + @Test + void testRoundFunction_WithFieldReference() { + testExpressionTranslationWithContext("round(PathNode/NumberField/number())", "ND-Root", + "round(BT-00-Number)"); + } + + @Test + void testRoundDownFunction() { + testExpressionTranslationWithContext("floor(3.7)", "ND-Root", "round-down(3.7)"); + } + + @Test + void testRoundDownFunction_WithFieldReference() { + testExpressionTranslationWithContext("floor(PathNode/NumberField/number())", "ND-Root", + "round-down(BT-00-Number)"); + } + + @Test + void testRoundUpFunction() { + testExpressionTranslationWithContext("ceiling(3.2)", "ND-Root", "round-up(3.2)"); + } + + @Test + void testRoundUpFunction_WithFieldReference() { + testExpressionTranslationWithContext("ceiling(PathNode/NumberField/number())", "ND-Root", + "round-up(BT-00-Number)"); + } + // #endregion: Numeric functions // #region: String functions ------------------------------------------------ From 9621b956db7a50047a977f3d23f31571e9a24c46 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 16:24:10 +0100 Subject: [PATCH 28/31] TEDEFO-4898 Add aggregate math functions (min, max, average) --- .../ted/efx/interfaces/ScriptGenerator.java | 24 +++++++++++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 15 ++++++++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 15 ++++++++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 24 +++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index ee0eb567..9547a1bb 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -351,6 +351,30 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public NumericExpression composeSumOperation(NumericSequenceExpression list); + /** + * Returns the target language script that computes the minimum value in a numeric sequence. + * + * @param list The numeric sequence to find the minimum of. + * @return A numeric expression representing the minimum value. + */ + public NumericExpression composeMinFunction(NumericSequenceExpression list); + + /** + * Returns the target language script that computes the maximum value in a numeric sequence. + * + * @param list The numeric sequence to find the maximum of. + * @return A numeric expression representing the maximum value. + */ + public NumericExpression composeMaxFunction(NumericSequenceExpression list); + + /** + * Returns the target language script that computes the average of a numeric sequence. + * + * @param list The numeric sequence to average. + * @return A numeric expression representing the average value. + */ + public NumericExpression composeAvgFunction(NumericSequenceExpression list); + public NumericExpression composeStringLengthCalculation(StringExpression text); /** diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index d40a457a..bd99285e 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2014,6 +2014,21 @@ public void exitSumFunction(SumFunctionContext ctx) { this.stack.push(this.script.composeSumOperation(this.stack.pop(NumericSequenceExpression.class))); } + @Override + public void exitMinFunction(MinFunctionContext ctx) { + this.stack.push(this.script.composeMinFunction(this.stack.pop(NumericSequenceExpression.class))); + } + + @Override + public void exitMaxFunction(MaxFunctionContext ctx) { + this.stack.push(this.script.composeMaxFunction(this.stack.pop(NumericSequenceExpression.class))); + } + + @Override + public void exitAverageFunction(AverageFunctionContext ctx) { + this.stack.push(this.script.composeAvgFunction(this.stack.pop(NumericSequenceExpression.class))); + } + @Override public void exitStringLengthFunction(StringLengthFunctionContext ctx) { this.stack 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 5b6b2097..1a083e56 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -481,6 +481,21 @@ public NumericExpression composeSumOperation(NumericSequenceExpression nodeSet) return new NumericExpression("sum(" + nodeSet.getScript() + ")"); } + @Override + public NumericExpression composeMinFunction(NumericSequenceExpression list) { + return new NumericExpression("min(" + list.getScript() + ")"); + } + + @Override + public NumericExpression composeMaxFunction(NumericSequenceExpression list) { + return new NumericExpression("max(" + list.getScript() + ")"); + } + + @Override + public NumericExpression composeAvgFunction(NumericSequenceExpression list) { + return new NumericExpression("avg(" + list.getScript() + ")"); + } + @Override public NumericExpression composeStringLengthCalculation(StringExpression text) { return new NumericExpression("string-length(" + text.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 9ea57e37..f2d8fd63 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1563,6 +1563,30 @@ void testSumFunction_UsingNumericSequenceFromIteration() { "ND-Root", "sum(for number:$v in BT-00-Number return $v +1)"); } + @Test + void testMinFunction_UsingFieldReference() { + testExpressionTranslationWithContext("min(PathNode/NumberField/number())", "ND-Root", + "min(BT-00-Number)"); + } + + @Test + void testMaxFunction_UsingFieldReference() { + testExpressionTranslationWithContext("max(PathNode/NumberField/number())", "ND-Root", + "max(BT-00-Number)"); + } + + @Test + void testAverageFunction_UsingFieldReference() { + testExpressionTranslationWithContext("avg(PathNode/NumberField/number())", "ND-Root", + "average(BT-00-Number)"); + } + + @Test + void testAverageFunction_UsingNumericSequenceFromIteration() { + testExpressionTranslationWithContext("avg(for $v in PathNode/NumberField/number() return $v + 1)", + "ND-Root", "average(for number:$v in BT-00-Number return $v +1)"); + } + @Test void testStringLengthFunction() { testExpressionTranslationWithContext( From 7a5d3c409ddab2a843bb80d734edef95d7159e67 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 16:41:20 +0100 Subject: [PATCH 29/31] TEDEFO-4900 Add date component extraction functions (year, month, day) --- .../ted/efx/interfaces/ScriptGenerator.java | 24 ++++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 15 +++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 15 +++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 39 +++++++++++++++++++ 4 files changed, 93 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index 9547a1bb..d73f1d23 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -377,6 +377,30 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, public NumericExpression composeStringLengthCalculation(StringExpression text); + /** + * Returns the target language script that extracts the year component from a date. + * + * @param date The date expression to extract the year from. + * @return A numeric expression representing the year. + */ + public NumericExpression composeYearFunction(DateExpression date); + + /** + * Returns the target language script that extracts the month component from a date. + * + * @param date The date expression to extract the month from. + * @return A numeric expression representing the month (1-12). + */ + public NumericExpression composeMonthFunction(DateExpression date); + + /** + * Returns the target language script that extracts the day component from a date. + * + * @param date The date expression to extract the day from. + * @return A numeric expression representing the day of the month (1-31). + */ + public NumericExpression composeDayFunction(DateExpression date); + /** * Returns the target language script that computes the absolute value of a number. * diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index bd99285e..3966ab63 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2035,6 +2035,21 @@ public void exitStringLengthFunction(StringLengthFunctionContext ctx) { .push(this.script.composeStringLengthCalculation(this.stack.pop(StringExpression.class))); } + @Override + public void exitYearFromDateFunction(YearFromDateFunctionContext ctx) { + this.stack.push(this.script.composeYearFunction(this.stack.pop(DateExpression.class))); + } + + @Override + public void exitMonthFromDateFunction(MonthFromDateFunctionContext ctx) { + this.stack.push(this.script.composeMonthFunction(this.stack.pop(DateExpression.class))); + } + + @Override + public void exitDayFromDateFunction(DayFromDateFunctionContext ctx) { + this.stack.push(this.script.composeDayFunction(this.stack.pop(DateExpression.class))); + } + @Override public void exitAbsoluteFunction(AbsoluteFunctionContext ctx) { this.stack.push(this.script.composeAbsFunction(this.stack.pop(NumericExpression.class))); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 1a083e56..2915b11c 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -501,6 +501,21 @@ public NumericExpression composeStringLengthCalculation(StringExpression text) { return new NumericExpression("string-length(" + text.getScript() + ")"); } + @Override + public NumericExpression composeYearFunction(DateExpression date) { + return new NumericExpression("year-from-date(" + date.getScript() + ")"); + } + + @Override + public NumericExpression composeMonthFunction(DateExpression date) { + return new NumericExpression("month-from-date(" + date.getScript() + ")"); + } + + @Override + public NumericExpression composeDayFunction(DateExpression date) { + return new NumericExpression("day-from-date(" + date.getScript() + ")"); + } + @Override public NumericExpression composeAbsFunction(NumericExpression number) { return new NumericExpression("abs(" + number.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index f2d8fd63..811b0329 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1594,6 +1594,45 @@ void testStringLengthFunction() { "string-length(BT-00-Text)"); } + @Test + void testYearFromDateFunction() { + testExpressionTranslationWithContext("year-from-date(xs:date('2024-03-15Z'))", "ND-Root", + "year(date('2024-03-15Z'))"); + } + + @Test + void testYearFromDateFunction_WithFieldReference() { + testExpressionTranslationWithContext( + "year-from-date(PathNode/StartDateField/xs:date(text()))", "ND-Root", + "year(BT-00-StartDate)"); + } + + @Test + void testMonthFromDateFunction() { + testExpressionTranslationWithContext("month-from-date(xs:date('2024-03-15Z'))", "ND-Root", + "month(date('2024-03-15Z'))"); + } + + @Test + void testMonthFromDateFunction_WithFieldReference() { + testExpressionTranslationWithContext( + "month-from-date(PathNode/StartDateField/xs:date(text()))", "ND-Root", + "month(BT-00-StartDate)"); + } + + @Test + void testDayFromDateFunction() { + testExpressionTranslationWithContext("day-from-date(xs:date('2024-03-15Z'))", "ND-Root", + "day(date('2024-03-15Z'))"); + } + + @Test + void testDayFromDateFunction_WithFieldReference() { + testExpressionTranslationWithContext( + "day-from-date(PathNode/StartDateField/xs:date(text()))", "ND-Root", + "day(BT-00-StartDate)"); + } + @Test void testAbsoluteFunction() { testExpressionTranslationWithContext("abs(-5)", "ND-Root", "absolute(-5)"); From 8beee6f88b9971ccee124d479a1e9717f1eff829 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 16:51:26 +0100 Subject: [PATCH 30/31] TEDEFO-4901 Add time component extraction functions (hours, minutes, seconds) --- .../ted/efx/interfaces/ScriptGenerator.java | 24 ++++++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 15 +++++++ .../ted/efx/xpath/XPathScriptGenerator.java | 15 +++++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 39 +++++++++++++++++++ 4 files changed, 93 insertions(+) diff --git a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java index d73f1d23..07223e7a 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -401,6 +401,30 @@ public NumericExpression composeNumericOperation(NumericExpression leftOperand, */ public NumericExpression composeDayFunction(DateExpression date); + /** + * Returns the target language script that extracts the hours component from a time. + * + * @param time The time expression to extract the hours from. + * @return A numeric expression representing the hours (0-23). + */ + public NumericExpression composeHoursFunction(TimeExpression time); + + /** + * Returns the target language script that extracts the minutes component from a time. + * + * @param time The time expression to extract the minutes from. + * @return A numeric expression representing the minutes (0-59). + */ + public NumericExpression composeMinutesFunction(TimeExpression time); + + /** + * Returns the target language script that extracts the seconds component from a time. + * + * @param time The time expression to extract the seconds from. + * @return A numeric expression representing the seconds (0-59). + */ + public NumericExpression composeSecondsFunction(TimeExpression time); + /** * Returns the target language script that computes the absolute value of a number. * diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 3966ab63..48b4b5e3 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -2050,6 +2050,21 @@ public void exitDayFromDateFunction(DayFromDateFunctionContext ctx) { this.stack.push(this.script.composeDayFunction(this.stack.pop(DateExpression.class))); } + @Override + public void exitHoursFromTimeFunction(HoursFromTimeFunctionContext ctx) { + this.stack.push(this.script.composeHoursFunction(this.stack.pop(TimeExpression.class))); + } + + @Override + public void exitMinutesFromTimeFunction(MinutesFromTimeFunctionContext ctx) { + this.stack.push(this.script.composeMinutesFunction(this.stack.pop(TimeExpression.class))); + } + + @Override + public void exitSecondsFromTimeFunction(SecondsFromTimeFunctionContext ctx) { + this.stack.push(this.script.composeSecondsFunction(this.stack.pop(TimeExpression.class))); + } + @Override public void exitAbsoluteFunction(AbsoluteFunctionContext ctx) { this.stack.push(this.script.composeAbsFunction(this.stack.pop(NumericExpression.class))); diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java index 2915b11c..1f3aacde 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -516,6 +516,21 @@ public NumericExpression composeDayFunction(DateExpression date) { return new NumericExpression("day-from-date(" + date.getScript() + ")"); } + @Override + public NumericExpression composeHoursFunction(TimeExpression time) { + return new NumericExpression("hours-from-time(" + time.getScript() + ")"); + } + + @Override + public NumericExpression composeMinutesFunction(TimeExpression time) { + return new NumericExpression("minutes-from-time(" + time.getScript() + ")"); + } + + @Override + public NumericExpression composeSecondsFunction(TimeExpression time) { + return new NumericExpression("seconds-from-time(" + time.getScript() + ")"); + } + @Override public NumericExpression composeAbsFunction(NumericExpression number) { return new NumericExpression("abs(" + number.getScript() + ")"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index 811b0329..c06089cb 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -1633,6 +1633,45 @@ void testDayFromDateFunction_WithFieldReference() { "day(BT-00-StartDate)"); } + @Test + void testHoursFromTimeFunction() { + testExpressionTranslationWithContext("hours-from-time(xs:time('14:30:00Z'))", "ND-Root", + "hours(time('14:30:00Z'))"); + } + + @Test + void testHoursFromTimeFunction_WithFieldReference() { + testExpressionTranslationWithContext( + "hours-from-time(PathNode/StartTimeField/xs:time(text()))", "ND-Root", + "hours(BT-00-StartTime)"); + } + + @Test + void testMinutesFromTimeFunction() { + testExpressionTranslationWithContext("minutes-from-time(xs:time('14:30:00Z'))", "ND-Root", + "minutes(time('14:30:00Z'))"); + } + + @Test + void testMinutesFromTimeFunction_WithFieldReference() { + testExpressionTranslationWithContext( + "minutes-from-time(PathNode/StartTimeField/xs:time(text()))", "ND-Root", + "minutes(BT-00-StartTime)"); + } + + @Test + void testSecondsFromTimeFunction() { + testExpressionTranslationWithContext("seconds-from-time(xs:time('14:30:45Z'))", "ND-Root", + "seconds(time('14:30:45Z'))"); + } + + @Test + void testSecondsFromTimeFunction_WithFieldReference() { + testExpressionTranslationWithContext( + "seconds-from-time(PathNode/StartTimeField/xs:time(text()))", "ND-Root", + "seconds(BT-00-StartTime)"); + } + @Test void testAbsoluteFunction() { testExpressionTranslationWithContext("abs(-5)", "ND-Root", "absolute(-5)"); From 8087f3aa20340349f329f74b86a5e874e8d45712 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 15 Feb 2026 20:34:50 +0100 Subject: [PATCH 31/31] TEDEFO-4902 Add locale-aware formatting functions (format-short/medium/long) --- .../efx/exceptions/InvalidUsageException.java | 12 +- .../ted/efx/interfaces/ScriptGenerator.java | 51 ++++++++ .../efx/sdk2/EfxExpressionTranslatorV2.java | 50 +++++++- .../ted/efx/sdk2/EfxTemplateTranslatorV2.java | 95 ++++++++++++-- .../ted/efx/xpath/XPathScriptGenerator.java | 32 +++++ .../sdk2/EfxExpressionTranslatorV2Test.java | 63 +++++++--- .../efx/sdk2/EfxTemplateTranslatorV2Test.java | 116 ++++++++++++++++++ 7 files changed, 385 insertions(+), 34 deletions(-) diff --git a/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java b/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java index ca5fd592..d91bb818 100644 --- a/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java +++ b/src/main/java/eu/europa/ted/efx/exceptions/InvalidUsageException.java @@ -16,7 +16,9 @@ import org.antlr.v4.runtime.misc.ParseCancellationException; /** - * Exception thrown when field validation fails during EFX template processing. + * Exception thrown when an EFX construct is used incorrectly, such as referencing + * a non-withholdable field for privacy properties, or calling a template-only + * function in an expression or validation rule. */ @SuppressWarnings("squid:MaximumInheritanceDepth") // Necessary to integrate with ANTLR4 parser cancellation public class InvalidUsageException extends ParseCancellationException { @@ -26,7 +28,8 @@ public enum ErrorCode { SHORTHAND_REQUIRES_FIELD_CONTEXT, INVALID_NOTICE_SUBTYPE_RANGE_ORDER, INVALID_NOTICE_SUBTYPE_TOKEN, - FIELD_NOT_WITHHOLDABLE + FIELD_NOT_WITHHOLDABLE, + TEMPLATE_ONLY_FUNCTION } private static final String SHORTHAND_REQUIRES_CODE_OR_INDICATOR = "Indirect label reference shorthand #{%1$s}, requires a field of type 'code' or 'indicator'. Field %1$s is of type %2$s."; @@ -34,6 +37,7 @@ public enum ErrorCode { private static final String INVALID_NOTICE_SUBTYPE_RANGE_ORDER = "Notice subtype range '%s-%s' is not in ascending order."; private static final String INVALID_NOTICE_SUBTYPE_TOKEN = "Invalid notice subtype token '%s'. Expected format: 'X' or 'X-Y'."; private static final String FIELD_NOT_WITHHOLDABLE = "Field '%s' is always published and cannot be withheld from publication."; + private static final String TEMPLATE_ONLY_FUNCTION = "Function '%s' can only be used in templates, not in expressions or validation rules."; private final ErrorCode errorCode; @@ -65,4 +69,8 @@ public static InvalidUsageException invalidNoticeSubtypeToken(String token) { public static InvalidUsageException fieldNotWithholdable(String fieldId) { return new InvalidUsageException(ErrorCode.FIELD_NOT_WITHHOLDABLE, String.format(FIELD_NOT_WITHHOLDABLE, fieldId)); } + + public static InvalidUsageException templateOnlyFunction(String functionName) { + return new InvalidUsageException(ErrorCode.TEMPLATE_ONLY_FUNCTION, String.format(TEMPLATE_ONLY_FUNCTION, functionName)); + } } 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 07223e7a..b5e6481b 100644 --- a/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/interfaces/ScriptGenerator.java @@ -848,6 +848,57 @@ public DateExpression composeSubtraction(final DateExpression date, public StringExpression composeNumberFormatting(NumericExpression number, StringExpression format); + /** + * Formats a date using a short locale-aware format (e.g. 15/02/2026). + * + * @param date The date expression to format. + * @return A string expression representing the formatted date. + */ + public StringExpression composeFormatDateShort(DateExpression date); + + /** + * Formats a date using a medium locale-aware format with abbreviated month name + * (e.g. 15 Feb 2026). + * + * @param date The date expression to format. + * @return A string expression representing the formatted date. + */ + public StringExpression composeFormatDateMedium(DateExpression date); + + /** + * Formats a date using a long locale-aware format with full month name + * (e.g. 15 February 2026). + * + * @param date The date expression to format. + * @return A string expression representing the formatted date. + */ + public StringExpression composeFormatDateLong(DateExpression date); + + /** + * Formats a time using a short locale-aware format (e.g. 14:30 CET). + * + * @param time The time expression to format. + * @return A string expression representing the formatted time. + */ + public StringExpression composeFormatTimeShort(TimeExpression time); + + /** + * Formats a time using a medium locale-aware format with seconds (e.g. 14:30:00). + * + * @param time The time expression to format. + * @return A string expression representing the formatted time. + */ + public StringExpression composeFormatTimeMedium(TimeExpression time); + + /** + * Formats a time using a long locale-aware format with seconds and timezone + * (e.g. 14:30:00 CET). + * + * @param time The time expression to format. + * @return A string expression representing the formatted time. + */ + public StringExpression composeFormatTimeLong(TimeExpression time); + public DurationExpression composeMultiplication(final NumericExpression number, final DurationExpression duration); diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java index 48b4b5e3..8f626f8f 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java @@ -60,7 +60,6 @@ import eu.europa.ted.efx.model.expressions.scalar.BooleanExpression; import eu.europa.ted.efx.model.expressions.scalar.DateExpression; import eu.europa.ted.efx.model.expressions.scalar.DurationExpression; -import eu.europa.ted.efx.model.expressions.scalar.MultilingualStringPath; import eu.europa.ted.efx.model.expressions.scalar.NumericExpression; import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression; import eu.europa.ted.efx.model.expressions.scalar.ScalarPath; @@ -2162,6 +2161,51 @@ public void exitFormatNumberFunction(FormatNumberFunctionContext ctx) { this.stack.push(this.script.composeNumberFormatting(number, format)); } + @Override + public void exitFormatShortDateFunction(FormatShortDateFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-short"); + } + + @Override + public void exitFormatShortTimeFunction(FormatShortTimeFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-short"); + } + + @Override + public void exitFormatMediumDateFunction(FormatMediumDateFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-medium"); + } + + @Override + public void exitFormatMediumTimeFunction(FormatMediumTimeFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-medium"); + } + + @Override + public void exitFormatLongDateFunction(FormatLongDateFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-long"); + } + + @Override + public void exitFormatLongTimeFunction(FormatLongTimeFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-long"); + } + + @Override + public void exitFormatShortDateTimeFunction(FormatShortDateTimeFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-short"); + } + + @Override + public void exitFormatMediumDateTimeFunction(FormatMediumDateTimeFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-medium"); + } + + @Override + public void exitFormatLongDateTimeFunction(FormatLongDateTimeFunctionContext ctx) { + throw InvalidUsageException.templateOnlyFunction("format-long"); + } + // #region New in EFX-2 ----------------------------------------------------- @Override @@ -2254,12 +2298,12 @@ public void exitStringJoinFunction(StringJoinFunctionContext ctx) { @Override public void exitPreferredLanguageFunction(PreferredLanguageFunctionContext ctx) { - this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringPath.class))); + throw InvalidUsageException.templateOnlyFunction("preferred-language"); } @Override public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionContext ctx) { - this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringPath.class))); + throw InvalidUsageException.templateOnlyFunction("preferred-language-text"); } @Override diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java index a9216d2f..95a3ac0e 100644 --- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java +++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java @@ -90,6 +90,7 @@ import eu.europa.ted.efx.model.variables.Template; import eu.europa.ted.efx.model.variables.Variable; import eu.europa.ted.efx.model.variables.Variables; +import eu.europa.ted.efx.model.expressions.scalar.MultilingualStringPath; import eu.europa.ted.efx.sdk2.EfxParser.*; /** @@ -834,11 +835,9 @@ public void exitDictionaryKeyClause(DictionaryKeyClauseContext ctx) { public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { var expression = this.stack.pop(Expression.class); - // TODO: Review this in EFX-2 - - // This is a hack to make sure that the date and time expressions are rendered in the correct - // format. We had to do this because EFX 1 does not support the format-date() and format-time() - // functions. + // Implicit formatting: date and time expressions in template blocks are automatically + // formatted using format-short for display. + // Users can override by using explicit format-short/format-medium/format-long in the expression. if (TypedExpression.class.isAssignableFrom(expression.getClass())) { if (EfxDataType.Date.class.isAssignableFrom(((TypedExpression) expression).getDataType())) { @@ -850,19 +849,19 @@ public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { this.script.composeIteratorList( List.of(this.script.composeIteratorExpression(loopVariable.declarationExpression, new DateSequenceExpression(expression.getScript())))), - new StringExpression("format-date($item, '[D01]/[M01]/[Y0001]')"), + this.script.composeFormatDateShort(new DateExpression(loopVariable.referenceExpression.getScript())), StringSequenceExpression.class); } else if (EfxDataType.Time.class.isAssignableFrom(((TypedExpression) expression).getDataType())) { var loopVariable = new Variable("item", - this.script.composeVariableDeclaration("item", DateExpression.class), DateExpression.empty(), - this.script.composeVariableReference("item", DateExpression.class)); + this.script.composeVariableDeclaration("item", TimeExpression.class), TimeExpression.empty(), + this.script.composeVariableReference("item", TimeExpression.class)); expression = this.script.composeForExpression( this.script.composeIteratorList( List.of(this.script.composeIteratorExpression(loopVariable.declarationExpression, new TimeSequenceExpression(expression.getScript())))), - new StringExpression("format-time($item, '[H01]:[m01] [Z]')"), + this.script.composeFormatTimeShort(new TimeExpression(loopVariable.referenceExpression.getScript())), StringSequenceExpression.class); } } @@ -870,6 +869,84 @@ public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) { this.stack.push(expression); } + // #region Formatting functions (template-only) -------------------------------- + + @Override + public void exitFormatShortDateFunction(FormatShortDateFunctionContext ctx) { + this.stack.push(this.script.composeFormatDateShort(this.stack.pop(DateExpression.class))); + } + + @Override + public void exitFormatShortTimeFunction(FormatShortTimeFunctionContext ctx) { + this.stack.push(this.script.composeFormatTimeShort(this.stack.pop(TimeExpression.class))); + } + + @Override + public void exitFormatShortDateTimeFunction(FormatShortDateTimeFunctionContext ctx) { + TimeExpression time = this.stack.pop(TimeExpression.class); + DateExpression date = this.stack.pop(DateExpression.class); + this.stack.push(this.script.composeStringConcatenation(List.of( + this.script.composeFormatDateShort(date), + this.script.getStringLiteralFromUnquotedString(" "), + this.script.composeFormatTimeShort(time)))); + } + + @Override + public void exitFormatMediumDateFunction(FormatMediumDateFunctionContext ctx) { + this.stack.push(this.script.composeFormatDateMedium(this.stack.pop(DateExpression.class))); + } + + @Override + public void exitFormatMediumTimeFunction(FormatMediumTimeFunctionContext ctx) { + this.stack.push(this.script.composeFormatTimeMedium(this.stack.pop(TimeExpression.class))); + } + + @Override + public void exitFormatMediumDateTimeFunction(FormatMediumDateTimeFunctionContext ctx) { + TimeExpression time = this.stack.pop(TimeExpression.class); + DateExpression date = this.stack.pop(DateExpression.class); + this.stack.push(this.script.composeStringConcatenation(List.of( + this.script.composeFormatDateMedium(date), + this.script.getStringLiteralFromUnquotedString(" "), + this.script.composeFormatTimeMedium(time)))); + } + + @Override + public void exitFormatLongDateFunction(FormatLongDateFunctionContext ctx) { + this.stack.push(this.script.composeFormatDateLong(this.stack.pop(DateExpression.class))); + } + + @Override + public void exitFormatLongTimeFunction(FormatLongTimeFunctionContext ctx) { + this.stack.push(this.script.composeFormatTimeLong(this.stack.pop(TimeExpression.class))); + } + + @Override + public void exitFormatLongDateTimeFunction(FormatLongDateTimeFunctionContext ctx) { + TimeExpression time = this.stack.pop(TimeExpression.class); + DateExpression date = this.stack.pop(DateExpression.class); + this.stack.push(this.script.composeStringConcatenation(List.of( + this.script.composeFormatDateLong(date), + this.script.getStringLiteralFromUnquotedString(" "), + this.script.composeFormatTimeLong(time)))); + } + + // #endregion Formatting functions --------------------------------------------- + + // #region Preferred language functions ---------------------------------------- + + @Override + public void exitPreferredLanguageFunction(PreferredLanguageFunctionContext ctx) { + this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringPath.class))); + } + + @Override + public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionContext ctx) { + this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringPath.class))); + } + + // #endregion Preferred language functions ------------------------------------- + /*** * Handles the $value shorthand syntax which renders the value of the field declared as context in * the current line of the template. 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 1f3aacde..4ad7741e 100644 --- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java +++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java @@ -728,6 +728,38 @@ public StringExpression composeNumberFormatting(NumericExpression number, return new StringExpression("format-number(" + number.getScript() + ", " + formatString + ")"); } + @Override + public StringExpression composeFormatDateShort(DateExpression date) { + return new StringExpression("format-date(" + date.getScript() + ", '[D01]/[M01]/[Y0001]')"); + } + + @Override + public StringExpression composeFormatDateMedium(DateExpression date) { + String lang = this.translatorOptions.getPrimaryLanguage2LetterCode(); + return new StringExpression("format-date(" + date.getScript() + ", '[D01] [MNn,3-3] [Y0001]', '" + lang + "', (), ())"); + } + + @Override + public StringExpression composeFormatDateLong(DateExpression date) { + String lang = this.translatorOptions.getPrimaryLanguage2LetterCode(); + return new StringExpression("format-date(" + date.getScript() + ", '[D01] [MNn] [Y0001]', '" + lang + "', (), ())"); + } + + @Override + public StringExpression composeFormatTimeShort(TimeExpression time) { + return new StringExpression("format-time(" + time.getScript() + ", '[H01]:[m01] [Z]')"); + } + + @Override + public StringExpression composeFormatTimeMedium(TimeExpression time) { + return new StringExpression("format-time(" + time.getScript() + ", '[H01]:[m01]:[s01]')"); + } + + @Override + public StringExpression composeFormatTimeLong(TimeExpression time) { + return new StringExpression("format-time(" + time.getScript() + ", '[H01]:[m01]:[s01] [Z]')"); + } + @Override public StringLiteral getStringLiteralFromUnquotedString(String value) { return new StringLiteral("'" + value + "'"); diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java index c06089cb..c608ed41 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2Test.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import eu.europa.ted.efx.EfxTestsBase; import eu.europa.ted.efx.exceptions.InvalidIdentifierException; +import eu.europa.ted.efx.exceptions.InvalidUsageException; import eu.europa.ted.efx.exceptions.TypeMismatchException; class EfxExpressionTranslatorV2Test extends EfxTestsBase { @@ -158,22 +159,18 @@ void testLikePatternCondition_WithTextMultilingualField() { "{ND-Root} ${every text:$lang in BT-00-Text-Multilingual/@languageID satisfies BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == $lang] like '[0-9]*'}"); } - @Test - void testPreferredLanguageFunction() { - testExpressionTranslation("PathNode/TextMultilingualField[./@languageID = efx:preferred-language(.)]/normalize-space(text())", - "{ND-Root} ${BT-00-Text-Multilingual[BT-00-Text-Multilingual/@languageID == preferred-language(BT-00-Text-Multilingual)]}"); - } - - @Test - void testPreferredLanguageFunction_InPredicate() { - testExpressionTranslation("efx:preferred-language(PathNode/TextMultilingualField)", - "{ND-Root} ${preferred-language(BT-00-Text-Multilingual)}"); + @Test + void testPreferredLanguage_ThrowsInExpressionContext() { + InvalidUsageException exception = assertThrows(InvalidUsageException.class, + () -> translateExpressionWithContext("ND-Root", "preferred-language(BT-00-Text-Multilingual)")); + assertEquals(InvalidUsageException.ErrorCode.TEMPLATE_ONLY_FUNCTION, exception.getErrorCode()); } - @Test - void testPreferredLanguageTextFunction() { - testExpressionTranslation("efx:preferred-language-text(PathNode/TextMultilingualField)", - "{ND-Root} ${preferred-language-text(BT-00-Text-Multilingual)}"); + @Test + void testPreferredLanguageText_ThrowsInExpressionContext() { + InvalidUsageException exception = assertThrows(InvalidUsageException.class, + () -> translateExpressionWithContext("ND-Root", "preferred-language-text(BT-00-Text-Multilingual)")); + assertEquals(InvalidUsageException.ErrorCode.TEMPLATE_ONLY_FUNCTION, exception.getErrorCode()); } @Test @@ -1355,9 +1352,10 @@ void testComputedProperty_wasWithheld() { @Test void testComputedProperty_wasWithheld_onNonWithholdableField() { - assertThrows(ParseCancellationException.class, + InvalidUsageException exception = assertThrows(InvalidUsageException.class, () -> translateExpressionWithContext("BT-00-Text", "BT-00-Text:wasWithheld")); + assertEquals(InvalidUsageException.ErrorCode.FIELD_NOT_WITHHOLDABLE, exception.getErrorCode()); } @Test @@ -1375,9 +1373,10 @@ void testComputedProperty_isWithheld() { @Test void testComputedProperty_isWithheld_onNonWithholdableField() { - assertThrows(ParseCancellationException.class, + InvalidUsageException exception = assertThrows(InvalidUsageException.class, () -> translateExpressionWithContext("BT-00-Text", "BT-00-Text:isWithheld")); + assertEquals(InvalidUsageException.ErrorCode.FIELD_NOT_WITHHOLDABLE, exception.getErrorCode()); } @Test @@ -1414,9 +1413,10 @@ void testComputedProperty_isDisclosed() { @Test void testComputedProperty_isDisclosed_onNonWithholdableField() { - assertThrows(ParseCancellationException.class, + InvalidUsageException exception = assertThrows(InvalidUsageException.class, () -> translateExpressionWithContext("BT-00-Text", "BT-00-Text:isDisclosed")); + assertEquals(InvalidUsageException.ErrorCode.FIELD_NOT_WITHHOLDABLE, exception.getErrorCode()); } @Test @@ -1432,9 +1432,10 @@ void testComputedProperty_isMasked() { @Test void testComputedProperty_isMasked_onNonWithholdableField() { - assertThrows(ParseCancellationException.class, + InvalidUsageException exception = assertThrows(InvalidUsageException.class, () -> translateExpressionWithContext("BT-00-Text", "BT-00-Text:isMasked")); + assertEquals(InvalidUsageException.ErrorCode.FIELD_NOT_WITHHOLDABLE, exception.getErrorCode()); } @Test @@ -1450,7 +1451,7 @@ void testComputedProperty_isMasked_numericField() { @Test void testComputedProperty_isMasked_repeatingFieldFromContext() { - assertThrows(ParseCancellationException.class, + assertThrows(TypeMismatchException.class, () -> translateExpressionWithContext("ND-Root", "BT-00-Text-In-Repeatable-Node:isMasked")); } @@ -1512,9 +1513,10 @@ void testMetadataProperty_privacyCode() { @Test void testMetadataProperty_privacyCode_onNonWithholdableField() { - assertThrows(ParseCancellationException.class, + InvalidUsageException exception = assertThrows(InvalidUsageException.class, () -> translateExpressionWithContext("BT-00-Text", "BT-00-Text:privacyCode")); + assertEquals(InvalidUsageException.ErrorCode.FIELD_NOT_WITHHOLDABLE, exception.getErrorCode()); } // #endregion: Boolean functions @@ -2119,6 +2121,27 @@ void testFormatNumberFunction() { "ND-Root", "format-number(BT-00-Number, '#,##0.00')"); } + @Test + void testFormatShort_ThrowsInExpressionContext() { + InvalidUsageException exception = assertThrows(InvalidUsageException.class, + () -> translateExpressionWithContext("ND-Root", "format-short(date('2026-02-15'))")); + assertEquals(InvalidUsageException.ErrorCode.TEMPLATE_ONLY_FUNCTION, exception.getErrorCode()); + } + + @Test + void testFormatMedium_ThrowsInExpressionContext() { + InvalidUsageException exception = assertThrows(InvalidUsageException.class, + () -> translateExpressionWithContext("ND-Root", "format-medium(date('2026-02-15'))")); + assertEquals(InvalidUsageException.ErrorCode.TEMPLATE_ONLY_FUNCTION, exception.getErrorCode()); + } + + @Test + void testFormatLong_ThrowsInExpressionContext() { + InvalidUsageException exception = assertThrows(InvalidUsageException.class, + () -> translateExpressionWithContext("ND-Root", "format-long(date('2026-02-15'))")); + assertEquals(InvalidUsageException.ErrorCode.TEMPLATE_ONLY_FUNCTION, exception.getErrorCode()); + } + // #endregion: String functions // #region: Date functions -------------------------------------------------- diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java index fa775c03..346fd5fe 100644 --- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java +++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java @@ -1428,6 +1428,122 @@ void testExpressionBlock_ShorthandFieldValueReferenceFromContextField_WithNodeCo // #endregion Expression block ----------------------------------------------- + // #region Formatting functions ------------------------------------------------ + + @Test + void testFormatShortDate() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(format-date(xs:date('2026-02-15'), '[D01]/[M01]/[Y0001]')) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-short(date('2026-02-15'))}")); + } + + @Test + void testFormatShortDate_WithFieldReference() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(format-date(PathNode/StartDateField/xs:date(text()), '[D01]/[M01]/[Y0001]')) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-short(BT-00-StartDate)}")); + } + + @Test + void testFormatMediumDate() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(format-date(xs:date('2026-02-15'), '[D01] [MNn,3-3] [Y0001]', 'en', (), ())) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-medium(date('2026-02-15'))}")); + } + + @Test + void testFormatLongDate() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(format-date(xs:date('2026-02-15'), '[D01] [MNn] [Y0001]', 'en', (), ())) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-long(date('2026-02-15'))}")); + } + + @Test + void testFormatShortTime() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(format-time(xs:time('14:30:00Z'), '[H01]:[m01] [Z]')) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-short(time('14:30:00Z'))}")); + } + + @Test + void testFormatMediumTime() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(format-time(xs:time('14:30:00Z'), '[H01]:[m01]:[s01]')) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-medium(time('14:30:00Z'))}")); + } + + @Test + void testFormatLongTime() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(format-time(xs:time('14:30:00Z'), '[H01]:[m01]:[s01] [Z]')) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-long(time('14:30:00Z'))}")); + } + + @Test + void testFormatShortDateTime() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(concat(format-date(xs:date('2026-02-15'), '[D01]/[M01]/[Y0001]'), ' ', format-time(xs:time('14:30:00Z'), '[H01]:[m01] [Z]'))) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-short(date('2026-02-15'), time('14:30:00Z'))}")); + } + + @Test + void testFormatMediumDateTime() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(concat(format-date(xs:date('2026-02-15'), '[D01] [MNn,3-3] [Y0001]', 'en', (), ()), ' ', format-time(xs:time('14:30:00Z'), '[H01]:[m01]:[s01]'))) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-medium(date('2026-02-15'), time('14:30:00Z'))}")); + } + + @Test + void testFormatLongDateTime() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(concat(format-date(xs:date('2026-02-15'), '[D01] [MNn] [Y0001]', 'en', (), ()), ' ', format-time(xs:time('14:30:00Z'), '[H01]:[m01]:[s01] [Z]'))) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${format-long(date('2026-02-15'), time('14:30:00Z'))}")); + } + + // #endregion Formatting functions --------------------------------------------- + + // #region Preferred language functions ---------------------------------------- + + @Test + void testPreferredLanguageFunction() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(efx:preferred-language(PathNode/TextMultilingualField)) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${preferred-language(BT-00-Text-Multilingual)}")); + } + + @Test + void testPreferredLanguageTextFunction() { + assertEquals( + lines("TEMPLATES:", + "let body01() -> { eval(efx:preferred-language-text(PathNode/TextMultilingualField)) }", + "MAIN:", "for-each(/*).call(body01())"), + translateTemplate("{/} ${preferred-language-text(BT-00-Text-Multilingual)}")); + } + + // #endregion Preferred language functions ------------------------------------- + // #region contextDeclarationBlock ------------------------------------------- @Test