configurationengine/source/cone/public/_etree_wrapper.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configurationengine/source/cone/public/_etree_wrapper.py	Thu Mar 11 17:04:37 2010 +0200
@@ -0,0 +1,245 @@
+#
+# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description: 
+# 
+
+from xml.parsers import expat
+
+# Import ElementTree (should always be available)
+try:
+    from elementtree import ElementTree
+except ImportError:
+    from xml.etree import ElementTree
+
+import exceptions
+
+
+class ElementTreeBackendWrapperBase(object):
+    def get_module(self):
+        raise NotImplementedError()
+    
+    def get_lineno(self, element):
+        raise NotImplementedError()
+
+class ElementTreeBackendWrapper(ElementTreeBackendWrapperBase):
+    
+    class CustomTreeBuilder(ElementTree.TreeBuilder):
+        """
+        Custom TreeBuilder for ElementTree that records line numbers
+        of the elements.
+        """
+        def start(self, tag, attrs):
+            elem = ElementTree.TreeBuilder.start(self, tag, attrs)
+            lineno = self._xmltreebuilder._parser.CurrentLineNumber
+            #print "Tag: %s, line: %r" % (tag, lineno)
+            elem.sourceline = lineno
+            return elem
+    
+    def get_module(self):
+        return ElementTree
+    
+    def fromstring(self, text):
+        try:
+            treebuilder = self.CustomTreeBuilder()
+            parser = ElementTree.XMLTreeBuilder(target=treebuilder)
+            treebuilder._xmltreebuilder = parser
+            parser.feed(text)
+            return parser.close()
+        except expat.ExpatError, e:
+            raise exceptions.XmlParseError(
+                "XML parse error on line %d: %s" % (e.lineno, e),
+                e.lineno, str(e))
+    
+    def tostring(self, etree, encoding=None):
+        return ElementTree.tostring(etree, encoding)
+    
+    def get_lineno(self, element):
+        return element.sourceline
+
+
+class CElementTreeBackendWrapper(ElementTreeBackendWrapperBase):
+    def __init__(self):
+        try:
+            from cElementTree import cElementTree
+        except ImportError:
+            from xml.etree import cElementTree
+        
+        self.cElementTree = cElementTree
+    
+    def get_module(self):
+        return self.cElementTree
+    
+    def fromstring(self, text):
+        try:
+            return self.cElementTree.fromstring(text)
+        except SyntaxError, e:
+            # cElementTree raises a SyntaxError, but does not set
+            # its lineno attribute, so look for the line number
+            # in the exception text
+            import re
+            match = re.search(r'line (\d+)\, column \d+$', str(e))
+            if match:   lineno = int(match.group(1))
+            else:       lineno = None
+            
+            raise exceptions.XmlParseError(
+                "XML parse error on line %s: %s" % (lineno, e),
+                lineno, str(e))
+    
+    def tostring(self, etree, encoding=None):
+        return self.cElementTree.tostring(etree, encoding)
+    
+    def get_lineno(self, element):
+        # cElementTree does not support line numbers
+        return None
+
+
+class LxmlBackendWrapper(ElementTreeBackendWrapperBase):
+    
+    def __init__(self):
+        import lxml.etree
+        self.lxml = lxml
+    
+    def get_module(self):
+        return self.lxml.etree
+    
+    def fromstring(self, text):
+        try:
+            elem = self.lxml.etree.fromstring(text)
+            
+            # lxml parses also comments, but ConE does not expect those,
+            # so remove them to prevent any errors on that account
+            def remove_comments(elem):
+                # Find the comments under this element
+                comments = []
+                for x in elem:
+                    if isinstance(x, self.lxml.etree._Comment):
+                        comments.append(x)
+                
+                # Remove them
+                for c in comments:
+                    elem.remove(c)
+                
+                # Recurse to sub-elements
+                for subelem in elem:
+                    remove_comments(subelem)
+            
+            remove_comments(elem)
+            
+            return elem
+        except self.lxml.etree.XMLSyntaxError, e:
+            raise exceptions.XmlParseError(
+                "XML parse error on line %d: %s" % (e.position[0], e),
+                e.position[0], str(e))
+    
+    def tostring(self, etree, encoding=None):
+        return self.lxml.etree.tostring(etree, encoding=encoding)
+    
+    def get_lineno(self, element):
+        return element.sourceline
+
+# ============================================================================
+#
+# ============================================================================
+
+class ElementTreeWrapper(object):
+    """
+    ElementTree wrapper class for providing a unified interface to different
+    ElementTree implementations.
+    
+    Currently supported are the pure Python ElementTree implementation,
+    cElementTree and lxml.etree
+    """
+    BACKEND_ELEMENT_TREE     = 'ElementTree'
+    BACKEND_C_ELEMENT_TREE   = 'cElementTree'
+    BACKEND_LXML             = 'lxml'
+    
+    # Import order for the default back-end. The list is traversed
+    # top-down and the first back-end whose importing is successful is
+    # used as the default back-end
+    DEFAULT_BACKEND_IMPORT_ORDER = [BACKEND_C_ELEMENT_TREE,
+                                    BACKEND_ELEMENT_TREE]
+    
+    _backend_mapping = {BACKEND_ELEMENT_TREE:     ElementTreeBackendWrapper,
+                        BACKEND_C_ELEMENT_TREE:   CElementTreeBackendWrapper,
+                        BACKEND_LXML:             LxmlBackendWrapper}
+    
+    _backend_id = None
+    _backend_wrapper = None
+
+    def get_backend_id(self):
+        """
+        Return the ID of the currently used ElementTree back-end.
+        """
+        # Make sure that the default back-end is set, so _backend_id
+        # will not be None
+        self._get_backend()
+        assert self._backend_id is not None
+        return self._backend_id
+    
+    def set_backend_id(self, backend_id):
+        """
+        Set the used ElementTree back-end by back-end ID.
+        """
+        if backend_id not in self._backend_mapping:
+            raise ValueError("Invalid ElementTree back-end ID: %r" % backend_id)
+        
+        if backend_id == self._backend_id:
+            return
+        
+        backend_wrapper_class = self._backend_mapping[backend_id]
+        self._backend_wrapper = backend_wrapper_class()
+        self._backend_id = backend_id
+    
+    def _get_backend(self):
+        """
+        Return the currently set ElementTree back-end wrapper object.
+        """
+        if self._backend_wrapper is None:
+            # Back-end not set, so set the default back-end.
+            # The default is the C version of ElementTree, but if that
+            # is not available, the pure Python version is used
+            for backend_id in self.DEFAULT_BACKEND_IMPORT_ORDER:
+                try:
+                    self.set_backend_id(backend_id)
+                except ImportError:
+                    pass
+            
+            if self._backend_wrapper is None:
+                raise RuntimeError("Failed to set any ElementTree backend! Tried these: %r" % self.DEFAULT_BACKEND_IMPORT_ORDER)
+        
+        return self._backend_wrapper
+    
+    def get_lineno(self, element):
+        """
+        Return the source line number of the given XML element.
+        
+        Note that for the cElementTree parser this will always return
+        None, since that parser does not support line numbers.
+        """
+        return self._get_backend().get_lineno(element)
+    
+    def __getattribute__(self, attrname):
+        try:
+            # Try to get the attribute from this object (the top-level wrapper)
+            return object.__getattribute__(self, attrname)
+        except AttributeError:
+            # If not overridden here, try to get it from the back-end wrapper
+            backend = self._get_backend()
+            try:
+                return getattr(backend, attrname)
+            except AttributeError:
+                # Last resort: try to get it from the module
+                # the back-end wrapper wraps
+                backend_module = backend.get_module()
+                return getattr(backend_module, attrname)