aboutsummaryrefslogtreecommitdiffstats
path: root/cps-path-parser
diff options
context:
space:
mode:
authorToineSiebelink <toine.siebelink@est.tech>2021-07-06 13:03:03 +0100
committerToineSiebelink <toine.siebelink@est.tech>2021-07-20 16:19:37 +0100
commitf0527c58c17963d940535d0ce0eb934c2b4c635c (patch)
treef2d8df5b92f3b3e50655fb9a0d685672bf010f0a /cps-path-parser
parent6355b212de77e658b16614eb775f03c7713c8460 (diff)
Support text() condition
- Added Antlr parsing of text() condition (as an optional additional to any query) - Implemented text-condition combined with descendants - Refactor descendants queries into using one more flexible Custom (native) Query builder - Refactor ALL cpsPath queries to now use FragmentRepositoryCpsPathQuery (custom query builder) - Refactor Antrl code to simply parsing of cpsPath and allow all combinations (no more query types, addresses CPS-436) - Minor clean up of some minor convention issues in CpsAdminServiceImplSpec.groovy (found during groovy demo) - Update .rst documentation of xPaths - Fixed incorrect matching of additional list indexes using more precise SIMILAR-TO regex in postgreSQL - Documented special chararter limitation (CPS-500) - Checked for consistent use of term 'CPS path' in documentation and error message - Included (updated) copyright in all .SQL test files Issue-ID: CPS-452 Issue-ID: CPS-436 Issue-ID: CPS-500 Signed-off-by: ToineSiebelink <toine.siebelink@est.tech> Change-Id: If422d25cafd2850d25c9a28dea16ba7a5f93dddb
Diffstat (limited to 'cps-path-parser')
-rw-r--r--cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g454
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java52
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathPrefixType.java (renamed from cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQueryType.java)17
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java28
-rw-r--r--cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy63
5 files changed, 120 insertions, 94 deletions
diff --git a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
index 198cede0d..cefeac438 100644
--- a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
+++ b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
@@ -20,56 +20,50 @@
grammar CpsPath ;
-cpsPath: (cpsPathWithSingleLeafCondition | cpsPathWithDescendant | cpsPathWithDescendantAndLeafConditions) ancestorAxis? ;
+cpsPath : ( prefix | descendant | incorrectPrefix ) multipleLeafConditions? textFunctionCondition? ancestorAxis? ;
-ancestorAxis: SLASH KW_ANCESTOR COLONCOLON ancestorPath ;
+ancestorAxis : SLASH KW_ANCESTOR COLONCOLON ancestorPath ;
-ancestorPath: yangElement (SLASH yangElement)* ;
+ancestorPath : yangElement ( SLASH yangElement)* ;
-cpsPathWithSingleLeafCondition: prefix singleValueCondition postfix? ;
+textFunctionCondition : SLASH leafName OB KW_TEXT_FUNCTION EQ StringLiteral CB ;
-/*
-No need to ditinguish between cpsPathWithDescendant | cpsPathWithDescendantAndLeafConditions really!
-See https://jira.onap.org/browse/CPS-436
-*/
-
-cpsPathWithDescendant: descendant ;
-
-cpsPathWithDescendantAndLeafConditions: descendant multipleValueConditions ;
+prefix : ( SLASH yangElement)* SLASH containerName ;
-descendant: SLASH prefix ;
+descendant : SLASH prefix ;
-prefix: (SLASH yangElement)* SLASH containerName ;
+incorrectPrefix : SLASH SLASH SLASH+ ;
-postfix: (SLASH yangElement)+ ;
+yangElement : containerName listElementRef? ;
-yangElement: containerName listElementRef? ;
+containerName : QName ;
-containerName: QName ;
+listElementRef : OB leafCondition ( KW_AND leafCondition)* CB ;
-listElementRef: multipleValueConditions ;
+multipleLeafConditions : OB leafCondition ( KW_AND leafCondition)* CB ;
-singleValueCondition: '[' leafCondition ']' ;
+leafCondition : AT leafName EQ ( IntegerLiteral | StringLiteral) ;
-multipleValueConditions: '[' leafCondition (' and ' leafCondition)* ']' ;
-
-leafCondition: '@' leafName '=' (IntegerLiteral | StringLiteral ) ;
-
-//To Confirm: defintion of Lefname with external xPath grammar
-leafName: QName ;
+leafName : QName ;
/*
* Lexer Rules
- * Most of the lexer rules below are 'imporetd' from
+ * Most of the lexer rules below are inspired by
* https://raw.githubusercontent.com/antlr/grammars-v4/master/xpath/xpath31/XPath31.g4
*/
-SLASH : '/';
+AT : '@' ;
+CB : ']' ;
COLONCOLON : '::' ;
+EQ : '=' ;
+OB : '[' ;
+SLASH : '/' ;
// KEYWORDS
KW_ANCESTOR : 'ancestor' ;
+KW_AND : 'and' ;
+KW_TEXT_FUNCTION: 'text()' ;
IntegerLiteral : FragDigits ;
// Add below type definitions for leafvalue comparision in https://jira.onap.org/browse/CPS-440
@@ -77,7 +71,7 @@ DecimalLiteral : ('.' FragDigits) | (FragDigits '.' [0-9]*) ;
DoubleLiteral : (('.' FragDigits) | (FragDigits ('.' [0-9]*)?)) [eE] [+-]? FragDigits ;
StringLiteral : ('"' (FragEscapeQuot | ~[^"])*? '"') | ('\'' (FragEscapeApos | ~['])*? '\'') ;
fragment FragEscapeQuot : '""' ;
-fragment FragEscapeApos : '\'';
+fragment FragEscapeApos : '\'' ;
fragment FragDigits : [0-9]+ ;
QName : FragQName ;
@@ -109,7 +103,7 @@ fragment FragNCNameChar
| '\u00B7' | '\u0300'..'\u036F'
| '\u203F'..'\u2040'
;
-fragment FragmentNCName : FragNCNameStartChar FragNCNameChar* ;
+fragment FragmentNCName : FragNCNameStartChar FragNCNameChar* ;
// https://www.w3.org/TR/REC-xml/#NT-Char
@@ -117,7 +111,7 @@ fragment FragChar : '\u0009' | '\u000a' | '\u000d'
| '\u0020'..'\ud7ff'
| '\ue000'..'\ufffd'
| '\u{10000}'..'\u{10ffff}'
- ;
+ ;
// Skip all Whitespace
Whitespace : ('\u000d' | '\u000a' | '\u0020' | '\u0009')+ -> skip ;
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
index b9d0c25b1..ebf6fd3c9 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
@@ -20,18 +20,18 @@
package org.onap.cps.cpspath.parser;
+import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT;
+
import java.util.HashMap;
import java.util.Map;
import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener;
import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.AncestorAxisContext;
-import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.CpsPathWithDescendantAndLeafConditionsContext;
-import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.CpsPathWithDescendantContext;
-import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.CpsPathWithSingleLeafConditionContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.DescendantContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.IncorrectPrefixContext;
import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.LeafConditionContext;
-import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.MultipleValueConditionsContext;
-import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.PostfixContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.MultipleLeafConditionsContext;
import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.PrefixContext;
-import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.SingleValueConditionContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.TextFunctionConditionContext;
public class CpsPathBuilder extends CpsPathBaseListener {
@@ -45,8 +45,8 @@ public class CpsPathBuilder extends CpsPathBaseListener {
}
@Override
- public void exitPostfix(final PostfixContext ctx) {
- throw new IllegalStateException(String.format("Unsupported postfix %s encountered in CpsPath.", ctx.getText()));
+ public void exitIncorrectPrefix(final IncorrectPrefixContext ctx) {
+ throw new IllegalStateException("CPS path can only start with one or two slashes (/)");
}
@Override
@@ -64,38 +64,18 @@ public class CpsPathBuilder extends CpsPathBaseListener {
}
@Override
- public void enterSingleValueCondition(final SingleValueConditionContext ctx) {
- leavesData.clear();
+ public void exitDescendant(final DescendantContext ctx) {
+ cpsPathQuery.setCpsPathPrefixType(DESCENDANT);
+ cpsPathQuery.setDescendantName(ctx.getText().substring(2));
}
@Override
- public void enterMultipleValueConditions(final MultipleValueConditionsContext ctx) {
+ public void enterMultipleLeafConditions(final MultipleLeafConditionsContext ctx) {
leavesData.clear();
}
@Override
- public void exitSingleValueCondition(final SingleValueConditionContext ctx) {
- final String leafName = ctx.leafCondition().leafName().getText();
- cpsPathQuery.setLeafName(leafName);
- cpsPathQuery.setLeafValue(leavesData.get(leafName));
- }
-
- @Override
- public void exitCpsPathWithSingleLeafCondition(final CpsPathWithSingleLeafConditionContext ctx) {
- cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_LEAF_VALUE);
- }
-
- @Override
- public void exitCpsPathWithDescendant(final CpsPathWithDescendantContext ctx) {
- cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE);
- cpsPathQuery.setDescendantName(cpsPathQuery.getXpathPrefix().substring(1));
- }
-
- @Override
- public void exitCpsPathWithDescendantAndLeafConditions(
- final CpsPathWithDescendantAndLeafConditionsContext ctx) {
- cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES);
- cpsPathQuery.setDescendantName(cpsPathQuery.getXpathPrefix().substring(1));
+ public void exitMultipleLeafConditions(final MultipleLeafConditionsContext ctx) {
cpsPathQuery.setLeavesData(leavesData);
}
@@ -104,6 +84,12 @@ public class CpsPathBuilder extends CpsPathBaseListener {
cpsPathQuery.setAncestorSchemaNodeIdentifier(ctx.ancestorPath().getText());
}
+ @Override
+ public void exitTextFunctionCondition(final TextFunctionConditionContext ctx) {
+ cpsPathQuery.setTextFunctionConditionLeafName(ctx.leafName().getText());
+ cpsPathQuery.setTextFunctionConditionValue(stripFirstAndLastCharacter(ctx.StringLiteral().getText()));
+ }
+
CpsPathQuery build() {
return cpsPathQuery;
}
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQueryType.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathPrefixType.java
index ac3e31334..dfac9b0c8 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQueryType.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathPrefixType.java
@@ -22,19 +22,16 @@
package org.onap.cps.cpspath.parser;
/**
- * The enum Cps path query type.
+ * The enum Cps path prefix type.
*/
-public enum CpsPathQueryType {
+public enum CpsPathPrefixType {
/**
- * Xpath descendant anywhere type e.g. //nodeName .
+ * Fully qualified Xpath starting from root with single slash e.g. /parent/child .
*/
- XPATH_HAS_DESCENDANT_ANYWHERE,
- /**
- * Xpath descendant anywhere type e.g. //nodeName[@leafName="value"] .
- */
- XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES,
+ ABSOLUTE,
+
/**
- * Xpath leaf value cps path query type e.g. /cps-path[@leaf1="leafValue" and @leaf2=123] .
+ * Xpath descendant anywhere starting with double slash type e.g. //child/grandchild .
*/
- XPATH_LEAF_VALUE
+ DESCENDANT
}
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
index 107bfa3e3..de7adf2b7 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
@@ -20,6 +20,8 @@
package org.onap.cps.cpspath.parser;
+import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE;
+
import java.util.Map;
import lombok.AccessLevel;
import lombok.Getter;
@@ -36,13 +38,13 @@ import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
@Setter(AccessLevel.PACKAGE)
public class CpsPathQuery {
- private CpsPathQueryType cpsPathQueryType;
private String xpathPrefix;
- private String leafName;
- private Object leafValue;
+ private CpsPathPrefixType cpsPathPrefixType = ABSOLUTE;
private String descendantName;
private Map<String, Object> leavesData;
private String ancestorSchemaNodeIdentifier = "";
+ private String textFunctionConditionLeafName;
+ private String textFunctionConditionValue;
/**
* Returns a cps path query.
@@ -68,7 +70,7 @@ public class CpsPathQuery {
}
/**
- * Has ancestor axis been populated.
+ * Has ancestor axis been included in cpsPath.
*
* @return boolean value.
*/
@@ -76,4 +78,22 @@ public class CpsPathQuery {
return !(ancestorSchemaNodeIdentifier.isEmpty());
}
+ /**
+ * Have leaf value conditions been included in cpsPath.
+ *
+ * @return boolean value.
+ */
+ public boolean hasLeafConditions() {
+ return leavesData != null;
+ }
+
+ /**
+ * Has text function condition been included in cpsPath.
+ *
+ * @return boolean value.
+ */
+ public boolean hasTextFunctionCondition() {
+ return textFunctionConditionLeafName != null;
+ }
+
}
diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
index b7826f67b..bfec574eb 100644
--- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
@@ -22,17 +22,21 @@ package org.onap.cps.cpspath.parser
import spock.lang.Specification
+import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE
+import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT
+
class CpsPathQuerySpec extends Specification {
def 'Parse cps path with valid cps path and a filter with #scenario.'() {
when: 'the given cps path is parsed'
def result = CpsPathQuery.createFrom(cpsPath)
- then: 'the query has the right type'
- result.cpsPathQueryType == CpsPathQueryType.XPATH_LEAF_VALUE
+ then: 'the query has the right xpath type'
+ result.cpsPathPrefixType == ABSOLUTE
and: 'the right query parameters are set'
result.xpathPrefix == expectedXpathPrefix
- result.leafName == expectedLeafName
- result.leafValue == expectedLeafValue
+ result.hasLeafConditions() == true
+ result.leavesData.containsKey(expectedLeafName) == true
+ result.leavesData.get(expectedLeafName) == expectedLeafValue
where: 'the following data is used'
scenario | cpsPath || expectedXpathPrefix | expectedLeafName | expectedLeafValue
'leaf of type String' | '/parent/child[@common-leaf-name="common-leaf-value"]' || '/parent/child' | 'common-leaf-name' | 'common-leaf-value'
@@ -46,8 +50,8 @@ class CpsPathQuerySpec extends Specification {
def 'Parse cps path of type ends with a #scenario.'() {
when: 'the given cps path is parsed'
def result = CpsPathQuery.createFrom(cpsPath)
- then: 'the query has the right type'
- result.cpsPathQueryType == CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
+ then: 'the query has the right xpath type'
+ result.cpsPathPrefixType == DESCENDANT
and: 'the right ends with parameters are set'
result.descendantName == expectedDescendantName
where: 'the following data is used'
@@ -59,9 +63,9 @@ class CpsPathQuerySpec extends Specification {
def 'Parse cps path that ends with a yang list containing #scenario.'() {
when: 'the given cps path is parsed'
def result = CpsPathQuery.createFrom(cpsPath)
- then: 'the query has the right type'
- result.cpsPathQueryType == CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES
- and: 'the right ends with parameters are set'
+ then: 'the query has the right xpath type'
+ result.cpsPathPrefixType == DESCENDANT
+ and: 'the right parameters are set'
result.descendantName == "child"
result.leavesData.size() == expectedNumberOfLeaves
where: 'the following data is used'
@@ -70,6 +74,27 @@ class CpsPathQuerySpec extends Specification {
'more than one attribute' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2
}
+ def 'Parse #scenario cps path with text function condition'() {
+ when: 'the given cps path is parsed'
+ def result = CpsPathQuery.createFrom(cpsPath)
+ then: 'the query has the right xpath type'
+ result.cpsPathPrefixType == DESCENDANT
+ and: 'leaf conditions are only present when expected'
+ result.hasLeafConditions() == expectLeafConditions
+ and: 'the right text function condition is set'
+ result.hasTextFunctionCondition()
+ result.textFunctionConditionLeafName == 'leaf-name'
+ result.textFunctionConditionValue == 'search'
+ and: 'the ancestor is only present when expected'
+ assert result.hasAncestorAxis() == expectHasAncestorAxis
+ where: 'the following data is used'
+ scenario | cpsPath || expectLeafConditions | expectHasAncestorAxis
+ 'descendant anywhere' | '//someContainer/leaf-name[text()="search"]' || false | false
+ 'descendant with leaf value' | '//child[@other-leaf=1]/leaf-name[text()="search"]' || true | false
+ 'descendant anywhere and ancestor' | '//someContainer/leaf-name[text()="search"]/ancestor::parent' || false | true
+ 'descendant with leaf value and ancestor' | '//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent' || true | true
+ }
+
def 'Parse cps path with error: #scenario.'() {
when: 'the given cps path is parsed'
CpsPathQuery.createFrom(cpsPath)
@@ -85,18 +110,20 @@ class CpsPathQuerySpec extends Specification {
'end with descendant and more than one attribute separated by "or"' | '//child[@int-leaf=5 or @leaf-name="leaf value"]'
'missing attribute value' | '//child[@int-leaf=5 and @name]'
'incomplete ancestor value' | '//books/ancestor::'
- 'unsupported postfix after single value condition (JIRA CPS-450)' | '/parent/child[@id=1]/somePostFix'
+// DISCUSS WITH TEAM : 'unsupported postfix after value condition (JIRA CPS-450)' | '/parent/child[@id=1]/somePostFix'
}
def 'Parse cps path using ancestor by schema node identifier with a #scenario.'() {
when: 'the given cps path is parsed'
def result = CpsPathQuery.createFrom('//descendant/ancestor::' + ancestorPath)
then: 'the query has the right type'
- result.cpsPathQueryType == CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
+ result.cpsPathPrefixType == DESCENDANT
and: 'the result has ancestor axis'
result.hasAncestorAxis()
and: 'the correct ancestor schema node identifier is set'
result.ancestorSchemaNodeIdentifier == ancestorPath
+ and: 'there are no leaves conditions'
+ result.hasLeafConditions() == false
where:
scenario | ancestorPath
'basic container' | 'someContainer'
@@ -109,18 +136,20 @@ class CpsPathQuerySpec extends Specification {
when: 'the given cps path is parsed'
def result = CpsPathQuery.createFrom(cpsPath + '/ancestor::someAncestor')
then: 'the query has the right type'
- result.cpsPathQueryType == expectedCpsPathQueryType
+ result.cpsPathPrefixType == DESCENDANT
+ and: 'leaf conditions are only present when expected'
+ result.hasLeafConditions() == expectLeafConditions
and: 'the result has ancestor axis'
result.hasAncestorAxis()
and: 'the correct ancestor schema node identifier is set'
result.ancestorSchemaNodeIdentifier == 'someAncestor'
result.descendantName == expectedDescendantName
where:
- scenario | cpsPath || expectedDescendantName | expectedCpsPathQueryType
- 'basic container' | '//someContainer' || 'someContainer' | CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
- 'container with parent' | '//parent/child' || 'parent/child' | CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
- 'container with list-parent' | '//parent[@id=1]/child' || 'parent[@id=1]/child' | CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
- 'container with list-parent' | '//parent[@id=1]/child[@name="test"]' || 'parent[@id=1]/child' | CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES
+ scenario | cpsPath || expectedDescendantName | expectLeafConditions
+ 'basic container' | '//someContainer' || 'someContainer' | false
+ 'container with parent' | '//parent/child' || 'parent/child' | false
+ 'container with list-parent' | '//parent[@id=1]/child' || 'parent[@id=1]/child' | false
+ 'container with list-parent' | '//parent[@id=1]/child[@name="test"]' || 'parent[@id=1]/child' | true
}
}