added XPathFilter, RSpecElement classes. added find(), find_elements(), get_element_a...
authorTony Mack <tmack@paris.CS.Princeton.EDU>
Thu, 1 Sep 2011 17:19:21 +0000 (13:19 -0400)
committerTony Mack <tmack@paris.CS.Princeton.EDU>
Thu, 1 Sep 2011 17:19:21 +0000 (13:19 -0400)
sfa/rspecs/rspec.py

index f2fa662..1a0c3ad 100755 (executable)
@@ -4,7 +4,40 @@ from StringIO import StringIO
 from datetime import datetime, timedelta
 from sfa.util.xrn import *
 from sfa.util.plxrn import hostname_to_urn
-from sfa.util.faults import SfaNotImplemented, InvalidRSpec
+from sfa.util.enumeration import Enum
+from sfa.util.faults import SfaNotImplemented, InvalidRSpec, InvalidRSpecElement
+
+
+class XpathFilter:
+    @staticmethod
+    def xpath(filter={}):
+        xpath = ""
+        if filter:
+            filter_list = []
+            for (key, value) in filter.items():
+                if key == 'text':
+                    key = 'text()'
+                else:
+                    key = '@'+key
+                if isinstance(value, str):
+                    filter_list.append('%s="%s"' % (key, value))
+                elif isinstance(value, list):
+                    filter_list.append('contains("%s", %s)' % (' '.join(map(str, value)), key))
+            if filter_list:
+                xpath = ' and '.join(filter_list)
+                xpath = '[' + xpath + ']'
+        return xpath
+
+# recognized top level rspec elements 
+RSpecElements = Enum('NETWORK', 'NODE', 'SLIVER', 'INTERFACE', 'LINK', 'VLINK')
+
+class RSpecElement:
+    def __init__(self, element_type, name, path):
+        if not element_type in RSpecElements:
+            raise InvalidRSpecElement(element_type)
+        self.type = element_type
+        self.name = name
+        self.path = path     
 
 class RSpec:
     header = '<?xml version="1.0"?>\n'
@@ -14,10 +47,11 @@ class RSpec:
     version = None
     namespaces = None
     user_options = {}
-  
     def __init__(self, rspec="", namespaces={}, type=None, user_options={}):
         self.type = type
         self.user_options = user_options
+        self.elements = {}
         if rspec:
             self.parse_rspec(rspec, namespaces)
         else:
@@ -53,8 +87,32 @@ class RSpec:
         if namespaces:
            self.namespaces = namespaces
 
+    def validate(self, schema):
+        """
+        Validate against rng schema
+        """
+
+        relaxng_doc = etree.parse(schema)
+        relaxng = etree.RelaxNG(relaxng_doc)
+        if not relaxng(self.xml):
+            error = relaxng.error_log.last_error
+            message = "%s (line %s)" % (error.message, error.line)
+            raise InvalidRSpec(message)
+        return True
+
     def xpath(self, xpath):
-        return this.xml.xpath(xpath, namespaces=self.namespaces)
+        return self.xml.xpath(xpath, namespaces=self.namespaces)
+
+    def register_element_type(self, element_type, element_name, element_path):
+        if element_type not in RSpecElements:
+            raise InvalidRSpecElement(element_type, extra="no such element type: %s. Must specify a valid RSpecElement" % element_type)
+        self.elements[element_type] = RSpecElement(element_type, element_name, element_path)
+
+    def get_element_type(self, element_type):
+        if element_type not in self.elements:
+            msg = "ElementType %s not registerd for this rspec" % element_type
+            raise InvalidRSpecElement(element_type, extra=msg)
+        return self.elements[element_type]
 
     def add_attribute(self, elem, name, value):
         """
@@ -105,24 +163,43 @@ class RSpec:
         for element in elements:
             parent = element.getparent()
             parent.remove(element)
-         
 
-    def merge(self, in_rspec):
-        pass
+    def get_element_attributes(self, elem=None, depth=0):
+        if elem == None:
+            elem = self.root_node
+        attrs = dict(elem.attrib)
+        attrs['text'] = str(elem.text).strip()
+        if isinstance(depth, int) and depth > 0:
+            for child_elem in list(elem):
+                key = str(child_elem.tag)
+                if key not in attrs:
+                    attrs[key] = [self.get_element_attributes(child_elem, depth-1)]
+                else:
+                    attrs[key].append(self.get_element_attributes(child_elem, depth-1))
+        else:
+            attrs['child_nodes'] = list(elem)
+        return attrs
 
-    def validate(self, schema):
+    def find(self, element_type, filter={}, depth=0):
+        elements = [self.get_element_attributes(element, depth=depth) for element in \
+                    self.find_elements(element_type, filter)]
+        return elements
+
+    def find_elements(self, element_type, filter={}):
         """
-        Validate against rng schema
+        search for a registered element
         """
-        
-        relaxng_doc = etree.parse(schema)
-        relaxng = etree.RelaxNG(relaxng_doc)
-        if not relaxng(self.xml):
-            error = relaxng.error_log.last_error
-            message = "%s (line %s)" % (error.message, error.line)
-            raise InvalidRSpec(message)
-        return True
-        
+        if element_type not in self.elements:
+            msg = "Unable to search for element %s in rspec, expath expression not found." % \
+                   element_type
+            raise InvalidRSpecElement(element_type, extra=msg)
+        rspec_element = self.get_element_type(element_type)
+        xpath = rspec_element.path + XpathFilter.xpath(filter)
+        return self.xpath(xpath)
+
+    def merge(self, in_rspec):
+        pass
+
     def cleanup(self):
         """
         Optional method which inheriting classes can choose to implent. 
@@ -160,5 +237,10 @@ class RSpec:
         f.close()
  
 if __name__ == '__main__':
-    rspec = RSpec()
+    rspec = RSpec('/tmp/resources.rspec')
     print rspec
+    #rspec.register_element_type(RSpecElements.NETWORK, 'network', '//network')
+    #rspec.register_element_type(RSpecElements.NODE, 'node', '//node')
+    #print rspec.find(RSpecElements.NODE)[0]
+    #print rspec.find(RSpecElements.NODE, depth=1)[0]
+