#
# 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()
def get_elem_from_lineno(self, root, lineno):
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)
self.root = parser.close()
return self.root
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):
try:
return element.sourceline
except AttributeError:
return None
def get_elem_from_lineno(self, root, lineno):
for elem in root.getiterator():
if elem.sourceline == lineno:
return elem
return None
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:
self.root = self.cElementTree.fromstring(text)
return self.root
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
def get_elem_from_lineno(self, root, lineno):
# 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:
self.root = self.lxml.etree.fromstring(text)
self.remove_comments(self.root)
return self.root
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 remove_comments(self, elem):
"""
lxml parses also comments, but ConE does not expect those,
so remove them to prevent any errors on that account
"""
# 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:
self.remove_comments(subelem)
def tostring(self, etree, encoding=None):
return self.lxml.etree.tostring(etree, encoding=encoding)
def get_lineno(self, element):
try:
return element.sourceline
except AttributeError:
return None
def get_elem_from_lineno(self, root, lineno):
for elem in root.getiterator():
if elem.sourceline == lineno:
return elem
return None
# ============================================================================
#
# ============================================================================
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_LXML,
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 get_elem_from_lineno(self, root, lineno):
"""
Return the element from the given line number of the given XML element.
@param root: the root element to search from
@param lineno: the line number to search for
Note that for the cElementTree parser this will always return
None, since that parser does not support line numbers.
"""
return self._get_backend().get_elem_from_lineno(root, lineno)
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)