[CalendarServer-changes] [14018] CalDAVTester/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Sep 29 08:51:07 PDT 2014


Revision: 14018
          http://trac.calendarserver.org//changeset/14018
Author:   cdaboo at apple.com
Date:     2014-09-29 08:51:07 -0700 (Mon, 29 Sep 2014)
Log Message:
-----------
Add more sophisticated xpath testing to allow tests for sibling elements that match specific values.

Modified Paths:
--------------
    CalDAVTester/trunk/README.txt
    CalDAVTester/trunk/verifiers/xmlElementMatch.py

Modified: CalDAVTester/trunk/README.txt
===================================================================
--- CalDAVTester/trunk/README.txt	2014-09-29 09:29:56 UTC (rev 14017)
+++ CalDAVTester/trunk/README.txt	2014-09-29 15:51:07 UTC (rev 14018)
@@ -830,6 +830,12 @@
 	[json] - node contains valid JSON data.
 	[icalendar] - node contains valid iCalendare data.
 	
+	Each path segment can now have its own test and "../" can be used to move up to
+	the parent. This allows testing for an element matching specific content plus
+	its sibling matching other specific content. e.g., "/{D}A/{D}B[=b]/../{D}C[=c]
+	which checks for an element {D}A with two child elements {D}B and {D}C each
+	with a specific value.
+	
 	Argument: 'parent'
 		ElementTree style path for an XML element to use as the root for any
 		subsequent "exists" or "notexists" tests. This is useful for targeting

Modified: CalDAVTester/trunk/verifiers/xmlElementMatch.py
===================================================================
--- CalDAVTester/trunk/verifiers/xmlElementMatch.py	2014-09-29 09:29:56 UTC (rev 14017)
+++ CalDAVTester/trunk/verifiers/xmlElementMatch.py	2014-09-29 15:51:07 UTC (rev 14018)
@@ -67,12 +67,12 @@
         resulttxt = ""
         for path in exists:
 
-            matched, txt = self.matchPath(root, path)
+            matched, txt = self.matchNode(root, path)
             result &= matched
             resulttxt += txt
 
         for path in notexists:
-            matched, _ignore_txt = self.matchPath(root, path)
+            matched, _ignore_txt = self.matchNode(root, path)
             if matched:
                 resulttxt += "        Items returned in XML for %s\n" % (path,)
                 result = False
@@ -153,26 +153,93 @@
         return results
 
 
-    def matchPath(self, root, path):
+    @classmethod
+    def testNode(cls, node, node_path, test):
+        result = None
+        if test[0] == '@':
+            if '=' in test:
+                attr, value = test[1:].split('=')
+                value = value[1:-1]
+            else:
+                attr = test[1:]
+                value = None
+            if attr not in node.keys():
+                result = "        Missing attribute returned in XML for %s\n" % (node_path,)
+            if value is not None and node.get(attr) != value:
+                result = "        Incorrect attribute value returned in XML for %s\n" % (node_path,)
+        elif test[0] == '=':
+            if node.text != test[1:]:
+                result = "        Incorrect value returned in XML for %s\n" % (node_path,)
+        elif test[0] == '!':
+            if node.text == test[1:]:
+                result = "        Incorrect value returned in XML for %s\n" % (node_path,)
+        elif test[0] == '*':
+            if node.text is None or node.text.find(test[1:]) == -1:
+                result = "        Incorrect value returned in XML for %s\n" % (node_path,)
+        elif test[0] == '$':
+            if node.text is None or node.text.find(test[1:]) != -1:
+                result = "        Incorrect value returned in XML for %s\n" % (node_path,)
+        elif test[0] == '+':
+            if node.text is None or not node.text.startswith(test[1:]):
+                result = "        Incorrect value returned in XML for %s\n" % (node_path,)
+        elif test[0] == '^':
+            if "=" in test:
+                element, value = test[1:].split("=", 1)
+            else:
+                element = test[1:]
+                value = None
+            for child in node.getchildren():
+                if child.tag == element and (value is None or child.text == value):
+                    break
+            else:
+                result = "        Missing child returned in XML for %s\n" % (node_path,)
 
+        # Try to parse as iCalendar
+        elif test == 'icalendar':
+            try:
+                Calendar.parseText(node.text)
+            except:
+                result = "        Incorrect value returned in iCalendar for %s\n" % (node_path,)
+
+        # Try to parse as JSON
+        elif test == 'json':
+            try:
+                json.loads(node.text)
+            except:
+                result = "        Incorrect value returned in XML for %s\n" % (node_path,)
+        return result
+
+
+    @classmethod
+    def matchNode(cls, root, xpath, parent_map=None, title=None):
+
+        if title is None:
+            title = xpath
         result = True
         resulttxt = ""
 
-        if '[' in path:
-            actual_path, tests = path.split('[', 1)
+        # Find the first test in the xpath
+        if '[' in xpath:
+            actual_xpath, tests = xpath.split('[', 1)
         else:
-            actual_path = path
+            actual_xpath = xpath
             tests = None
 
-        # Handle absolute root element
-        if actual_path[0] == '/':
-            actual_path = actual_path[1:]
-        r = re.search("(\{[^\}]+\}[^/]+)(.*)", actual_path)
+        if parent_map is None:
+            parent_map = dict((c, p) for p in root.getiterator() for c in p)
+
+        # Handle parents
+        if actual_xpath.startswith("../"):
+            root = parent_map[root]
+            actual_xpath = "./" + actual_xpath[3:]
+
+        # Handle absolute root element and find all matching nodes
+        r = re.search("(/\{[^\}]+\}[^/]+|\.)(.*)", actual_xpath)
         if r.group(2):
             root_path = r.group(1)
             child_path = r.group(2)[1:]
-            if root.tag != root_path:
-                resulttxt += "        Items not returned in XML for %s\n" % (path,)
+            if root_path != "." and root.tag != root_path[1:]:
+                resulttxt += "        Items not returned in XML for %s\n" % (title,)
                 result = False
                 return result, resulttxt
             nodes = root.findall(child_path)
@@ -180,76 +247,58 @@
             nodes = (root,)
 
         if len(nodes) == 0:
-            resulttxt += "        Items not returned in XML for %s\n" % (path,)
+            resulttxt += "        Items not returned in XML for %s\n" % (title,)
             result = False
             return result, resulttxt
 
+
         if tests:
-            tests = [item[:-1] for item in tests.split('[')]
-            for test in tests:
+            # Split the tests into tests plus additional path
+            pos = tests.find("]/")
+            if pos != -1:
+                node_tests = tests[:pos + 1]
+                next_path = tests[pos + 1:]
+            else:
+                node_tests = tests
+                next_path = None
+
+            node_tests = [item[:-1] for item in node_tests.split('[')]
+            for test in node_tests:
                 for node in nodes:
+                    testresult = cls.testNode(node, title, test)
+                    if testresult is None:
+                        if next_path:
+                            next_result, testresult = cls.matchNode(node, next_path[1:], parent_map, title)
+                            if next_result:
+                                break
+                        else:
+                            break
 
-                    def _doTest():
-                        result = None
-                        if test[0] == '@':
-                            if '=' in test:
-                                attr, value = test[1:].split('=')
-                                value = value[1:-1]
-                            else:
-                                attr = test[1:]
-                                value = None
-                            if attr not in node.keys():
-                                result = "        Missing attribute returned in XML for %s\n" % (path,)
-                            if value is not None and node.get(attr) != value:
-                                result = "        Incorrect attribute value returned in XML for %s\n" % (path,)
-                        elif test[0] == '=':
-                            if node.text != test[1:]:
-                                result = "        Incorrect value returned in XML for %s\n" % (path,)
-                        elif test[0] == '!':
-                            if node.text == test[1:]:
-                                result = "        Incorrect value returned in XML for %s\n" % (path,)
-                        elif test[0] == '*':
-                            if node.text is None or node.text.find(test[1:]) == -1:
-                                result = "        Incorrect value returned in XML for %s\n" % (path,)
-                        elif test[0] == '$':
-                            if node.text is None or node.text.find(test[1:]) != -1:
-                                result = "        Incorrect value returned in XML for %s\n" % (path,)
-                        elif test[0] == '+':
-                            if node.text is None or not node.text.startswith(test[1:]):
-                                result = "        Incorrect value returned in XML for %s\n" % (path,)
-                        elif test[0] == '^':
-                            if "=" in test:
-                                element, value = test[1:].split("=", 1)
-                            else:
-                                element = test[1:]
-                                value = None
-                            for child in node.getchildren():
-                                if child.tag == element and (value is None or child.text == value):
-                                    break
-                            else:
-                                result = "        Missing child returned in XML for %s\n" % (path,)
-
-                        # Try to parse as iCalendar
-                        elif test == 'icalendar':
-                            try:
-                                Calendar.parseText(node.text)
-                            except:
-                                result = "        Incorrect value returned in iCalendar for %s\n" % (path,)
-
-                        # Try to parse as JSON
-                        elif test == 'json':
-                            try:
-                                json.loads(node.text)
-                            except:
-                                result = "        Incorrect value returned in XML for %s\n" % (path,)
-                        return result
-
-                    testresult = _doTest()
-                    if testresult is None:
-                        break
-                if testresult is not None:
+                if testresult:
                     resulttxt += testresult
                     result = False
                     break
 
         return result, resulttxt
+
+
+# Tests
+if __name__ == '__main__':
+    xmldata = """
+<D:test xmlns:D="DAV:">
+    <D:a>A</D:a>
+    <D:b>
+        <D:c>C</D:c>
+        <D:d>D</D:d>
+    </D:b>
+    <D:b>
+        <D:c>C</D:c>
+        <D:d>F</D:d>
+    </D:b>
+</D:test>
+"""
+
+    node = ElementTree(file=StringIO.StringIO(xmldata)).getroot()
+
+    assert Verifier.matchNode(node, "/{DAV:}test/{DAV:}b/{DAV:}c[=C]/../{DAV:}d[=D]")[0]
+    assert not Verifier.matchNode(node, "/{DAV:}test/{DAV:}b/{DAV:}c[=C]/../{DAV:}d[=E]")[0]
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140929/063ed9c7/attachment-0001.html>


More information about the calendarserver-changes mailing list