diff -r 000000000000 -r 2e8eeb919028 configurationengine/source/cone/public/_etree_wrapper.py --- /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)