diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32890d1e..dc2266f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-# EFX Toolkit 2.0.0-alpha.1 Release Notes
+# EFX Toolkit 2.0.0-alpha.2 Release Notes
_The EFX Toolkit for Java developers is a library that enables the transpilation of [EFX](https://docs.ted.europa.eu/eforms/latest/efx) expressions and templates to different target languages. It also includes an implementation of an EFX-to-XPath transpiler._
@@ -6,17 +6,23 @@ _The EFX Toolkit for Java developers is a library that enables the transpilation
## In this release
-This release:
+This release improves translation of EFX-1 templates as follows:
+
+- Renders sequences of labels when a sequence expression is used to provide asset-ids.
+- Renders distinct labels from sequences.
+- Improves date and time formatting.
+
+This release also includes a refactoring that moved XPath processing classes to the eForms Core Library 1.2.0 to improve reusability.
+
+There are no changes in EFX-2 translation included in this release.
-- Improves translation of EFX-1.
-- Adds support for translating EFX-2 expressions and templates.
-- Removes support of the obsolete EFX versions included in pre-release versions of the SDK (SDK 0.x.x).
-- Introduces some breaking changes in the interfaces that need to be implemented by new translators (SymbolResolver, ScriptGenerator, MarkupGenerator).
## EFX-1 Support
-Although this is a pre-release version of the EFX Toolkit, it provides production-level support for EFX-1 transpilation.
-EFX-1 is the current version of EFX released with SDK 1. Transpilation of EFX-1 to XPath is on par with the EFX Toolkit 1.3.0.
+Although this is a pre-release version of the EFX Toolkit, it provides production-level support for EFX-1 transpilation. EFX-1 is the current version of EFX released with SDK 1.
+
+NOTE: Transpilation of EFX-1 to XPath and XSL in this version of the EFX Toolkit is **better than** what is provided by **EFX Toolkit 1.3.0**.
+
## EFX-2 Support
@@ -35,7 +41,7 @@ Users of the Toolkit that only use the included EFX-to-XPath transpiler will not
## Future development
-Further alpha and beta releases of SDK 2 and EFX Toolkit 2 will be issued. While in "alpha" development stage, further braking changes may be introduced. SDK 2 and EFX 2 are expected to continue to be under development util late 2023.
+Further alpha and beta releases of SDK 2 and EFX Toolkit 2 will be issued. While in "alpha" development stage, further braking changes may be introduced. SDK 2 and EFX 2 are expected to continue to be under development through the first quarter of 2024.
---
@@ -51,4 +57,4 @@ This version of the EFX Toolkit has a compile-time dependency on the following v
- eForms SDK 1.x.x
- eForms SDK 2.0.0-alpha.1
-It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.0.5.
+It also depends on the [eForms Core Java library](https://github.com/OP-TED/eforms-core-java) version 1.3.0.
diff --git a/README.md b/README.md
index d78dcba6..972c9f8a 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ See ".github/workflows/settings.xml".
Unit tests are available under `src/test/java/`. They show in particular a variety of EFX expressions and the corresponding XPath expression.
-After running the unit tests with `mvn test`, you can generate a coverage report with`mvn jacoco:report`.
+After running the unit tests with `mvn test`, you can generate a coverage report with `mvn jacoco:report`.
The report is available under `target/site/jacoco/`, in HTML, CSV, and XML format.
## Download
diff --git a/pom.xml b/pom.xml
index b5c9a1d3..bfd0d30c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
eu.europa.ted.eforms
efx-toolkit-java
- 2.0.0-alpha.1
+ 2.0.0-alpha.2
jar
EFX Toolkit for Java
@@ -49,7 +49,7 @@
UTF-8
- 2023-05-30T06:25:09Z
+ 2023-07-28T16:03:53Z
s01.oss.sonatype.org
@@ -59,7 +59,7 @@
${project.build.directory}/eforms-sdk/antlr4
- 1.0.5
+ 1.3.0
4.9.3
@@ -330,7 +330,6 @@
**/EfxBaseListener.class
**/EfxLexer.class
**/EfxParser*.class
- **/XPath20*.class
diff --git a/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4 b/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4
deleted file mode 100644
index 893ef349..00000000
--- a/src/main/antlr4/eu/europa/ted/efx/xpath/XPath20.g4
+++ /dev/null
@@ -1,343 +0,0 @@
-// XPath v2.0
-// Author--Ken Domino
-// Date--2 Jan 2022
-//
-// This is a faithful implementation of the XPath version 2.0 grammar
-// from the spec at https://www.w3.org/TR/xpath20/
-
-grammar XPath20;
-
-// [1]
-xpath : expr EOF ;
-expr : exprsingle ( COMMA exprsingle)* ;
-exprsingle : forexpr | quantifiedexpr | ifexpr | orexpr ;
-forexpr : simpleforclause KW_RETURN exprsingle ;
-// [5]
-simpleforclause : KW_FOR DOLLAR varname KW_IN exprsingle ( COMMA DOLLAR varname KW_IN exprsingle )* ;
-quantifiedexpr : ( KW_SOME | KW_EVERY) DOLLAR varname KW_IN exprsingle ( COMMA DOLLAR varname KW_IN exprsingle)* KW_SATISFIES exprsingle ;
-ifexpr : KW_IF OP expr CP KW_THEN exprsingle KW_ELSE exprsingle ;
-orexpr : andexpr ( KW_OR andexpr )* ;
-andexpr : comparisonexpr ( KW_AND comparisonexpr )* ;
-// [10]
-comparisonexpr : rangeexpr ( (valuecomp | generalcomp | nodecomp) rangeexpr )? ;
-rangeexpr : additiveexpr ( KW_TO additiveexpr )? ;
-additiveexpr : multiplicativeexpr ( (PLUS | MINUS) multiplicativeexpr )* ;
-multiplicativeexpr : unionexpr ( (STAR | KW_DIV | KW_IDIV | KW_MOD) unionexpr )* ;
-unionexpr : intersectexceptexpr ( (KW_UNION | P) intersectexceptexpr )* ;
-// [15]
-intersectexceptexpr : instanceofexpr ( ( KW_INTERSECT | KW_EXCEPT) instanceofexpr )* ;
-instanceofexpr : treatexpr ( KW_INSTANCE KW_OF sequencetype )? ;
-treatexpr : castableexpr ( KW_TREAT KW_AS sequencetype )? ;
-castableexpr : castexpr ( KW_CASTABLE KW_AS singletype )? ;
-castexpr : unaryexpr ( KW_CAST KW_AS singletype )? ;
-// [20]
-unaryexpr : ( MINUS | PLUS)* valueexpr ;
-valueexpr : pathexpr ;
-generalcomp : EQ | NE | LT | LE | GT | GE ;
-valuecomp : KW_EQ | KW_NE | KW_LT | KW_LE | KW_GT | KW_GE ;
-nodecomp : KW_IS | LL | GG ;
-// [25]
-pathexpr : ( SLASH relativepathexpr?) | ( SS relativepathexpr) | relativepathexpr ;
-relativepathexpr : stepexpr (( SLASH | SS) stepexpr)* ;
-stepexpr : filterexpr | axisstep ;
-axisstep : (reversestep | forwardstep) predicatelist ;
-forwardstep : (forwardaxis nodetest) | abbrevforwardstep ;
-// [30]
-forwardaxis : ( KW_CHILD COLONCOLON) | ( KW_DESCENDANT COLONCOLON) | ( KW_ATTRIBUTE COLONCOLON) | ( KW_SELF COLONCOLON) | ( KW_DESCENDANT_OR_SELF COLONCOLON) | ( KW_FOLLOWING_SIBLING COLONCOLON) | ( KW_FOLLOWING COLONCOLON) | ( KW_NAMESPACE COLONCOLON) ;
-abbrevforwardstep : AT? nodetest ;
-reversestep : (reverseaxis nodetest) | abbrevreversestep ;
-reverseaxis : ( KW_PARENT COLONCOLON) | ( KW_ANCESTOR COLONCOLON) | ( KW_PRECEDING_SIBLING COLONCOLON) | ( KW_PRECEDING COLONCOLON) | ( KW_ANCESTOR_OR_SELF COLONCOLON) ;
-abbrevreversestep : DD ;
-// [35]
-nodetest : kindtest | nametest ;
-nametest : qname | wildcard ;
-wildcard : STAR | (NCName CS) | ( SC NCName) ;
-filterexpr : primaryexpr predicatelist ;
-predicatelist : predicate* ;
-// [40]
-predicate : OB expr CB ;
-primaryexpr : literal | varref | parenthesizedexpr | contextitemexpr | functioncall ;
-literal : numericliteral | StringLiteral ;
-numericliteral : IntegerLiteral | DecimalLiteral | DoubleLiteral ;
-varref : DOLLAR varname ;
-// [45]
-varname : qname ;
-parenthesizedexpr : OP expr? CP ;
-contextitemexpr : D ;
-functioncall :
- { !(
- getInputStream().LA(1)==KW_ARRAY
- || getInputStream().LA(1)==KW_ATTRIBUTE
- || getInputStream().LA(1)==KW_COMMENT
- || getInputStream().LA(1)==KW_DOCUMENT_NODE
- || getInputStream().LA(1)==KW_ELEMENT
- || getInputStream().LA(1)==KW_EMPTY_SEQUENCE
- || getInputStream().LA(1)==KW_FUNCTION
- || getInputStream().LA(1)==KW_IF
- || getInputStream().LA(1)==KW_ITEM
- || getInputStream().LA(1)==KW_MAP
- || getInputStream().LA(1)==KW_NAMESPACE_NODE
- || getInputStream().LA(1)==KW_NODE
- || getInputStream().LA(1)==KW_PROCESSING_INSTRUCTION
- || getInputStream().LA(1)==KW_SCHEMA_ATTRIBUTE
- || getInputStream().LA(1)==KW_SCHEMA_ELEMENT
- || getInputStream().LA(1)==KW_TEXT
- ) }?
- qname OP (exprsingle ( COMMA exprsingle)*)? CP ;
-singletype : atomictype QM? ;
-// [50]
-sequencetype : ( KW_EMPTY_SEQUENCE OP CP) | (itemtype occurrenceindicator?) ;
-occurrenceindicator : QM | STAR | PLUS ;
-itemtype : kindtest | ( KW_ITEM OP CP) | atomictype ;
-atomictype : qname ;
-kindtest : documenttest | elementtest | attributetest | schemaelementtest | schemaattributetest | pitest | commenttest | texttest | anykindtest ;
-// [55]
-anykindtest : KW_NODE OP CP ;
-documenttest : KW_DOCUMENT_NODE OP (elementtest | schemaelementtest)? CP ;
-texttest : KW_TEXT OP CP ;
-commenttest : KW_COMMENT OP CP ;
-pitest : KW_PROCESSING_INSTRUCTION OP (NCName | StringLiteral)? CP ;
-// [60]
-attributetest : KW_ATTRIBUTE OP (attribnameorwildcard ( COMMA typename_)?)? CP ;
-attribnameorwildcard : attributename | STAR ;
-schemaattributetest : KW_SCHEMA_ATTRIBUTE OP attributedeclaration CP ;
-attributedeclaration : attributename ;
-elementtest : KW_ELEMENT OP (elementnameorwildcard ( COMMA typename_ QM?)?)? CP ;
-// [65]
-elementnameorwildcard : elementname | STAR ;
-schemaelementtest : KW_SCHEMA_ELEMENT OP elementdeclaration CP ;
-elementdeclaration : elementname ;
-attributename : qname ;
-elementname : qname ;
-// [70]
-typename_ : qname ;
-
-
-// Error in the spec. EQName also includes acceptable keywords.
-qname : QName | URIQualifiedName
- | KW_ANCESTOR
- | KW_ANCESTOR_OR_SELF
- | KW_AND
- | KW_ARRAY
- | KW_AS
- | KW_ATTRIBUTE
- | KW_CAST
- | KW_CASTABLE
- | KW_CHILD
- | KW_COMMENT
- | KW_DESCENDANT
- | KW_DESCENDANT_OR_SELF
- | KW_DIV
- | KW_DOCUMENT_NODE
- | KW_ELEMENT
- | KW_ELSE
- | KW_EMPTY_SEQUENCE
- | KW_EQ
- | KW_EVERY
- | KW_EXCEPT
- | KW_FOLLOWING
- | KW_FOLLOWING_SIBLING
- | KW_FOR
- | KW_FUNCTION
- | KW_GE
- | KW_GT
- | KW_IDIV
- | KW_IF
- | KW_IN
- | KW_INSTANCE
- | KW_INTERSECT
- | KW_IS
- | KW_ITEM
- | KW_LE
- | KW_LET
- | KW_LT
- | KW_MAP
- | KW_MOD
- | KW_NAMESPACE
- | KW_NAMESPACE_NODE
- | KW_NE
- | KW_NODE
- | KW_OF
- | KW_OR
- | KW_PARENT
- | KW_PRECEDING
- | KW_PRECEDING_SIBLING
- | KW_PROCESSING_INSTRUCTION
- | KW_RETURN
- | KW_SATISFIES
- | KW_SCHEMA_ATTRIBUTE
- | KW_SCHEMA_ELEMENT
- | KW_SELF
- | KW_SOME
- | KW_TEXT
- | KW_THEN
- | KW_TREAT
- | KW_UNION
- ;
-
-// Not per spec. Specified for testing.
-auxilary : (expr SEMI )+ EOF;
-
-
-AT : '@' ;
-BANG : '!' ;
-CB : ']' ;
-CC : '}' ;
-CEQ : ':=' ;
-COLON : ':' ;
-COLONCOLON : '::' ;
-COMMA : ',' ;
-CP : ')' ;
-CS : ':*' ;
-D : '.' ;
-DD : '..' ;
-DOLLAR : '$' ;
-EG : '=>' ;
-EQ : '=' ;
-GE : '>=' ;
-GG : '>>' ;
-GT : '>' ;
-LE : '<=' ;
-LL : '<<' ;
-LT : '<' ;
-MINUS : '-' ;
-NE : '!=' ;
-OB : '[' ;
-OC : '{' ;
-OP : '(' ;
-P : '|' ;
-PLUS : '+' ;
-POUND : '#' ;
-PP : '||' ;
-QM : '?' ;
-SC : '*:' ;
-SLASH : '/' ;
-SS : '//' ;
-STAR : '*' ;
-
-// KEYWORDS
-
-KW_ANCESTOR : 'ancestor' ;
-KW_ANCESTOR_OR_SELF : 'ancestor-or-self' ;
-KW_AND : 'and' ;
-KW_ARRAY : 'array' ;
-KW_AS : 'as' ;
-KW_ATTRIBUTE : 'attribute' ;
-KW_CAST : 'cast' ;
-KW_CASTABLE : 'castable' ;
-KW_CHILD : 'child' ;
-KW_COMMENT : 'comment' ;
-KW_DESCENDANT : 'descendant' ;
-KW_DESCENDANT_OR_SELF : 'descendant-or-self' ;
-KW_DIV : 'div' ;
-KW_DOCUMENT_NODE : 'document-node' ;
-KW_ELEMENT : 'element' ;
-KW_ELSE : 'else' ;
-KW_EMPTY_SEQUENCE : 'empty-sequence' ;
-KW_EQ : 'eq' ;
-KW_EVERY : 'every' ;
-KW_EXCEPT : 'except' ;
-KW_FOLLOWING : 'following' ;
-KW_FOLLOWING_SIBLING : 'following-sibling' ;
-KW_FOR : 'for' ;
-KW_FUNCTION : 'function' ;
-KW_GE : 'ge' ;
-KW_GT : 'gt' ;
-KW_IDIV : 'idiv' ;
-KW_IF : 'if' ;
-KW_IN : 'in' ;
-KW_INSTANCE : 'instance' ;
-KW_INTERSECT : 'intersect' ;
-KW_IS : 'is' ;
-KW_ITEM : 'item' ;
-KW_LE : 'le' ;
-KW_LET : 'let' ;
-KW_LT : 'lt' ;
-KW_MAP : 'map' ;
-KW_MOD : 'mod' ;
-KW_NAMESPACE : 'namespace' ;
-KW_NAMESPACE_NODE : 'namespace-node' ;
-KW_NE : 'ne' ;
-KW_NODE : 'node' ;
-KW_OF : 'of' ;
-KW_OR : 'or' ;
-KW_PARENT : 'parent' ;
-KW_PRECEDING : 'preceding' ;
-KW_PRECEDING_SIBLING : 'preceding-sibling' ;
-KW_PROCESSING_INSTRUCTION : 'processing-instruction' ;
-KW_RETURN : 'return' ;
-KW_SATISFIES : 'satisfies' ;
-KW_SCHEMA_ATTRIBUTE : 'schema-attribute' ;
-KW_SCHEMA_ELEMENT : 'schema-element' ;
-KW_SELF : 'self' ;
-KW_SOME : 'some' ;
-KW_TEXT : 'text' ;
-KW_THEN : 'then' ;
-KW_TO : 'to' ;
-KW_TREAT : 'treat' ;
-KW_UNION : 'union' ;
-
-// A.2.1. TEMINAL SYMBOLS
-// This isn't a complete list of tokens in the language.
-// Keywords and symbols are terminals.
-
-IntegerLiteral : FragDigits ;
-DecimalLiteral : ('.' FragDigits) | (FragDigits '.' [0-9]*) ;
-DoubleLiteral : (('.' FragDigits) | (FragDigits ('.' [0-9]*)?)) [eE] [+-]? FragDigits ;
-StringLiteral : ('"' (FragEscapeQuot | ~[^"])*? '"') | ('\'' (FragEscapeApos | ~['])*? '\'') ;
-URIQualifiedName : BracedURILiteral NCName ;
-BracedURILiteral : 'Q' '{' [^{}]* '}' ;
-// Error in spec: EscapeQuot and EscapeApos are not terminals!
-fragment FragEscapeQuot : '""' ;
-fragment FragEscapeApos : '\'';
-// Error in spec: Comment isn't really a terminal, but an off-channel object.
-Comment : '(:' (Comment | CommentContents)*? ':)' -> skip ;
-QName : FragQName ;
-NCName : FragmentNCName ;
-// Error in spec: Char is not a terminal!
-fragment Char : FragChar ;
-fragment FragDigits : [0-9]+ ;
-fragment CommentContents : Char ;
-// https://www.w3.org/TR/REC-xml-names/#NT-QName
-fragment FragQName : FragPrefixedName | FragUnprefixedName ;
-fragment FragPrefixedName : FragPrefix ':' FragLocalPart ;
-fragment FragUnprefixedName : FragLocalPart ;
-fragment FragPrefix : FragmentNCName ;
-fragment FragLocalPart : FragmentNCName ;
-fragment FragNCNameStartChar
- : 'A'..'Z'
- | '_'
- | 'a'..'z'
- | '\u00C0'..'\u00D6'
- | '\u00D8'..'\u00F6'
- | '\u00F8'..'\u02FF'
- | '\u0370'..'\u037D'
- | '\u037F'..'\u1FFF'
- | '\u200C'..'\u200D'
- | '\u2070'..'\u218F'
- | '\u2C00'..'\u2FEF'
- | '\u3001'..'\uD7FF'
- | '\uF900'..'\uFDCF'
- | '\uFDF0'..'\uFFFD'
- | '\u{10000}'..'\u{EFFFF}'
- ;
-fragment FragNCNameChar
- : FragNCNameStartChar | '-' | '.' | '0'..'9'
- | '\u00B7' | '\u0300'..'\u036F'
- | '\u203F'..'\u2040'
- ;
-fragment FragmentNCName : FragNCNameStartChar FragNCNameChar* ;
-
-// https://www.w3.org/TR/REC-xml/#NT-Char
-
-fragment FragChar : '\u0009' | '\u000a' | '\u000d'
- | '\u0020'..'\ud7ff'
- | '\ue000'..'\ufffd'
- | '\u{10000}'..'\u{10ffff}'
- ;
-
-// https://github.com/antlr/grammars-v4/blob/17d3db3fd6a8fc319a12176e0bb735b066ec0616/xpath/xpath31/XPath31.g4#L389
-Whitespace : ('\u000d' | '\u000a' | '\u0020' | '\u0009')+ -> skip ;
-
-// Not per spec. Specified for testing.
-SEMI : ';' ;
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java
index 0f797ab7..a581c928 100644
--- a/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java
+++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java
@@ -16,11 +16,13 @@
import eu.europa.ted.eforms.sdk.repository.SdkFieldRepository;
import eu.europa.ted.eforms.sdk.repository.SdkNodeRepository;
import eu.europa.ted.eforms.sdk.resource.SdkResourceLoader;
+import eu.europa.ted.eforms.xpath.XPathInfo;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
import eu.europa.ted.efx.interfaces.SymbolResolver;
+import eu.europa.ted.efx.model.expressions.Expression;
import eu.europa.ted.efx.model.expressions.path.NodePathExpression;
import eu.europa.ted.efx.model.expressions.path.PathExpression;
import eu.europa.ted.efx.model.types.FieldTypes;
-import eu.europa.ted.efx.xpath.XPathAttributeLocator;
import eu.europa.ted.efx.xpath.XPathContextualizer;
@SdkComponent(versions = { "1", "2" }, componentType = SdkComponentType.SYMBOL_RESOLVER)
@@ -181,7 +183,7 @@ public boolean isAttributeField(final String fieldId) {
if (!additionalFieldInfoMap.containsKey(fieldId)) {
this.cacheAdditionalFieldInfo(fieldId);
}
- return additionalFieldInfoMap.get(fieldId).isAttribute;
+ return additionalFieldInfoMap.get(fieldId).isAttribute();
}
@Override
@@ -189,7 +191,7 @@ public String getAttributeNameFromAttributeField(final String fieldId) {
if (!additionalFieldInfoMap.containsKey(fieldId)) {
this.cacheAdditionalFieldInfo(fieldId);
}
- return additionalFieldInfoMap.get(fieldId).attributeName;
+ return additionalFieldInfoMap.get(fieldId).getAttributeName();
}
@Override
@@ -197,38 +199,23 @@ public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(final String fie
if (!additionalFieldInfoMap.containsKey(fieldId)) {
this.cacheAdditionalFieldInfo(fieldId);
}
- return additionalFieldInfoMap.get(fieldId).pathWithoutAttribute;
+ return Expression.instantiate(additionalFieldInfoMap.get(fieldId).getPathToLastElement(), NodePathExpression.class);
}
// #region Temporary helpers ------------------------------------------------
- /**
- * Temporary workaround to store additional info about fields.
- *
- * TODO: Move this additional info to SdkField class, and move the XPathAttributeLocator to the eforms-core-library.
- */
- class AdditionalFieldInfo {
- public boolean isAttribute;
- public String attributeName;
- public PathExpression pathWithoutAttribute;
- }
-
/**
* Caches the results of xpath parsing to mitigate performance impact.
* This is a temporary solution until we move the additional info to the SdkField class.
*/
- Map additionalFieldInfoMap = new HashMap<>();
+ Map additionalFieldInfoMap = new HashMap<>();
private void cacheAdditionalFieldInfo(final String fieldId) {
if (additionalFieldInfoMap.containsKey(fieldId)) {
return;
}
- var parsedPath = XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId));
- var info = new AdditionalFieldInfo();
- info.isAttribute = parsedPath.hasAttribute();
- info.attributeName = parsedPath.getAttributeName();
- info.pathWithoutAttribute = parsedPath.getElementPath();
- additionalFieldInfoMap.put(fieldId, info);
+ XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript());
+ additionalFieldInfoMap.put(fieldId, xpathInfo);
}
// #endregion Temporary helpers ------------------------------------------------
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 460a4eb7..98804869 100644
--- a/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java
+++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1.java
@@ -26,6 +26,7 @@
import eu.europa.ted.efx.model.Context.FieldContext;
import eu.europa.ted.efx.model.Context.NodeContext;
import eu.europa.ted.efx.model.expressions.Expression;
+import eu.europa.ted.efx.model.expressions.TypedExpression;
import eu.europa.ted.efx.model.expressions.path.PathExpression;
import eu.europa.ted.efx.model.expressions.path.StringPathExpression;
import eu.europa.ted.efx.model.expressions.scalar.StringExpression;
@@ -33,6 +34,7 @@
import eu.europa.ted.efx.model.templates.ContentBlock;
import eu.europa.ted.efx.model.templates.ContentBlockStack;
import eu.europa.ted.efx.model.templates.Markup;
+import eu.europa.ted.efx.model.types.EfxDataType;
import eu.europa.ted.efx.model.variables.Variable;
import eu.europa.ted.efx.model.variables.VariableList;
import eu.europa.ted.efx.sdk1.EfxParser.AssetIdContext;
@@ -244,8 +246,36 @@ public void exitExpressionTemplate(ExpressionTemplateContext ctx) {
@Override
public void exitStandardLabelReference(StandardLabelReferenceContext ctx) {
- StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class)
- : this.script.getStringLiteralFromUnquotedString("");
+ if (!this.stack.empty() && StringSequenceExpression.class.isAssignableFrom(this.stack.peek().getClass()) && ctx.assetId() != null) {
+
+ // This is a workaround that allows EFX 1 to render a sequence of labels without a special
+ // syntax. When a standard label reference is processed, the template translator checks if
+ // the assetId is provided with a SequenceExpression. If this is the case, then the
+ // translator generates the appropriate code to render a sequence of labels.
+ //
+ // For example, this will render a sequence of labels for a label reference of the form
+ // #{assetType|labelType|${for text:$t in ('assetId1','assetId2') return $t}}:}
+ // The only restriction is that the assetType and labelType must be the same for all labels in the sequence.
+
+ StringSequenceExpression assetIdSequence = this.stack.pop(StringSequenceExpression.class);
+ this.exitStandardLabelReference(ctx, assetIdSequence);
+ } else {
+
+ // Standard implementation as originally intended by EFX 1
+
+ StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class)
+ : this.script.getStringLiteralFromUnquotedString("");
+ this.exitStandardLabelReference(ctx, assetId);
+ }
+ }
+
+ /**
+ * Renders a single label from a standard label reference.
+ *
+ * @param ctx The ParserRuleContext of the standard label reference.
+ * @param assetId The assetId of the label to render.
+ */
+ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, StringExpression assetId) {
StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class)
: this.script.getStringLiteralFromUnquotedString("");
StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class)
@@ -256,6 +286,39 @@ public void exitStandardLabelReference(StandardLabelReferenceContext ctx) {
this.script.getStringLiteralFromUnquotedString("|"), assetId))));
}
+ /**
+ * Renders a sequence of labels from a standard label reference.
+ *
+ * @param ctx The ParserRuleContext of the standard label reference.
+ * @param assetIdSequence The sequence of assetIds for the labels to render.
+ */
+ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, StringSequenceExpression assetIdSequence) {
+ StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class)
+ : this.script.getStringLiteralFromUnquotedString("");
+ StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class)
+ : this.script.getStringLiteralFromUnquotedString("");
+
+ Variable loopVariable = new Variable("item",
+ this.script.composeVariableDeclaration("item", StringExpression.class), StringExpression.empty(),
+ this.script.composeVariableReference("item", StringExpression.class));
+
+ this.stack.push(this.markup.renderLabelFromExpression(
+ this.script.composeDistinctValuesFunction(
+ this.script.composeForExpression(
+ this.script.composeIteratorList(
+ List.of(
+ this.script.composeIteratorExpression(loopVariable.declarationExpression, assetIdSequence))),
+ this.script.composeStringConcatenation(List.of(
+ assetType,
+ this.script.getStringLiteralFromUnquotedString("|"),
+ labelType,
+ this.script.getStringLiteralFromUnquotedString("|"),
+ new StringExpression(loopVariable.referenceExpression.getScript()))),
+ StringSequenceExpression.class),
+ StringSequenceExpression.class)));
+ }
+
+
@Override
public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) {
StringExpression assetId = this.script.getStringLiteralFromUnquotedString(ctx.BtId().getText());
@@ -303,36 +366,42 @@ private void shorthandIndirectLabelReference(final String fieldId) {
this.script.composeVariableReference("item", StringExpression.class));
switch (fieldType) {
case "indicator":
- this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression(
- this.script.composeIteratorList(
- List.of(
- this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
- this.script.composeStringConcatenation(
- List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN),
- this.script.getStringLiteralFromUnquotedString("-"),
- new StringExpression(loopVariable.referenceExpression.getScript()),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(fieldId))),
- StringSequenceExpression.class)));
+ this.stack.push(this.markup.renderLabelFromExpression(
+ this.script.composeDistinctValuesFunction(
+ this.script.composeForExpression(
+ this.script.composeIteratorList(
+ List.of(
+ this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
+ this.script.composeStringConcatenation(
+ List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN),
+ this.script.getStringLiteralFromUnquotedString("-"),
+ new StringExpression(loopVariable.referenceExpression.getScript()),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(fieldId))),
+ StringSequenceExpression.class),
+ StringSequenceExpression.class)));
break;
case "code":
case "internal-code":
- this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression(
- this.script.composeIteratorList(
- List.of(
- this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
- this.script.composeStringConcatenation(List.of(
- this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_NAME),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(
- this.symbols.getRootCodelistOfField(fieldId)),
- this.script.getStringLiteralFromUnquotedString("."),
- new StringExpression(loopVariable.referenceExpression.getScript()))),
- StringSequenceExpression.class)));
+ this.stack.push(this.markup.renderLabelFromExpression(
+ this.script.composeDistinctValuesFunction(
+ this.script.composeForExpression(
+ this.script.composeIteratorList(
+ List.of(
+ this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
+ this.script.composeStringConcatenation(List.of(
+ this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_NAME),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(
+ this.symbols.getRootCodelistOfField(fieldId)),
+ this.script.getStringLiteralFromUnquotedString("."),
+ new StringExpression(loopVariable.referenceExpression.getScript()))),
+ StringSequenceExpression.class),
+ StringSequenceExpression.class)));
break;
default:
throw new ParseCancellationException(String.format(
@@ -420,7 +489,20 @@ public void exitAssetId(AssetIdContext ctx) {
*/
@Override
public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) {
- this.stack.push(this.stack.pop(Expression.class));
+ var expression = this.stack.pop(Expression.class);
+
+ // 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.
+ if (TypedExpression.class.isAssignableFrom(expression.getClass())) {
+ if (EfxDataType.Date.class.isAssignableFrom(((TypedExpression) expression).getDataType())) {
+ expression = new StringExpression("format-date(" + expression.getScript() + ", '[D01]/[M01]/[Y0001]')");
+ } else if (EfxDataType.Time.class.isAssignableFrom(((TypedExpression) expression).getDataType())) {
+ expression = new StringExpression("format-time(" + expression.getScript() + ", '[H01]:[m01] [Z]')");
+ }
+ }
+
+ this.stack.push(expression);
}
/***
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 bbf16e9d..8dce460c 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
@@ -2,10 +2,11 @@
import eu.europa.ted.eforms.sdk.component.SdkComponent;
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
+import eu.europa.ted.eforms.xpath.XPathInfo;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
import eu.europa.ted.efx.interfaces.TranslatorOptions;
import eu.europa.ted.efx.model.expressions.path.PathExpression;
import eu.europa.ted.efx.model.types.EfxDataType;
-import eu.europa.ted.efx.xpath.XPathContextualizer;
import eu.europa.ted.efx.xpath.XPathScriptGenerator;
@SdkComponent(versions = {"1"}, componentType = SdkComponentType.SCRIPT_GENERATOR)
@@ -40,7 +41,8 @@ public XPathScriptGeneratorV1(TranslatorOptions translatorOptions) {
*/
@Override
public PathExpression composeFieldValueReference(PathExpression fieldReference) {
- if (fieldReference.is(EfxDataType.MultilingualString.class) && !XPathContextualizer.hasPredicate(fieldReference, "@languageID")) {
+ XPathInfo xpathInfo = XPathProcessor.parse(fieldReference.getScript());
+ if (fieldReference.is(EfxDataType.MultilingualString.class) && !xpathInfo.hasPredicate("@languageID")) {
return PathExpression.instantiate("efx:preferred-language-text(" + fieldReference.getScript() + ")", fieldReference.getDataType());
}
return super.composeFieldValueReference(fieldReference);
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 ed39da2d..ccd9e19f 100644
--- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java
+++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java
@@ -42,6 +42,7 @@
import eu.europa.ted.efx.model.templates.ContentBlock;
import eu.europa.ted.efx.model.templates.ContentBlockStack;
import eu.europa.ted.efx.model.templates.Markup;
+import eu.europa.ted.efx.model.types.EfxDataType;
import eu.europa.ted.efx.model.types.FieldTypes;
import eu.europa.ted.efx.model.variables.Variable;
import eu.europa.ted.efx.model.variables.VariableList;
@@ -279,12 +280,42 @@ public void exitSecondaryTemplate(SecondaryTemplateContext ctx) {
@Override
public void exitStandardLabelReference(StandardLabelReferenceContext ctx) {
- // New in EFX-2: Pluralisation of labels based on a supplied quantity
- NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class)
- : NumericExpression.empty();
+ if (!this.stack.empty() && StringSequenceExpression.class.isAssignableFrom(this.stack.peek().getClass()) && ctx.assetId() != null) {
- StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class)
- : this.script.getStringLiteralFromUnquotedString("");
+ // TODO: Review this in EFX-2
+
+ // This is a workaround that allows EFX 1 to render a sequence of labels without a special
+ // syntax. When a standard label reference is processed, the template translator checks if
+ // the assetId is provided with a SequenceExpression. If this is the case, then the
+ // translator generates the appropriate code to render a sequence of labels.
+ //
+ // For example, this will render a sequence of labels for a label reference of the form
+ // #{assetType|labelType|${for text:$t in ('assetId1','assetId2') return $t}}:}
+ // The only restriction is that the assetType and labelType must be the same for all labels in the sequence.
+
+ StringSequenceExpression assetIdSequence = this.stack.pop(StringSequenceExpression.class);
+ this.exitStandardLabelReference(ctx, assetIdSequence); // TODO: pluralisation is not addressed here yet
+ } else {
+
+ // Standard implementation as originally intended by EFX 1
+
+ // New in EFX-2: Pluralisation of labels based on a supplied quantity
+ NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class)
+ : NumericExpression.empty();
+
+ StringExpression assetId = ctx.assetId() != null ? this.stack.pop(StringExpression.class)
+ : this.script.getStringLiteralFromUnquotedString("");
+ this.exitStandardLabelReference(ctx, assetId, quantity);
+ }
+ }
+
+ /**
+ * Renders a single label from a standard label reference.
+ *
+ * @param ctx The ParserRuleContext of the standard label reference.
+ * @param assetId The assetId of the label to render.
+ */
+ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, StringExpression assetId, NumericExpression quantity) {
StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class)
: this.script.getStringLiteralFromUnquotedString("");
StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class)
@@ -295,6 +326,39 @@ public void exitStandardLabelReference(StandardLabelReferenceContext ctx) {
this.script.getStringLiteralFromUnquotedString("|"), assetId)), quantity));
}
+ /**
+ * Renders a sequence of labels from a standard label reference.
+ *
+ * @param ctx The ParserRuleContext of the standard label reference.
+ * @param assetIdSequence The sequence of assetIds for the labels to render.
+ */
+ private void exitStandardLabelReference(StandardLabelReferenceContext ctx, StringSequenceExpression assetIdSequence) {
+ StringExpression labelType = ctx.labelType() != null ? this.stack.pop(StringExpression.class)
+ : this.script.getStringLiteralFromUnquotedString("");
+ StringExpression assetType = ctx.assetType() != null ? this.stack.pop(StringExpression.class)
+ : this.script.getStringLiteralFromUnquotedString("");
+
+ Variable loopVariable = new Variable("item",
+ this.script.composeVariableDeclaration("item", StringExpression.class), StringExpression.empty(),
+ this.script.composeVariableReference("item", StringExpression.class));
+
+ this.stack.push(this.markup.renderLabelFromExpression(
+ this.script.composeDistinctValuesFunction(
+ this.script.composeForExpression(
+ this.script.composeIteratorList(
+ List.of(
+ this.script.composeIteratorExpression(loopVariable.declarationExpression, assetIdSequence))),
+ this.script.composeStringConcatenation(List.of(
+ assetType,
+ this.script.getStringLiteralFromUnquotedString("|"),
+ labelType,
+ this.script.getStringLiteralFromUnquotedString("|"),
+ new StringExpression(loopVariable.referenceExpression.getScript()))),
+ StringSequenceExpression.class),
+ StringSequenceExpression.class)));
+ }
+
+
@Override
public void exitShorthandBtLabelReference(ShorthandBtLabelReferenceContext ctx) {
NumericExpression quantity = ctx.pluraliser() != null ? this.stack.pop(NumericExpression.class) : NumericExpression.empty();
@@ -347,36 +411,43 @@ private void shorthandIndirectLabelReference(final String fieldId, final Numeric
this.script.composeVariableReference("item", StringExpression.class));
switch (fieldType) {
case "indicator":
- this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression(
- this.script.composeIteratorList(
- List.of(
- this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
- this.script.composeStringConcatenation(
- List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN),
- this.script.getStringLiteralFromUnquotedString("-"),
- new StringExpression(loopVariable.referenceExpression.getScript()),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(fieldId))),
- StringSequenceExpression.class), quantity));
+ this.stack.push(this.markup.renderLabelFromExpression(
+ this.script.composeDistinctValuesFunction(
+ this.script.composeForExpression(
+ this.script.composeIteratorList(
+ List.of(
+ this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
+ this.script.composeStringConcatenation(
+ List.of(this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_INDICATOR),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_WHEN),
+ this.script.getStringLiteralFromUnquotedString("-"),
+ new StringExpression(loopVariable.referenceExpression.getScript()),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(fieldId))),
+ StringSequenceExpression.class),
+ StringSequenceExpression.class), quantity));
break;
case "code":
case "internal-code":
- this.stack.push(this.markup.renderLabelFromExpression(this.script.composeForExpression(
- this.script.composeIteratorList(
- List.of(
- this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
- this.script.composeStringConcatenation(List.of(
- this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_NAME),
- this.script.getStringLiteralFromUnquotedString("|"),
- this.script.getStringLiteralFromUnquotedString(
- this.symbols.getRootCodelistOfField(fieldId)),
- this.script.getStringLiteralFromUnquotedString("."),
- new StringExpression(loopVariable.referenceExpression.getScript()))),
- StringSequenceExpression.class), quantity));
+ this.stack.push(this.markup.renderLabelFromExpression(
+ this.script.composeDistinctValuesFunction(
+ this.script.composeForExpression(
+ this.script.composeIteratorList(
+ List.of(
+ this.script.composeIteratorExpression(loopVariable.declarationExpression, valueReference))),
+ this.script.composeStringConcatenation(List.of(
+ this.script.getStringLiteralFromUnquotedString(ASSET_TYPE_CODE),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(LABEL_TYPE_NAME),
+ this.script.getStringLiteralFromUnquotedString("|"),
+ this.script.getStringLiteralFromUnquotedString(
+ this.symbols.getRootCodelistOfField(fieldId)),
+ this.script.getStringLiteralFromUnquotedString("."),
+ new StringExpression(loopVariable.referenceExpression.getScript()))),
+ StringSequenceExpression.class),
+ StringSequenceExpression.class),
+ quantity));
break;
default:
throw new ParseCancellationException(String.format(
@@ -479,7 +550,22 @@ public void exitComputedLabelReference(ComputedLabelReferenceContext ctx) {
*/
@Override
public void exitStandardExpressionBlock(StandardExpressionBlockContext ctx) {
- this.stack.push(this.stack.pop(Expression.class));
+ 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.
+ if (TypedExpression.class.isAssignableFrom(expression.getClass())) {
+ if (EfxDataType.Date.class.isAssignableFrom(((TypedExpression) expression).getDataType())) {
+ expression = new StringExpression("format-date(" + expression.getScript() + ", '[D01]/[M01]/[Y0001]')");
+ } else if (EfxDataType.Time.class.isAssignableFrom(((TypedExpression) expression).getDataType())) {
+ expression = new StringExpression("format-time(" + expression.getScript() + ", '[H01]:[m01] [Z]')");
+ }
+ }
+
+ this.stack.push(expression);
}
/***
diff --git a/src/main/java/eu/europa/ted/efx/xpath/README.md b/src/main/java/eu/europa/ted/efx/xpath/README.md
index e35001ab..324bd0c9 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/README.md
+++ b/src/main/java/eu/europa/ted/efx/xpath/README.md
@@ -1,8 +1,8 @@
# Translating EFX to XPath
+
This package contains classes used for translating EFX expressions to XPath.
* `XPathScriptGenerator`: Implements the `ScriptGenerator` interface for EFX to XPath translation.
-* `XPathContextualized`: Used to convert a given absolute XPath to an XPath relative to another absolute XPath.
-* `XPathAttributeLocator`: Used to parse an XPath expression and extract any XML attributes it may contain.
+* `XPathContextualizer`: Used to convert a given absolute XPath expression to an XPath relative to another absolute XPath.
-_Note: There is one more class that is specific to EFX-to-XPath translation which is not contained in this package: the [`SdkSymbolResolver`](../../eforms/sdk/SdkSymbolResolver.java) class. It is XPath specific because it returns XPaths taken from the eForms SDK._
\ No newline at end of file
+_Note: There is one more class that is specific to EFX-to-XPath translation which is not contained in this package: the [`SdkSymbolResolver`](../../eforms/sdk/SdkSymbolResolver.java) class. It is XPath specific because it returns XPaths taken from the eForms SDK._
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java
deleted file mode 100644
index 34d24ef4..00000000
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathAttributeLocator.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package eu.europa.ted.efx.xpath;
-
-import org.antlr.v4.runtime.CharStream;
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.tree.ParseTree;
-import org.antlr.v4.runtime.tree.ParseTreeWalker;
-import org.apache.commons.lang3.StringUtils;
-
-import eu.europa.ted.efx.model.expressions.Expression;
-import eu.europa.ted.efx.model.expressions.path.NodePathExpression;
-import eu.europa.ted.efx.model.expressions.path.PathExpression;
-import eu.europa.ted.efx.xpath.XPath20Parser.AbbrevforwardstepContext;
-import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext;
-
-/**
- * Uses the {@link XPath20Parser} to extract an attribute from an XPath expression. Arguably one
- * could try to do the same thing using regular expressions, however, with the parser we can handle
- * correctly any XPath expression regardless of how complicated it maybe.
- *
- * The reason we need to examine if an XPath expression points to an attribute is because some
- * eForms Fields represent attribute values. This means that these attributes are effectively hidden
- * behind a Field identifier and cannot be visible by the lexical analyzer or the parser. They are
- * only visible to the translator after dereferencing such Field identifiers. At this point, the
- * translator relies on this class to detect the presence of such attributes and translate
- * accordingly.
- */
-public class XPathAttributeLocator extends XPath20BaseListener {
-
- private int inPredicate = 0;
- private int splitPosition = -1;
- private String path;
- private String attribute;
-
- /**
- * Gets the XPath to the XML element that contains the attribute.
- * The returned XPath therefore does not contain the attribute itself.
- *
- * @return A {@link NodePathExpression} pointing to the XML element that contains the attribute.
- */
- public NodePathExpression getElementPath() {
- return Expression.instantiate(path, NodePathExpression.class);
- }
-
- /**
- * Gets the name of the attribute (without the @ prefix).
- * If the parsed XPath did not point to an attribute, then this method returns null.
- *
- * @return The name of the attribute (or null if the parsed XPath did not point to an attribute).
- */
- public String getAttributeName() {
- return StringUtils.isBlank(attribute) ? null : attribute;
- }
-
- public Boolean hasAttribute() {
- return attribute != null;
- }
-
- @Override
- public void enterPredicate(PredicateContext ctx) {
- this.inPredicate++;
- }
-
- @Override
- public void exitPredicate(PredicateContext ctx) {
- this.inPredicate--;
- }
-
- @Override
- public void exitAbbrevforwardstep(AbbrevforwardstepContext ctx) {
- if (this.inPredicate == 0 && ctx.AT() != null) {
- this.splitPosition = ctx.AT().getSymbol().getCharPositionInLine();
- this.attribute = ctx.nodetest().getText();
- }
- }
-
- public static XPathAttributeLocator findAttribute(final PathExpression xpath) {
- return findAttribute(xpath.getScript());
- }
-
- public static XPathAttributeLocator findAttribute(final String xpath) {
-
- final XPathAttributeLocator locator = new XPathAttributeLocator();
-
- if (!xpath.contains("@")) {
- locator.path = xpath;
- locator.attribute = null;
- return locator;
- }
-
- final CharStream inputStream = CharStreams.fromString(xpath);
- final XPath20Lexer lexer = new XPath20Lexer(inputStream);
- final CommonTokenStream tokens = new CommonTokenStream(lexer);
- final XPath20Parser parser = new XPath20Parser(tokens);
- final ParseTree tree = parser.xpath();
- final ParseTreeWalker walker = new ParseTreeWalker();
-
- walker.walk(locator, tree);
-
- if (locator.splitPosition > -1) {
- // The attribute we are looking for is at splitPosition
- String path = xpath.substring(0, locator.splitPosition);
- while (path.endsWith("/")) {
- path = path.substring(0, path.length() - 1);
- }
- locator.path = path;
- } else {
- // the XPAth does not point to an attribute
- locator.path = xpath;
- }
-
- return locator;
- }
-}
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
index 499285ad..9f95c1c8 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathContextualizer.java
@@ -1,64 +1,9 @@
package eu.europa.ted.efx.xpath;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Queue;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-import org.antlr.v4.runtime.CharStream;
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.ParserRuleContext;
-import org.antlr.v4.runtime.misc.Interval;
-import org.antlr.v4.runtime.tree.ParseTree;
-import org.antlr.v4.runtime.tree.ParseTreeWalker;
-
+import eu.europa.ted.eforms.xpath.XPathProcessor;
import eu.europa.ted.efx.model.expressions.path.PathExpression;
-import eu.europa.ted.efx.xpath.XPath20Parser.AxisstepContext;
-import eu.europa.ted.efx.xpath.XPath20Parser.FilterexprContext;
-import eu.europa.ted.efx.xpath.XPath20Parser.PredicateContext;
-
-public class XPathContextualizer extends XPath20BaseListener {
-
- private final CharStream inputStream;
- private final LinkedList steps = new LinkedList<>();
-
- public XPathContextualizer(CharStream inputStream) {
- this.inputStream = inputStream;
- }
-
- /**
- * Parses the XPath represented by th e given {@link PathExpression}} and
- * returns a queue containing a {@link StepInfo} object for each step that the
- * XPath is comprised of.
- */
- private static Queue getSteps(PathExpression xpath) {
- return getSteps(xpath.getScript());
- }
-
- /**
- * Parses the given xpath and returns a queue containing a {@link StepInfo} for
- * each step that the XPath is comprised of.
- */
- private static Queue getSteps(String xpath) {
-
- final CharStream inputStream = CharStreams.fromString(xpath);
- final XPath20Lexer lexer = new XPath20Lexer(inputStream);
- final CommonTokenStream tokens = new CommonTokenStream(lexer);
- final XPath20Parser parser = new XPath20Parser(tokens);
- final ParseTree tree = parser.xpath();
- final ParseTreeWalker walker = new ParseTreeWalker();
- final XPathContextualizer contextualizer = new XPathContextualizer(inputStream);
- walker.walk(contextualizer, tree);
-
- return contextualizer.steps;
- }
+public class XPathContextualizer {
/**
* Makes the given xpath relative to the given context xpath.
@@ -74,362 +19,16 @@ public static PathExpression contextualize(final PathExpression contextXpath,
if (contextXpath == null || contextXpath.getScript().isEmpty()) {
return xpath;
}
- return PathExpression.instantiate(contextualize(contextXpath.getScript(), xpath.getScript()), xpath.getDataType());
- }
-
- public static String contextualize(final String contextXpath,
- final String xpath) {
-
- // If we are asked to contextualise against a null or empty context
- // then we must return the original xpath (instead of throwing an exception).
- if (contextXpath == null || contextXpath.isEmpty()) {
- return xpath;
- }
-
- Queue contextSteps = new LinkedList(getSteps(contextXpath));
- Queue pathSteps = new LinkedList(getSteps(xpath));
-
- return getContextualizedXpath(contextSteps, pathSteps);
- }
-
- public static boolean hasPredicate(final PathExpression xpath, String match) {
- return hasPredicate(xpath.getScript(), match);
- }
-
- public static boolean hasPredicate(final String xpath, String match) {
- return getSteps(xpath).stream().anyMatch(s -> s.getPredicateText().contains(match));
- }
-
- public static PathExpression addPredicate(final PathExpression pathExpression, final String predicate) {
- return PathExpression.instantiate(addPredicate(pathExpression.getScript(), predicate), pathExpression.getDataType());
- }
-
- /**
- * Attempts to add a predicate to the given xpath.
- * It will add the predicate to the last axis-step in the xpath.
- * If there is no axis-step in the xpath then it will add the predicate to the last step.
- * If the xpath is empty then it will still return a PathExpression but with an empty xpath.
- *
- * @param xpath the xpath to add the predicate to
- * @param predicate the predicate to add
- * @return the xpath with the predicate added
- */
- public static String addPredicate(final String xpath, final String predicate) {
- if (predicate == null) {
- return xpath;
- }
-
- String _predicate = predicate.trim();
-
- if (_predicate.isEmpty()) {
- return xpath;
- }
- if (!_predicate.startsWith("[")) {
- _predicate = "[" + _predicate;
- }
-
- if (!_predicate.endsWith("]")) {
- _predicate = _predicate + "]";
- }
-
- LinkedList steps = new LinkedList<>(getSteps(xpath));
+ String result = XPathProcessor.contextualize(contextXpath.getScript(), xpath.getScript());
- StepInfo lastAxisStep = getLastAxisStep(steps);
- if (lastAxisStep != null) {
- lastAxisStep.predicates.add(_predicate);
- } else if (steps.size() > 0) {
- steps.getLast().predicates.add(_predicate);
- }
- return steps.stream().map(s -> s.stepText + s.getPredicateText()).collect(Collectors.joining("/"));
- }
-
- private static StepInfo getLastAxisStep(LinkedList steps) {
- int i = steps.size() - 1;
- while (i >= 0 && !AxisStepInfo.class.isInstance(steps.get(i))) {
- i--;
- }
- if (i < 0) {
- return null;
- }
- return steps.get(i);
+ return PathExpression.instantiate(result, xpath.getDataType());
}
public static PathExpression join(final PathExpression first, final PathExpression second) {
- if (first == null || first.getScript().trim().isEmpty()) {
- return second;
- }
-
- if (second == null || second.getScript().trim().isEmpty()) {
- return first;
- }
-
- LinkedList firstPartSteps = new LinkedList<>(getSteps(first));
- LinkedList secondPartSteps = new LinkedList<>(getSteps(second));
-
- return PathExpression.instantiate(getJoinedXPath(firstPartSteps, secondPartSteps), second.getDataType());
- }
-
- public static PathExpression addAxis(String axis, PathExpression path) {
- LinkedList steps = new LinkedList<>(getSteps(path));
-
- while (steps.getFirst().stepText.equals("..")) {
- steps.removeFirst();
- }
-
- return PathExpression.instantiate(
- axis + "::" + steps.stream().map(s -> s.stepText).collect(Collectors.joining("/")), path.getDataType());
- }
-
- private static String getContextualizedXpath(Queue contextQueue,
- final Queue pathQueue) {
-
- // We will store the relative xPath here as we build it.
- String relativeXpath = "";
-
- if (contextQueue != null) {
-
- // First we will "consume" all nodes that are the same in both xPaths.
- while (!contextQueue.isEmpty() && !pathQueue.isEmpty()
- && pathQueue.peek().isTheSameAs(contextQueue.peek())) {
- contextQueue.poll();
- pathQueue.poll();
- }
+ String joinedXPath = XPathProcessor.join(first.getScript(), second.getScript());
- // At this point there are no more matching nodes in the two queues.
-
- // We look at the first of the remaining steps in both queues and look if
- // they are "similar" (meaning that they share the same step-text but but
- // the path has different predicates). In this case we want to use a dot step
- // with the predicate.
- if (!contextQueue.isEmpty() && !pathQueue.isEmpty() && pathQueue.peek().isSimilarTo(contextQueue.peek())) {
- contextQueue.poll(); // consume the same step from the contextQueue
- if (contextQueue.isEmpty()) {
- // Since there are no more steps in the contextQueue, the relative xpath should
- // start with a dot step to provide a context for the predicate.
- relativeXpath += "." + pathQueue.poll().getPredicateText();
- } else {
- // Since there are more steps in the contextQueue which we will need to navigate back to,
- // using back-steps, we will use a back-step to provide context of the predicate.
- // This avoids an output that looks like ../.[predicate] which is valid but silly.
- contextQueue.poll(); // consume the step from the contextQueue
- relativeXpath += ".." + pathQueue.poll().getPredicateText();
- }
- }
-
- // We start building the resulting relativeXpath by appending any nodes
- // remaining in the pathQueue.
- while (!pathQueue.isEmpty()) {
- final StepInfo step = pathQueue.poll();
- relativeXpath += "/" + step.stepText + step.getPredicateText();
- }
-
- // We remove any leading forward slashes from the resulting xPath.
- while (relativeXpath.startsWith("/")) {
- relativeXpath = relativeXpath.substring(1);
- }
-
- // For each step remaining in the contextQueue we prepend a back-step (..) in
- // the resulting relativeXpath.
- while (!contextQueue.isEmpty()) {
- contextQueue.poll(); // consume the step
- relativeXpath = "../" + relativeXpath; // prepend a back-step
- }
-
- // We remove any trailing forward slashes from the resulting xPath.
- while (relativeXpath.endsWith("/")) {
- relativeXpath = relativeXpath.substring(0, relativeXpath.length() - 1);
- }
-
-
- // The relativeXpath will be empty if the path was identical to the context.
- // In this case we return a dot.
- if (relativeXpath.isEmpty()) {
- relativeXpath = ".";
- }
- }
-
- return relativeXpath;
- }
-
-
- private static String getJoinedXPath(LinkedList first,
- final LinkedList second) {
- List dotSteps = Arrays.asList("..", ".");
- while (second.getFirst().stepText.equals("..")
- && !dotSteps.contains(first.getLast().stepText) && !first.getLast().isVariableStep()) {
- second.removeFirst();
- first.removeLast();
- }
-
- return first.stream().map(f -> f.stepText).collect(Collectors.joining("/"))
- + "/" + second.stream().map(s -> s.stepText).collect(Collectors.joining("/"));
- }
-
- /**
- * Helper method that returns the input text that matched a parser rule context. It is useful
- * because {@link ParserRuleContext#getText()} omits whitespace and other lexer tokens in the
- * HIDDEN channel.
- *
- * @param context
- * @return
- */
- private String getInputText(ParserRuleContext context) {
- return this.inputStream
- .getText(new Interval(context.start.getStartIndex(), context.stop.getStopIndex()));
- }
-
- int predicateMode = 0;
-
- private Boolean inPredicateMode() {
- return predicateMode > 0;
- }
-
- @Override
- public void exitAxisstep(AxisstepContext ctx) {
- if (inPredicateMode()) {
- return;
- }
-
- // When we recognize a step, we add it to the queue if is is empty.
- // If the queue is not empty, and the depth of the new step is not smaller than
- // the depth of the last step in the queue, then this step needs to be added to
- // the queue too.
- // Otherwise, the last step in the queue is a sub-expression of the new step,
- // and we need to
- // replace it in the queue with the new step.
- if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) {
- this.steps.offer(new AxisStepInfo(ctx, this::getInputText));
- } else {
- Interval removedInterval = ctx.getSourceInterval();
- while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) {
- this.steps.removeLast();
- }
- this.steps.offer(new AxisStepInfo(ctx, this::getInputText));
- }
- }
-
- @Override
- public void exitFilterexpr(FilterexprContext ctx) {
- if (inPredicateMode()) {
- return;
- }
-
- // Same logic as for axis steps here (sse exitAxisstep).
- if (this.steps.isEmpty() || !this.steps.getLast().isPartOf(ctx.getSourceInterval())) {
- this.steps.offer(new FilterStepInfo(ctx, this::getInputText));
- } else {
- Interval removedInterval = ctx.getSourceInterval();
- while(!this.steps.isEmpty() && this.steps.getLast().isPartOf(removedInterval)) {
- this.steps.removeLast();
- }
- this.steps.offer(new FilterStepInfo(ctx, this::getInputText));
- }
- }
-
- @Override
- public void enterPredicate(PredicateContext ctx) {
- this.predicateMode++;
- }
-
- @Override
- public void exitPredicate(PredicateContext ctx) {
- this.predicateMode--;
- }
-
- public class AxisStepInfo extends StepInfo {
-
- public AxisStepInfo(AxisstepContext ctx, Function getInputText) {
- super(ctx.reversestep() != null? getInputText.apply(ctx.reversestep()) : getInputText.apply(ctx.forwardstep()),
- ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval());
- }
- }
-
- public class FilterStepInfo extends StepInfo {
-
- public FilterStepInfo(FilterexprContext ctx, Function getInputText) {
- super(getInputText.apply(ctx.primaryexpr()),
- ctx.predicatelist().predicate().stream().map(getInputText).collect(Collectors.toList()), ctx.getSourceInterval());
- }
- }
-
- public class StepInfo {
- String stepText;
- List predicates;
- int a;
- int b;
-
- protected StepInfo(String stepText, List predicates, Interval interval) {
- this.stepText = stepText;
- this.predicates = predicates;
- this.a = interval.a;
- this.b = interval.b;
- }
-
- public Boolean isVariableStep() {
- return this.stepText.startsWith("$");
- }
-
- public String getPredicateText() {
- return String.join("", this.predicates);
- }
-
- public Boolean isTheSameAs(final StepInfo contextStep) {
-
- // First check the step texts are the different.
- if (!Objects.equals(contextStep.stepText, this.stepText)) {
- return false;
- }
-
- // If one of the two steps has more predicates that the other,
- if (this.predicates.size() != contextStep.predicates.size()) {
- // then the steps are the same if the path has no predicates
- // or all the predicates of the path are also found in the context.
- return this.predicates.isEmpty() || contextStep.predicates.containsAll(this.predicates);
- }
-
- // If there are no predicates then the steps are the same.
- if (this.predicates.isEmpty()) {
- return true;
- }
-
- // If there is only one predicate in each step, then we can do a quick comparison.
- if (this.predicates.size() == 1) {
- return Objects.equals(contextStep.predicates.get(0), this.predicates.get(0));
- }
-
- // Both steps contain multiple predicates.
- // We need to compare them one by one.
- // First we make a copy so that we can sort them without affecting the original lists.
- List pathPredicates = new ArrayList<>(this.predicates);
- List contextPredicates = new ArrayList<>(contextStep.predicates);
- Collections.sort(pathPredicates);
- Collections.sort(contextPredicates);
- return pathPredicates.equals(contextPredicates);
- }
-
- public Boolean isSimilarTo(final StepInfo contextStep) {
-
- // First check the step texts are the different.
- if (!Objects.equals(contextStep.stepText, this.stepText)) {
- return false;
- }
-
- // If one of the two steps has more predicates that the other,
- if (this.predicates.size() != contextStep.predicates.size()) {
- // then the steps are the same if either of them has no predicates
- // or all the predicates of the path are also found in the context.
- return this.predicates.isEmpty() || contextStep.predicates.isEmpty()
- || contextStep.predicates.containsAll(this.predicates);
- }
-
- assert !this.isTheSameAs(contextStep) : "You should not be calling isSimilarTo() without first checking isTheSameAs()";
- return false;
- }
-
- public Boolean isPartOf(Interval interval) {
- return this.a >= interval.a && this.b <= interval.b;
- }
+ return PathExpression.instantiate(joinedXPath, second.getDataType());
}
}
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 f04c4260..3c33cf26 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -14,6 +14,7 @@
import eu.europa.ted.eforms.sdk.component.SdkComponent;
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
import eu.europa.ted.efx.interfaces.ScriptGenerator;
import eu.europa.ted.efx.interfaces.TranslatorOptions;
import eu.europa.ted.efx.model.expressions.Expression;
@@ -74,7 +75,8 @@ public PathExpression composeFieldReferenceWithPredicate(PathExpression fieldRef
@Override
public PathExpression composeFieldReferenceWithAxis(final PathExpression fieldReference,
final String axis) {
- return PathExpression.instantiate(XPathContextualizer.addAxis(axis, fieldReference).getScript(), fieldReference.getDataType());
+ String resultXPath = XPathProcessor.addAxis(axis, fieldReference.getScript());
+ return PathExpression.instantiate(resultXPath, fieldReference.getDataType());
}
@Override
diff --git a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java b/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java
deleted file mode 100644
index 89316907..00000000
--- a/src/test/java/eu/europa/ted/efx/XPathAttributeLocatorTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package eu.europa.ted.efx;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-
-import org.junit.jupiter.api.Test;
-
-import eu.europa.ted.efx.xpath.XPathAttributeLocator;
-
-class XPathAttributeLocatorTest {
- private void testAttribute(final String attributePath, final String expectedPath,
- final String expectedAttribute) {
- final XPathAttributeLocator locator =
- XPathAttributeLocator.findAttribute(attributePath);
-
- assertEquals(expectedPath, locator.getElementPath().getScript());
- assertEquals(expectedAttribute, locator.getAttributeName());
- }
-
- @Test
- void testXPathAttributeLocator_WithAttribute() {
- testAttribute("/path/path/@attribute", "/path/path", "attribute");
- }
-
- @Test
- void testXPathAttributeLocator_WithMultipleAttributes() {
- testAttribute("/path/path[@otherAttribute = 'text']/@attribute",
- "/path/path[@otherAttribute = 'text']", "attribute");
- }
-
- @Test
- void testXPathAttributeLocator_WithoutAttribute() {
- final XPathAttributeLocator locator = XPathAttributeLocator
- .findAttribute("/path/path[@otherAttribute = 'text']");
- assertEquals("/path/path[@otherAttribute = 'text']", locator.getElementPath().getScript());
- assertNull(locator.getAttributeName());
- }
-
- @Test
- void testXPathAttributeLocator_WithoutPath() {
- testAttribute("@attribute", "", "attribute");
- }
-}
diff --git a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java b/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java
deleted file mode 100644
index 9e666a25..00000000
--- a/src/test/java/eu/europa/ted/efx/XPathContextualizerTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package eu.europa.ted.efx;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-import eu.europa.ted.efx.xpath.XPathContextualizer;
-
-class XPathContextualizerTest {
- private String contextualize(final String context, final String xpath) {
- return XPathContextualizer.contextualize(context, xpath);
- }
-
- @Test
- void testIdentical() {
- assertEquals(".", contextualize("/a/b/c", "/a/b/c"));
- }
-
- @Test
- void testContextEmpty() {
- assertEquals("/a/b/c", contextualize("", "/a/b/c"));
- }
-
- @Test
- void testUnderContext() {
- assertEquals("c", contextualize("/a/b", "/a/b/c"));
- }
-
- @Test
- void testAboveContext() {
- assertEquals("..", contextualize("/a/b/c", "/a/b"));
- }
-
- @Test
- void testSibling() {
- assertEquals("../d", contextualize("/a/b/c", "/a/b/d"));
- }
-
- @Test
- void testTwoLevelsDifferent() {
- assertEquals("../../x/y", contextualize("/a/b/c/d", "/a/b/x/y"));
- }
-
- @Test
- void testAllDifferent() {
- assertEquals("../../../x/y/z", contextualize("/a/b/c/d", "/a/x/y/z"));
- }
-
- @Test
- void testDifferentRoot() {
- // Not realistic, as XML has a single root, but a valid result
- assertEquals("../../../x/y/z", contextualize("/a/b/c", "/x/y/z"));
- }
-
- @Test
- void testAttributeInXpath() {
- assertEquals("../c/@attribute", contextualize("/a/b", "/a/c/@attribute"));
- }
-
- @Test
- void testAttributeInContext() {
- assertEquals("../c/d", contextualize("/a/b/@attribute", "/a/b/c/d"));
- }
-
- @Test
- void testAttributeInBoth() {
- assertEquals("../@x", contextualize("/a/b/c/@d", "/a/b/c/@x"));
- }
-
- @Test
- void testAttributeInBothSame() {
- assertEquals(".", contextualize("/a/b/c/@d", "/a/b/c/@d"));
- }
-
- @Test
- void testPredicateInXpathLeaf() {
- assertEquals("../d[x/y = 'z']", contextualize("/a/b/c", "/a/b/d[x/y = 'z']"));
- }
-
- @Test
- void testPredicateBeingTheOnlyDifference() {
- assertEquals(".[x/y = 'z']", contextualize("/a/b/c", "/a/b/c[x/y = 'z']"));
- }
-
- @Test
- void testPredicateInContextBeingTheOnlyDifference() {
- assertEquals(".", contextualize("/a/b/c[e/f = 'z']", "/a/b/c"));
- }
-
- @Test
- void testPredicatesBeingTheOnlyDifferences() {
- assertEquals("..[u/v = 'w']/c[x/y = 'z']", contextualize("/a/b/c", "/a/b[u/v = 'w']/c[x/y = 'z']"));
- }
-
- @Test
- void testPredicateInContextLeaf() {
- assertEquals("../d", contextualize("/a/b/c[e/f = 'z']", "/a/b/d"));
- }
-
- @Test
- void testPredicateInBothLeaf() {
- assertEquals("../d[x = 'y']", contextualize("/a/b/c[e = 'f']", "/a/b/d[x = 'y']"));
- }
-
- @Test
- void testPredicateInXpathMiddle() {
- assertEquals("..[x/y = 'z']/d", contextualize("/a/b/c", "/a/b[x/y = 'z']/d"));
- }
-
- @Test
- void testPredicateInContextMiddle() {
- assertEquals("../d", contextualize("/a/b[e/f = 'z']/c", "/a/b/d"));
- }
-
- @Test
- void testPredicateSameInBoth() {
- assertEquals("../d", contextualize("/a/b[e/f = 'z']/c", "/a/b[e/f = 'z']/d"));
- }
-
- @Test
- void testPredicateDifferentOnSameElement() {
- assertEquals("../../b[x = 'y']/d", contextualize("/a/b[e = 'f']/c", "/a/b[x = 'y']/d"));
- }
-
- @Test
- void testPredicateDifferent() {
- assertEquals(".[x = 'y']/d", contextualize("/a/b[e = 'f']/c", "/a/b/c[x = 'y']/d"));
- }
-
- @Test
- void testPredicateMoreInXpath() {
- assertEquals("../../b[e][f]/c/d", contextualize("/a/b[e]/c", "/a/b[e][f]/c/d"));
- }
-
- @Test
- void testPredicateMoreInContext() {
- assertEquals("d", contextualize("/a/b[e][f]/c", "/a/b[e]/c/d"));
- }
-
- @Test
- void testSeveralPredicatesIdentical() {
- assertEquals("d", contextualize("/a/b[e][f]/c", "/a/b[e][f]/c/d"));
- }
-
- @Test
- void testSeveralPredicatesOneDifferent() {
- assertEquals("../../b[e][x]/c/d", contextualize("/a/b[e][f]/c", "/a/b[e][x]/c/d"));
- }
-}
diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java
index 8d67e44c..8c5e3d57 100644
--- a/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java
+++ b/src/test/java/eu/europa/ted/efx/mock/sdk1/SymbolResolverMockV1.java
@@ -9,12 +9,15 @@
import java.util.Map.Entry;
import java.util.Optional;
+import eu.europa.ted.eforms.xpath.XPathInfo;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
import eu.europa.ted.efx.mock.AbstractSymbolResolverMock;
+import eu.europa.ted.efx.model.expressions.Expression;
+import eu.europa.ted.efx.model.expressions.path.NodePathExpression;
import eu.europa.ted.efx.model.expressions.path.PathExpression;
import eu.europa.ted.efx.sdk1.entity.SdkCodelistV1;
import eu.europa.ted.efx.sdk1.entity.SdkFieldV1;
import eu.europa.ted.efx.sdk1.entity.SdkNodeV1;
-import eu.europa.ted.efx.xpath.XPathAttributeLocator;
public class SymbolResolverMockV1
extends AbstractSymbolResolverMock {
@@ -57,16 +60,19 @@ protected String getFieldsJsonFilename() {
@Override
public boolean isAttributeField(final String fieldId) {
- return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).hasAttribute();
+ XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript());
+ return xpathInfo.isAttribute();
}
@Override
public String getAttributeNameFromAttributeField(String fieldId) {
- return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttributeName();
+ XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript());
+ return xpathInfo.getAttributeName();
}
@Override
public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) {
- return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getElementPath();
+ XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript());
+ return Expression.instantiate(xpathInfo.getPathToLastElement(), NodePathExpression.class);
}
}
diff --git a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java
index e2353b13..8a4028c4 100644
--- a/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java
+++ b/src/test/java/eu/europa/ted/efx/mock/sdk2/SymbolResolverMockV2.java
@@ -10,12 +10,15 @@
import java.util.Optional;
import java.util.stream.Collectors;
+import eu.europa.ted.eforms.xpath.XPathInfo;
+import eu.europa.ted.eforms.xpath.XPathProcessor;
import eu.europa.ted.efx.mock.AbstractSymbolResolverMock;
+import eu.europa.ted.efx.model.expressions.Expression;
+import eu.europa.ted.efx.model.expressions.path.NodePathExpression;
import eu.europa.ted.efx.model.expressions.path.PathExpression;
import eu.europa.ted.efx.sdk2.entity.SdkCodelistV2;
import eu.europa.ted.efx.sdk2.entity.SdkFieldV2;
import eu.europa.ted.efx.sdk2.entity.SdkNodeV2;
-import eu.europa.ted.efx.xpath.XPathAttributeLocator;
public class SymbolResolverMockV2
extends AbstractSymbolResolverMock {
@@ -83,16 +86,19 @@ protected String getFieldsJsonFilename() {
@Override
public boolean isAttributeField(final String fieldId) {
- return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).hasAttribute();
+ XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript());
+ return xpathInfo.isAttribute();
}
@Override
public String getAttributeNameFromAttributeField(String fieldId) {
- return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getAttributeName();
+ XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript());
+ return xpathInfo.getAttributeName();
}
@Override
public PathExpression getAbsolutePathOfFieldWithoutTheAttribute(String fieldId) {
- return XPathAttributeLocator.findAttribute(this.getAbsolutePathOfField(fieldId)).getElementPath();
+ XPathInfo xpathInfo = XPathProcessor.parse(this.getAbsolutePathOfField(fieldId).getScript());
+ return Expression.instantiate(xpathInfo.getPathToLastElement(), NodePathExpression.class);
}
}
diff --git a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java
index a98697b5..4e2c45ee 100644
--- a/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java
+++ b/src/test/java/eu/europa/ted/efx/sdk1/EfxTemplateTranslatorV1Test.java
@@ -180,6 +180,13 @@ void testStandardLabelReference_UsingLabelTypeAsAssetId() {
translateTemplate("{BT-00-Text} #{auxiliary|text|value}"));
}
+ @Test
+ void testStandardLabelReference_WithAssetIdIterator() {
+ assertEquals(
+ "let block01() -> { label(distinct-values(for $item in for $t in ./normalize-space(text()) return $t return concat('field', '|', 'name', '|', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ translateTemplate("{BT-00-Text} #{field|name|${for text:$t in BT-00-Text return $t}}"));
+ }
+
@Test
void testShorthandBtLabelReference() {
assertEquals(
@@ -203,42 +210,42 @@ void testShorthandBtLabelReference_MissingLabelType() {
@Test
void testShorthandIndirectLabelReferenceForIndicator() {
assertEquals(
- "let block01() -> { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-Indicator}"));
}
@Test
void testShorthandIndirectLabelReferenceForCode() {
assertEquals(
- "let block01() -> { label(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-Code}"));
}
@Test
void testShorthandIndirectLabelReferenceForInternalCode() {
assertEquals(
- "let block01() -> { label(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}"));
}
@Test
void testShorthandIndirectLabelReferenceForCodeAttribute() {
assertEquals(
- "let block01() -> { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-CodeAttribute}"));
}
@Test
void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameAttributeInContext() {
assertEquals(
- "let block01() -> { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())",
translateTemplate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}"));
}
@Test
void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() {
assertEquals(
- "let block01() -> { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())",
translateTemplate("{BT-00-Code} #{BT-00-CodeAttribute}"));
}
@@ -297,7 +304,7 @@ void testShorthandLabelReferenceFromContext_WithNodeContext() {
@Test
void testShorthandIndirectLabelReferenceFromContextField() {
assertEquals(
- "let block01() -> { label(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())",
translateTemplate("{BT-00-Code} #value"));
}
@@ -318,7 +325,7 @@ void testShorthandFieldValueReferenceFromContextField() {
@Test
void testShorthandFieldValueReferenceFromContextField_WithText() {
assertEquals(
- "let block01() -> { text('blah ')label(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' ')text('blah ')eval(./normalize-space(text()))text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())",
+ "let block01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' ')text('blah ')eval(./normalize-space(text()))text(' ')text('blah') }\nfor-each(/*/PathNode/CodeField).call(block01())",
translateTemplate("{BT-00-Code} blah #value blah $value blah"));
}
@@ -343,4 +350,14 @@ void testEndOfLineComments() {
"let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' ')text('blah blah') }\nfor-each(/*).call(block01())",
translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah"));
}
+
+ @Test
+ void testImplicitFormatting_Dates() {
+ assertEquals("let block01() -> { eval(format-date(PathNode/StartDateField/xs:date(text()), '[D01]/[M01]/[Y0001]')) }\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} ${BT-00-StartDate}"));
+ }
+
+ @Test
+ void testImplicitFormatting_Times() {
+ assertEquals("let block01() -> { eval(format-time(PathNode/StartTimeField/xs:time(text()), '[H01]:[m01] [Z]')) }\nfor-each(/*).call(block01())", translateTemplate("{ND-Root} ${BT-00-StartTime}"));
+ }
}
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 f78cc073..127f911d 100644
--- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java
+++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java
@@ -292,42 +292,42 @@ void testShorthandBtLabelReference_MissingLabelType() {
@Test
void testShorthandIndirectLabelReferenceForIndicator() {
assertEquals(
- "let block01() -> { label(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator')) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../IndicatorField return concat('indicator', '|', 'when', '-', $item, '|', 'BT-00-Indicator'))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-Indicator}"));
}
@Test
void testShorthandIndirectLabelReferenceForCode() {
assertEquals(
- "let block01() -> { label(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../CodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-Code}"));
}
@Test
void testShorthandIndirectLabelReferenceForInternalCode() {
assertEquals(
- "let block01() -> { label(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../InternalCodeField/normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-Internal-Code}"));
}
@Test
void testShorthandIndirectLabelReferenceForCodeAttribute() {
assertEquals(
- "let block01() -> { label(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/TextField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../CodeField/@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/TextField).call(block01())",
translateTemplate("{BT-00-Text} #{BT-00-CodeAttribute}"));
}
@Test
void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameAttributeInContext() {
assertEquals(
- "let block01() -> { label(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ../@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField/@attribute).call(block01())",
translateTemplate("{BT-00-CodeAttribute} #{BT-00-CodeAttribute}"));
}
@Test
void testShorthandIndirectLabelReferenceForCodeAttribute_WithSameElementInContext() {
assertEquals(
- "let block01() -> { label(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ./@attribute return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())",
translateTemplate("{BT-00-Code} #{BT-00-CodeAttribute}"));
}
@@ -386,7 +386,7 @@ void testShorthandLabelReferenceFromContext_WithNodeContext() {
@Test
void testShorthandIndirectLabelReferenceFromContextField() {
assertEquals(
- "let block01() -> { label(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)) }\nfor-each(/*/PathNode/CodeField).call(block01())",
+ "let block01() -> { label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))) }\nfor-each(/*/PathNode/CodeField).call(block01())",
translateTemplate("{BT-00-Code} #value"));
}
@@ -407,7 +407,7 @@ void testShorthandFieldValueReferenceFromContextField() {
@Test
void testShorthandFieldValueReferenceFromContextField_WithText() {
assertEquals(
- "let block01() -> { text('blah ')label(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item))text(' blah ')eval(./normalize-space(text()))text(' blah') }\nfor-each(/*/PathNode/CodeField).call(block01())",
+ "let block01() -> { text('blah ')label(distinct-values(for $item in ./normalize-space(text()) return concat('code', '|', 'name', '|', 'main-activity', '.', $item)))text(' blah ')eval(./normalize-space(text()))text(' blah') }\nfor-each(/*/PathNode/CodeField).call(block01())",
translateTemplate("{BT-00-Code} blah #value blah $value blah"));
}