aboutsummaryrefslogtreecommitdiffstats
path: root/cps-path-parser
diff options
context:
space:
mode:
authorToineSiebelink <toine.siebelink@est.tech>2021-05-20 16:44:21 +0100
committerToineSiebelink <toine.siebelink@est.tech>2021-06-01 10:12:55 +0100
commitc37678a3eb62685d32a1581729e2a4e26002bffc (patch)
tree1901f7e3517ae339f99905f7ffc0021553874842 /cps-path-parser
parent9de3b68373dd8554e64f34bb3093403521f8759f (diff)
Introducing Antlr4 for cpsPath parsing
-created new module for cpPathParser -added antlr rule for cpsPathWithSingleLeafCondition -added antlr rule for cpsPathWithDescendant (and with leaf conditions) -added antlr rule for ancestor axis -added unit test (copied from existing CpsPathQuerySpec) -udpated cps-ri to use new cpPathQuery from parser module -'imported' lexer rules from publix xPath grammar -Re-used existing CpsPathException but conversion happens in cps-ri to prevent additional dependency in cps-path-parser module Issue-ID: CPS-376 Change-Id: I2c5df98969402cbf69f6573c52705879450ce606 Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Diffstat (limited to 'cps-path-parser')
-rw-r--r--cps-path-parser/lombok.config2
-rw-r--r--cps-path-parser/pom.xml91
-rw-r--r--cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4123
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java107
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java79
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQueryType.java39
-rw-r--r--cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy124
7 files changed, 565 insertions, 0 deletions
diff --git a/cps-path-parser/lombok.config b/cps-path-parser/lombok.config
new file mode 100644
index 000000000..a23edb413
--- /dev/null
+++ b/cps-path-parser/lombok.config
@@ -0,0 +1,2 @@
+config.stopBubbling = true
+lombok.addLombokGeneratedAnnotation = true \ No newline at end of file
diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml
new file mode 100644
index 000000000..38ba9acf3
--- /dev/null
+++ b/cps-path-parser/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============LICENSE_START=======================================================
+ Copyright (c) 2021 Linux Foundation.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.onap.cps</groupId>
+ <artifactId>cps-parent</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <relativePath>../cps-parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>cps-path-parser</artifactId>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr4-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>antlr4</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr4-runtime</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+ <!-- T E S T D E P E N D E N C I E S -->
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-spring</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>cglib</groupId>
+ <artifactId>cglib-nodep</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+</project>
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
new file mode 100644
index 000000000..86095459e
--- /dev/null
+++ b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
@@ -0,0 +1,123 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+grammar CpsPath ;
+
+cpsPath: (cpsPathWithSingleLeafCondition | cpsPathWithDescendant | cpsPathWithDescendantAndLeafConditions) ancestorAxis? ;
+
+ancestorAxis: SLASH KW_ANCESTOR COLONCOLON ancestorPath ;
+
+ancestorPath: yangElement (SLASH yangElement)* ;
+
+cpsPathWithSingleLeafCondition: prefix singleValueCondition ;
+
+/*
+No need to ditinguish between cpsPathWithDescendant | cpsPathWithDescendantAndLeafConditions really!
+See https://jira.onap.org/browse/CPS-436
+*/
+
+cpsPathWithDescendant: descendant ;
+
+cpsPathWithDescendantAndLeafConditions: descendant multipleValueConditions ;
+
+descendant: SLASH prefix ;
+
+prefix: (SLASH yangElement)* SLASH containerName ;
+
+yangElement: containerName listElementRef? ;
+
+containerName: QName ;
+
+listElementRef: multipleValueConditions ;
+
+singleValueCondition: '[' leafCondition ']' ;
+
+multipleValueConditions: '[' leafCondition (' and ' leafCondition)* ']' ;
+
+leafCondition: '@' leafName '=' (IntegerLiteral | StringLiteral ) ;
+
+//To Confirm: defintion of Lefname with external xPath grammar
+leafName: QName ;
+
+/*
+ * Lexer Rules
+ * Most of the lexer rules below are 'imporetd' from
+ * https://raw.githubusercontent.com/antlr/grammars-v4/master/xpath/xpath31/XPath31.g4
+ */
+
+SLASH : '/';
+COLONCOLON : '::' ;
+
+// KEYWORDS
+
+KW_ANCESTOR : 'ancestor' ;
+
+IntegerLiteral : FragDigits ;
+// Add below type definitions for leafvalue comparision in https://jira.onap.org/browse/CPS-440
+DecimalLiteral : ('.' FragDigits) | (FragDigits '.' [0-9]*) ;
+DoubleLiteral : (('.' FragDigits) | (FragDigits ('.' [0-9]*)?)) [eE] [+-]? FragDigits ;
+StringLiteral : ('"' (FragEscapeQuot | ~[^"])*? '"') | ('\'' (FragEscapeApos | ~['])*? '\'') ;
+fragment FragEscapeQuot : '""' ;
+fragment FragEscapeApos : '\'';
+fragment FragDigits : [0-9]+ ;
+
+QName : FragQName ;
+NCName : FragmentNCName ;
+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}'
+ ;
+
+// Skip all Whitespace
+Whitespace : ('\u000d' | '\u000a' | '\u0020' | '\u0009')+ -> skip ;
+
+// handle characters which failed to match any other token (otherwise Antlr will ignore them)
+ErrorCharacter : . ;
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
new file mode 100644
index 000000000..83e076d53
--- /dev/null
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
@@ -0,0 +1,107 @@
+package org.onap.cps.cpspath.parser;
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+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.LeafConditionContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.MultipleValueConditionsContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.PrefixContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.SingleValueConditionContext;
+
+public class CpsPathBuilder extends CpsPathBaseListener {
+
+ final CpsPathQuery cpsPathQuery = new CpsPathQuery();
+
+ final Map<String, Object> leavesData = new HashMap<>();
+
+ @Override
+ public void exitPrefix(final PrefixContext ctx) {
+ cpsPathQuery.setXpathPrefix(ctx.getText());
+ }
+
+ @Override
+ public void exitLeafCondition(final LeafConditionContext ctx) {
+ Object comparisonValue = null;
+ if (ctx.IntegerLiteral() != null) {
+ comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText());
+ }
+ if (ctx.StringLiteral() != null) {
+ comparisonValue = stripFirstAndLastCharacter(ctx.StringLiteral().getText());
+ } else if (comparisonValue == null) {
+ throw new IllegalStateException("Unsupported comparison value encountered in expression" + ctx.getText());
+ }
+ leavesData.put(ctx.leafName().getText(), comparisonValue);
+ }
+
+ @Override
+ public void enterSingleValueCondition(final SingleValueConditionContext ctx) {
+ leavesData.clear();
+ }
+
+ @Override
+ public void enterMultipleValueConditions(final MultipleValueConditionsContext 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));
+ cpsPathQuery.setLeavesData(leavesData);
+ }
+
+ @Override
+ public void exitAncestorAxis(final AncestorAxisContext ctx) {
+ cpsPathQuery.setAncestorSchemaNodeIdentifier(ctx.ancestorPath().getText());
+ }
+
+ CpsPathQuery build() {
+ return cpsPathQuery;
+ }
+
+ private static String stripFirstAndLastCharacter(final String wrappedString) {
+ return wrappedString.substring(1, wrappedString.length() - 1);
+ }
+
+}
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
new file mode 100644
index 000000000..32fe0cbb7
--- /dev/null
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
@@ -0,0 +1,79 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.cpspath.parser;
+
+
+import java.util.Map;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathLexer;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
+
+@Getter
+@Setter(AccessLevel.PACKAGE)
+public class CpsPathQuery {
+
+ private CpsPathQueryType cpsPathQueryType;
+ private String xpathPrefix;
+ private String leafName;
+ private Object leafValue;
+ private String descendantName;
+ private Map<String, Object> leavesData;
+ private String ancestorSchemaNodeIdentifier = "";
+
+ /**
+ * Returns a cps path query.
+ *
+ * @param cpsPathSource cps path
+ * @return a CpsPathQuery object.
+ */
+ public static CpsPathQuery createFrom(final String cpsPathSource) {
+ final var inputStream = CharStreams.fromString(cpsPathSource);
+ final var cpsPathLexer = new CpsPathLexer(inputStream);
+ final var cpsPathParser = new CpsPathParser(new CommonTokenStream(cpsPathLexer));
+ cpsPathParser.addErrorListener(new BaseErrorListener() {
+ @Override
+ public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line,
+ final int charPositionInLine, final String msg, final RecognitionException e) {
+ throw new IllegalStateException("failed to parse at line " + line + " due to " + msg, e);
+ }
+ });
+ final var cpsPathBuilder = new CpsPathBuilder();
+ cpsPathParser.addParseListener(cpsPathBuilder);
+ cpsPathParser.cpsPath();
+ return cpsPathBuilder.build();
+ }
+
+ /**
+ * Has ancestor axis been populated.
+ *
+ * @return boolean value.
+ */
+ public boolean hasAncestorAxis() {
+ return !(ancestorSchemaNodeIdentifier.isEmpty());
+ }
+
+}
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/CpsPathQueryType.java
new file mode 100644
index 000000000..bc6b9f092
--- /dev/null
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQueryType.java
@@ -0,0 +1,39 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Nordix Foundation
+ * Modifications Copyright (C) 2020-2021 Bell Canada.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.cpspath.parser;
+
+/**
+ * The enum Cps path query type.
+ */
+public enum CpsPathQueryType {
+ /**
+ * Xpath descendant anywhere type e.g. //nodeName .
+ */
+ XPATH_HAS_DESCENDANT_ANYWHERE,
+ /**
+ * Xpath descendant anywhere type e.g. //nodeName[@leafName="value"] .
+ */
+ XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES,
+ /**
+ * Xpath leaf value cps path query type e.g. /cps-path[@leaf1="leafValue" and @leaf2=123] .
+ */
+ XPATH_LEAF_VALUE
+}
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
new file mode 100644
index 000000000..0e7fc35cf
--- /dev/null
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
@@ -0,0 +1,124 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.cpspath.parser
+
+import spock.lang.Specification
+
+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
+ and: 'the right query parameters are set'
+ result.xpathPrefix == expectedXpathPrefix
+ result.leafName == expectedLeafName
+ result.leafValue == 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'
+ 'leaf of type String' | '/parent/child[@common-leaf-name=\'common-leaf-value\']' || '/parent/child' | 'common-leaf-name' | 'common-leaf-value'
+ 'leaf of type Integer' | '/parent/child[@common-leaf-name-int=5]' || '/parent/child' | 'common-leaf-name-int' | 5
+ 'spaces around =' | '/parent/child[@common-leaf-name-int = 5]' || '/parent/child' | 'common-leaf-name-int' | 5
+ 'key in top container' | '/parent[@common-leaf-name-int=5]' || '/parent' | 'common-leaf-name-int' | 5
+ 'parent list' | '/shops/shop[@id=1]/categories[@id=1]/book[@title="Dune"]' || '/shops/shop[@id=1]/categories[@id=1]/book' | 'title' | 'Dune'
+ }
+
+ 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
+ and: 'the right ends with parameters are set'
+ result.descendantName == expectedDescendantName
+ where: 'the following data is used'
+ scenario | cpsPath || expectedDescendantName
+ 'yang container' | '//cps-path' || 'cps-path'
+ 'parent & child' | '//parent/child' || 'parent/child'
+ }
+
+ 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'
+ result.descendantName == "child"
+ result.leavesData.size() == expectedNumberOfLeaves
+ where: 'the following data is used'
+ scenario | cpsPath || expectedNumberOfLeaves
+ 'one attribute' | '//child[@common-leaf-name-int=5]' || 1
+ 'more than one attribute' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2
+ }
+
+ def 'Parse cps path with error: #scenario.'() {
+ when: 'the given cps path is parsed'
+ CpsPathQuery.createFrom(cpsPath)
+ then: 'a CpsPathException is thrown'
+ thrown(IllegalStateException)
+ where: 'the following data is used'
+ scenario | cpsPath
+ 'no / at the start' | 'invalid-cps-path/child'
+ 'additional / after descendant option' | '///cps-path'
+ 'float value' | '/parent/child[@someFloat=5.0]'
+ 'unmatched quotes, double quote first ' | '/parent/child[@someString="value with unmatched quotes\']'
+ 'unmatched quotes, single quote first' | '/parent/child[@someString=\'value with unmatched quotes"]'
+ '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::'
+ }
+
+ 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
+ and: 'the result has ancestor axis'
+ result.hasAncestorAxis()
+ and: 'the correct ancestor schema node identifier is set'
+ result.ancestorSchemaNodeIdentifier == ancestorPath
+ where:
+ scenario | ancestorPath
+ 'basic container' | 'someContainer'
+ 'container with parent' | 'parent/child'
+ 'ancestor that is a list' | 'categories[@code=1]'
+ 'parent that is a list' | 'parent[@id=1]/child'
+ }
+
+ def 'Combinations #scenario.'() {
+ when: 'the given cps path is parsed'
+ def result = CpsPathQuery.createFrom(cpsPath + '/ancestor::someAncestor')
+ then: 'the query has the right type'
+ result.cpsPathQueryType == expectedCpsPathQueryType
+ 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
+ }
+
+}