StringIO
[sfa.git] / sfa / util / xml.py
index 401cd6a..af151b7 100755 (executable)
@@ -1,10 +1,12 @@
 #!/usr/bin/python 
-from types import StringTypes
 from lxml import etree
-from StringIO import StringIO
 from sfa.util.faults import InvalidXML
 from sfa.rspecs.elements.element import Element
 
+from sfa.util.py23 import StringType
+from sfa.util.py23 import StringIO
+
+# helper functions to help build xpaths
 class XpathFilter:
     @staticmethod
 
@@ -19,7 +21,8 @@ class XpathFilter:
         return xpath
 
     @staticmethod
-    def xpath(filter={}):
+    def xpath(filter=None):
+        if filter is None: filter={}
         xpath = ""
         if filter:
             filter_list = []
@@ -38,21 +41,32 @@ class XpathFilter:
                 xpath = '[' + xpath + ']'
         return xpath
 
+# a wrapper class around lxml.etree._Element
+# the reason why we need this one is because of the limitations
+# we've found in xpath to address documents with multiple namespaces defined
+# in a nutshell, we deal with xml documents that have
+# a default namespace defined (xmlns="http://default.com/") and specific prefixes defined
+# (xmlns:foo="http://foo.com")
+# according to the documentation instead of writing
+# element.xpath ( "//node/foo:subnode" ) 
+# we'd then need to write xpaths like
+# element.xpath ( "//{http://default.com/}node/{http://foo.com}subnode" ) 
+# which is a real pain..
+# So just so we can keep some reasonable programming style we need to manage the
+# namespace map that goes with the _Element (its internal .nsmap being unmutable)
+
 class XmlElement:
     def __init__(self, element, namespaces):
         self.element = element
-        self.tag = element.tag 
-        self.text = element.text
-        self.attrib = element.attrib
         self.namespaces = namespaces
         
-
+    # redefine as few methods as possible
     def xpath(self, xpath, namespaces=None):
         if not namespaces:
             namespaces = self.namespaces 
         elems = self.element.xpath(xpath, namespaces=namespaces)
         return [XmlElement(elem, namespaces) for elem in elems]
-    
+
     def add_element(self, tagname, **kwds):
         element = etree.SubElement(self.element, tagname, **kwds)
         return XmlElement(element, self.namespaces)
@@ -66,11 +80,12 @@ class XmlElement:
     def getparent(self):
         return XmlElement(self.element.getparent(), self.namespaces)
 
-    def get_instance(self, instance_class=None, fields=[]):
+    def get_instance(self, instance_class=None, fields=None):
         """
         Returns an instance (dict) of this xml element. The instance
-        holds a reference this xml element.   
+        holds a reference to this xml element.   
         """
+        if fields is None: fields=[]
         if not instance_class:
             instance_class = Element
         if not fields and hasattr(instance_class, 'fields'):
@@ -85,11 +100,12 @@ class XmlElement:
                    instance[field] = self.attrib[field]  
         return instance             
 
-    def add_instance(self, name, instance, fields=[]):
+    def add_instance(self, name, instance, fields=None):
         """
         Adds the specifed instance(s) as a child element of this xml 
         element. 
         """
+        if fields is None: fields=[]
         if not fields and hasattr(instance, 'keys'):
             fields = instance.keys()
         elem = self.add_element(name)
@@ -138,7 +154,7 @@ class XmlElement:
     # are redirected on self.element
     def __getattr__ (self, name):
         if not hasattr(self.element, name):
-            raise AttributeError, name
+            raise AttributeError(name)
         return getattr(self.element, name)
 
 class XML:
@@ -167,7 +183,7 @@ class XML:
             # 'rspec' file doesnt exist. 'rspec' is proably an xml string
             try:
                 tree = etree.parse(StringIO(xml), parser)
-            except Exception, e:
+            except Exception as e:
                 raise InvalidXML(str(e))
         root = tree.getroot()
         self.namespaces = dict(root.nsmap)
@@ -183,11 +199,12 @@ class XML:
             self.namespaces['default'] = 'default' 
 
         self.root = XmlElement(root, self.namespaces)
-        # set schema 
+        # set schema
         for key in self.root.attrib.keys():
             if key.endswith('schemaLocation'):
-                # schema location should be at the end of the list
-                schema_parts  = self.root.attrib[key].split(' ')
+                # schemaLocation should be at the end of the list.
+                # Use list comprehension to filter out empty strings 
+                schema_parts  = [x for x in self.root.attrib[key].split(' ') if x]
                 self.schema = schema_parts[1]    
                 namespace, schema  = schema_parts[0], schema_parts[1]
                 break
@@ -196,7 +213,7 @@ class XML:
         if element is None: 
             if self.root is None:
                 self.parse_xml('<%s/>' % root_tag_name)
-            element = self.root
+            element = self.root.element
 
         if 'text' in d:
             text = d.pop('text')
@@ -212,9 +229,9 @@ class XML:
                         self.parse_dict(val, key, child_element)
                     elif isinstance(val, basestring):
                         child_element = etree.SubElement(element, key).text = val
-                        
+
             elif isinstance(value, int):
-                d[key] = unicode(d[key])  
+                d[key] = unicode(d[key])
             elif value is None:
                 d.pop(key)
 
@@ -223,7 +240,7 @@ class XML:
         d=d.copy()
         # looks like iteritems won't stand side-effects
         for k in d.keys():
-            if not isinstance(d[k],StringTypes):
+            if not isinstance(d[k], StringType):
                 del d[k]
 
         element.attrib.update(d)
@@ -245,16 +262,14 @@ class XML:
             namespaces = self.namespaces
         return self.root.xpath(xpath, namespaces=namespaces)
 
-    def set(self, key, value, element=None):
-        if not element:
-            element = self.root 
-        return element.set(key, value)
+    def set(self, key, value):
+        return self.root.set(key, value)
 
     def remove_attribute(self, name, element=None):
         if not element:
             element = self.root
         element.remove_attribute(name) 
-        
+
     def add_element(self, *args, **kwds):
         """
         Wrapper around etree.SubElement(). Adds an element to