[Cps Path Parser] Introduce Attribute axis 87/139587/3
authordanielhanrahan <daniel.hanrahan@est.tech>
Wed, 27 Nov 2024 15:38:41 +0000 (15:38 +0000)
committerdanielhanrahan <daniel.hanrahan@est.tech>
Mon, 2 Dec 2024 13:30:15 +0000 (13:30 +0000)
Add grammar and tests for attribute-axis to match cps paths like:

  //books/@title

which should return the titles of all books (a subsequent patch will
implement the logic). The syntax is compatible with XPath standard.

Issue-ID: CPS-2416
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: I25164b23670147c504f0f0f6c0cc8ff15997f2a3

cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy

index 74b99fe..bb6bfc3 100644 (file)
 
 grammar CpsPath ;
 
-cpsPath : ( prefix | descendant ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? EOF ;
+cpsPath : ( prefix | descendant ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? attributeAxis? EOF ;
 
 slash : SLASH ;
 
+attributeAxis : SLASH AT leafName ;
+
 ancestorAxis : KW_ANCESTOR_AXIS_PREFIX ancestorPath ;
 
 ancestorPath : yangElement ( slash yangElement)* ;
index b67d708..d0deb7d 100644 (file)
@@ -130,6 +130,13 @@ public class CpsPathBuilder extends CpsPathBaseListener {
                 normalizedXpathBuilder.substring(startIndexOfAncestorSchemaNodeIdentifier));
     }
 
+    @Override
+    public void exitAttributeAxis(final CpsPathParser.AttributeAxisContext ctx) {
+        final String attributeName = ctx.leafName().getText();
+        normalizedXpathBuilder.append("/@").append(attributeName);
+        cpsPathQuery.setAttributeAxisAttributeName(attributeName);
+    }
+
     @Override
     public void exitTextFunctionCondition(final TextFunctionConditionContext ctx) {
         cpsPathQuery.setTextFunctionConditionLeafName(ctx.leafName().getText());
index 03602b6..3612ec5 100644 (file)
@@ -40,6 +40,7 @@ public class CpsPathQuery {
     private String descendantName;
     private List<LeafCondition> leafConditions;
     private String ancestorSchemaNodeIdentifier = "";
+    private String attributeAxisAttributeName = "";
     private String textFunctionConditionLeafName;
     private String textFunctionConditionValue;
     private List<String> booleanOperators;
@@ -65,6 +66,15 @@ public class CpsPathQuery {
         return !(ancestorSchemaNodeIdentifier.isEmpty());
     }
 
+    /**
+     * Has attribute axis been included in cpsPath.
+     *
+     * @return boolean value.
+     */
+    public boolean hasAttributeAxis() {
+        return !(attributeAxisAttributeName.isEmpty());
+    }
+
     /**
      * Have leaf value conditions been included in cpsPath.
      *
index a1bf289..b551080 100644 (file)
@@ -34,6 +34,7 @@ class CpsPathQuerySpec extends Specification {
         then: 'the query has the correct default properties'
             assert result.cpsPathPrefixType == ABSOLUTE
             assert result.hasAncestorAxis() == false
+            assert result.hasAttributeAxis() == false
             assert result.hasLeafConditions() == false
             assert result.hasTextFunctionCondition() == false
             assert result.hasContainsFunctionCondition() == false
@@ -107,6 +108,7 @@ class CpsPathQuerySpec extends Specification {
             'parent & child with multiple OR  operators'                  | '/parent/child[@key1=1 or @key2="abc" or @key="xyz"]/child2'   || "/parent/child[@key1='1' or @key2='abc' or @key='xyz']/child2"
             'parent & child with multiple AND/OR combination'             | '/parent/child[@key1=1 and @key2="abc" or @key="xyz"]/child2'  || "/parent/child[@key1='1' and @key2='abc' or @key='xyz']/child2"
             'parent & child with multiple OR/AND combination'             | '/parent/child[@key1=1 or @key2="abc" and @key="xyz"]/child2'  || "/parent/child[@key1='1' or @key2='abc' and @key='xyz']/child2"
+            'attribute axis'                                              | '/parent/child/@key'                                           || '/parent/child/@key'
     }
 
     def 'Parse xpath to form the Normalized xpath containing #scenario.'() {
@@ -254,4 +256,20 @@ class CpsPathQuerySpec extends Specification {
             '/test[@name2="value2" and @name1="value1"]' || 'name2'               | 'name1'
     }
 
+    def 'Cps Path using attribute axis.'() {
+        when: 'the cps path is parsed'
+            def result = CpsPathQuery.createFrom(cpsPath)
+        then: 'the query has the correct properties for attribute axis'
+            assert result.hasAttributeAxis() == expectAttributeAxis
+            assert result.attributeAxisAttributeName == expectedAttributeName
+        where: 'the following data is used'
+            cpsPath                      || expectAttributeAxis | expectedAttributeName
+            '/test'                      || false               | ''
+            '/test/@id'                  || true                | 'id'
+            '/test[@id="id"]'            || false               | ''
+            '/test[@id="id"]/@id'        || true                | 'id'
+            '//test/ancestor::root'      || false               | ''
+            '//test/ancestor::root/@xyz' || true                | 'xyz'
+    }
+
 }