Index: Lib/test/test_xml_etree.py =================================================================== --- Lib/test/test_xml_etree.py (revision 78865) +++ Lib/test/test_xml_etree.py (working copy) @@ -1,22 +1,42 @@ # xml.etree test. This file contains enough tests to make sure that -# all included components work as they should. For a more extensive -# test suite, see the selftest script in the ElementTree distribution. +# all included components work as they should. +# Large parts are extracted from the upstream test suite. + +# IMPORTANT: the same doctests are run from "test_xml_etree_c" in +# order to ensure consistency between the C implementation and the +# Python implementation. +# +# For this purpose, the module-level "ET" symbol is temporarily +# monkey-patched when running the "test_xml_etree_c" test suite. +# Don't re-import "xml.etree.ElementTree" module in the docstring, +# except if the test is specific to the Python implementation. -import doctest import sys from test import support -SAMPLE_XML = """ +from xml.etree import ElementTree as ET + +SAMPLE_XML = """\
-120 Mz is adequate for an average home user.
This document has been accessed 324387 times.
@@ -353,7 +1343,7 @@ >>> document = xinclude_loader("C3.xml") >>> ElementInclude.include(document, xinclude_loader) - >>> print(serialize(ET, document)) # C3 + >>> print(serialize(document)) # C3The following is the source of the "data.xml" resource:
Example.
+120 Mz is adequate for an average home user.
+# The {@link #ElementTree} class can be used to wrap an element # structure, and convert it from and to XML. ## -import sys, re +import sys +import re +import warnings + + +class _SimpleElementPath: + # emulate pre-1.2 find/findtext/findall behaviour + def find(self, element, tag, namespaces=None): + for elem in element: + if elem.tag == tag: + return elem + return None + def findtext(self, element, tag, default=None, namespaces=None): + elem = self.find(element, tag) + if elem is None: + return default + return elem.text or "" + def iterfind(self, element, tag, namespaces=None): + if tag[:3] == ".//": + for elem in element.iter(tag[3:]): + yield elem + for elem in element: + if elem.tag == tag: + yield elem + def findall(self, element, tag, namespaces=None): + return list(self.iterfind(element, tag, namespaces)) + +try: + from . import ElementPath +except ImportError: + ElementPath = _SimpleElementPath() + +## +# Parser error. This is a subclass of SyntaxError. +#
+# In addition to the exception value, an exception instance contains a +# specific exception code in the code attribute, and the line and +# column of the error in the position attribute. + +class ParseError(SyntaxError): + pass -from . import ElementPath +# -------------------------------------------------------------------- -# TODO: add support for custom namespace resolvers/default namespaces -# TODO: add improved support for incremental parsing +## +# Checks if an object appears to be a valid element object. +# +# @param An element instance. +# @return A true value if this is an element object. +# @defreturn flag -VERSION = "1.2.6" +def iselement(element): + # FIXME: not sure about this; might be a better idea to look + # for tag/attrib/text attributes + return isinstance(element, Element) or hasattr(element, "tag") ## -# Internal element class. This class defines the Element interface, -# and provides a reference implementation of this interface. +# Element class. This class defines the Element interface, and +# provides a reference implementation of this interface. #
-# You should not create instances of this class directly. Use the
-# appropriate factory functions instead, such as {@link #Element}
-# and {@link #SubElement}.
+# The element name, attribute names, and attribute values can be
+# either ASCII strings (ordinary Python strings containing only 7-bit
+# ASCII characters) or Unicode strings.
#
+# @param tag The element name.
+# @param attrib An optional dictionary, containing element attributes.
+# @param **extra Additional attributes, given as keyword arguments.
# @see Element
# @see SubElement
# @see Comment
# @see ProcessingInstruction
-class _ElementInterface:
+class Element:
#
- # If the tree structure is modified during iteration, the result
- # is undefined.
+ # If the tree structure is modified during iteration, new or removed
+ # elements may or may not be included. To get a stable set, use the
+ # list() function on the iterator, and loop over the resulting list.
#
# @param tag What tags to look for (default is to return all elements).
- # @return A list or iterator containing all the matching elements.
- # @defreturn list or iterator
+ # @return An iterator containing all the matching elements.
+ # @defreturn iterator
- def getiterator(self, tag=None):
- nodes = []
+ def iter(self, tag=None):
if tag == "*":
tag = None
if tag is None or self.tag == tag:
- nodes.append(self)
- for node in self._children:
- nodes.extend(node.getiterator(tag))
- return nodes
+ yield self
+ for e in self._children:
+ for e in e.iter(tag):
+ yield e
-# compatibility
-_Element = _ElementInterface
+ # compatibility
+ def getiterator(self, tag=None):
+ # Change for a DeprecationWarning in 1.4
+ warnings.warn(
+ "This method will be removed in future versions. "
+ "Use 'elem.iter()' or 'list(elem.iter())' instead.",
+ PendingDeprecationWarning, stacklevel=2
+ )
+ return list(self.iter(tag))
-##
-# Element factory. This function returns an object implementing the
-# standard Element interface. The exact class or type of that object
-# is implementation dependent, but it will always be compatible with
-# the {@link #_ElementInterface} class in this module.
-#
-# The element name, attribute names, and attribute values can be
-# either 8-bit ASCII strings or Unicode strings.
-#
-# @param tag The element name.
-# @param attrib An optional dictionary, containing element attributes.
-# @param **extra Additional attributes, given as keyword arguments.
-# @return An element instance.
-# @defreturn Element
+ ##
+ # Creates a text iterator. The iterator loops over this element
+ # and all subelements, in document order, and returns all inner
+ # text.
+ #
+ # @return An iterator containing all inner text.
+ # @defreturn iterator
-def Element(tag, attrib={}, **extra):
- attrib = attrib.copy()
- attrib.update(extra)
- return _ElementInterface(tag, attrib)
+ def itertext(self):
+ tag = self.tag
+ if not isinstance(tag, str) and tag is not None:
+ return
+ if self.text:
+ yield self.text
+ for e in self:
+ for s in e.itertext():
+ yield s
+ if e.tail:
+ yield e.tail
+
+# compatibility
+_Element = _ElementInterface = Element
##
# Subelement factory. This function creates an element instance, and
@@ -447,7 +533,8 @@
##
# Comment element factory. This factory function creates a special
-# element that will be serialized as an XML comment.
+# element that will be serialized as an XML comment by the standard
+# serializer.
#
# The comment string can be either an 8-bit ASCII string or a Unicode
# string.
@@ -463,7 +550,8 @@
##
# PI element factory. This factory function creates a special element
-# that will be serialized as an XML processing instruction.
+# that will be serialized as an XML processing instruction by the standard
+# serializer.
#
# @param target A string containing the PI target.
# @param text A string containing the PI contents, if any.
@@ -523,19 +611,21 @@
return self.text != other.text
return self.text != other
+# --------------------------------------------------------------------
+
##
# ElementTree wrapper class. This class represents an entire element
# hierarchy, and adds some extra support for serialization to and from
# standard XML.
#
# @param element Optional root element.
-# @keyparam file Optional file handle or name. If given, the
+# @keyparam file Optional file handle or file name. If given, the
# tree is initialized with the contents of this XML file.
class ElementTree:
def __init__(self, element=None, file=None):
- assert element is None or iselement(element)
+ # assert element is None or iselement(element)
self._root = element # first node
if file:
self.parse(file)
@@ -557,25 +647,27 @@
# @param element An element instance.
def _setroot(self, element):
- assert iselement(element)
+ # assert iselement(element)
self._root = element
##
# Loads an external XML document into this element tree.
#
- # @param source A file name or file object.
- # @param parser An optional parser instance. If not given, the
- # standard {@link XMLTreeBuilder} parser is used.
+ # @param source A file name or file object. If a file object is
+ # given, it only has to implement a read(n) method.
+ # @keyparam parser An optional parser instance. If not given, the
+ # standard {@link XMLParser} parser is used.
# @return The document root element.
# @defreturn Element
+ # @exception ParseError If the parser fails to parse the document.
def parse(self, source, parser=None):
if not hasattr(source, "read"):
source = open(source, "rb")
if not parser:
- parser = XMLTreeBuilder()
+ parser = XMLParser(target=TreeBuilder())
while 1:
- data = source.read(32768)
+ data = source.read(65536)
if not data:
break
parser.feed(data)
@@ -590,23 +682,40 @@
# @return An iterator.
# @defreturn iterator
+ def iter(self, tag=None):
+ # assert self._root is not None
+ return self._root.iter(tag)
+
+ # compatibility
def getiterator(self, tag=None):
- assert self._root is not None
- return self._root.getiterator(tag)
+ # Change for a DeprecationWarning in 1.4
+ warnings.warn(
+ "This method will be removed in future versions. "
+ "Use 'tree.iter()' or 'list(tree.iter())' instead.",
+ PendingDeprecationWarning, stacklevel=2
+ )
+ return list(self.iter(tag))
##
# Finds the first toplevel element with given tag.
# Same as getroot().find(path).
#
# @param path What element to look for.
+ # @keyparam namespaces Optional namespace prefix map.
# @return The first matching element, or None if no element was found.
# @defreturn Element or None
- def find(self, path):
- assert self._root is not None
+ def find(self, path, namespaces=None):
+ # assert self._root is not None
if path[:1] == "/":
path = "." + path
- return self._root.find(path)
+ warnings.warn(
+ "This search is broken in 1.3 and earlier, and will be "
+ "fixed in a future version. If you rely on the current "
+ "behaviour, change it to %r" % path,
+ FutureWarning, stacklevel=2
+ )
+ return self._root.find(path, namespaces)
##
# Finds the element text for the first toplevel element with given
@@ -614,153 +723,353 @@
#
# @param path What toplevel element to look for.
# @param default What to return if the element was not found.
+ # @keyparam namespaces Optional namespace prefix map.
# @return The text content of the first matching element, or the
# default value no element was found. Note that if the element
- # has is found, but has no text content, this method returns an
+ # is found, but has no text content, this method returns an
# empty string.
# @defreturn string
- def findtext(self, path, default=None):
- assert self._root is not None
+ def findtext(self, path, default=None, namespaces=None):
+ # assert self._root is not None
if path[:1] == "/":
path = "." + path
- return self._root.findtext(path, default)
+ warnings.warn(
+ "This search is broken in 1.3 and earlier, and will be "
+ "fixed in a future version. If you rely on the current "
+ "behaviour, change it to %r" % path,
+ FutureWarning, stacklevel=2
+ )
+ return self._root.findtext(path, default, namespaces)
##
# Finds all toplevel elements with the given tag.
# Same as getroot().findall(path).
#
# @param path What element to look for.
+ # @keyparam namespaces Optional namespace prefix map.
# @return A list or iterator containing all matching elements,
# in document order.
# @defreturn list of Element instances
- def findall(self, path):
- assert self._root is not None
+ def findall(self, path, namespaces=None):
+ # assert self._root is not None
if path[:1] == "/":
path = "." + path
- return self._root.findall(path)
+ warnings.warn(
+ "This search is broken in 1.3 and earlier, and will be "
+ "fixed in a future version. If you rely on the current "
+ "behaviour, change it to %r" % path,
+ FutureWarning, stacklevel=2
+ )
+ return self._root.findall(path, namespaces)
+
+ ##
+ # Finds all matching subelements, by tag name or path.
+ # Same as getroot().iterfind(path).
+ #
+ # @param path What element to look for.
+ # @keyparam namespaces Optional namespace prefix map.
+ # @return An iterator or sequence containing all matching elements,
+ # in document order.
+ # @defreturn a generated sequence of Element instances
+
+ def iterfind(self, path, namespaces=None):
+ # assert self._root is not None
+ if path[:1] == "/":
+ path = "." + path
+ warnings.warn(
+ "This search is broken in 1.3 and earlier, and will be "
+ "fixed in a future version. If you rely on the current "
+ "behaviour, change it to %r" % path,
+ FutureWarning, stacklevel=2
+ )
+ return self._root.iterfind(path, namespaces)
##
# Writes the element tree to a file, as XML.
#
+ # @def write(file, **options)
# @param file A file name, or a file object opened for writing.
- # @param encoding Optional output encoding (default is None)
-
- def write(self, file, encoding=None):
- assert self._root is not None
- if not hasattr(file, "write"):
+ # @param **options Options, given as keyword arguments.
+ # @keyparam encoding Optional output encoding (default is None).
+ # @keyparam method Optional output method ("xml", "html", "text" or
+ # "c14n"; default is "xml").
+ # @keyparam xml_declaration Controls if an XML declaration should
+ # be added to the file. Use False for never, True for always,
+ # None for only if not US-ASCII or UTF-8. None is default.
+
+ def write(self, file_or_filename,
+ # keyword arguments
+ encoding=None,
+ xml_declaration=None,
+ default_namespace=None,
+ method=None):
+ # assert self._root is not None
+ if not method:
+ method = "xml"
+ elif method not in _serialize:
+ # FIXME: raise an ImportError for c14n if ElementC14N is missing?
+ raise ValueError("unknown method %r" % method)
+ if hasattr(file_or_filename, "write"):
+ file = file_or_filename
+ else:
if encoding:
- file = open(file, "wb")
+ file = open(file_or_filename, "wb")
else:
- file = open(file, "w")
- if encoding and encoding != "utf-8":
- file.write(_encode("\n" % encoding, encoding))
- self._write(file, self._root, encoding, {})
-
- def _write(self, file, node, encoding, namespaces):
- # write XML to file
- tag = node.tag
- if tag is Comment:
- file.write(_encode("" % node.text, encoding))
- elif tag is ProcessingInstruction:
- file.write(_encode("%s?>" % node.text, encoding))
+ file = open(file_or_filename, "w")
+ if encoding:
+ def write(text):
+ try:
+ return file.write(text.encode(encoding,
+ "xmlcharrefreplace"))
+ except (TypeError, AttributeError):
+ _raise_serialization_error(text)
else:
- items = list(node.items())
- xmlns_items = [] # new namespaces in this scope
- try:
- if isinstance(tag, QName) or tag[:1] == "{":
- tag, xmlns = fixtag(tag, namespaces)
- if xmlns: xmlns_items.append(xmlns)
- except TypeError:
- _raise_serialization_error(tag)
- file.write(_encode("<" + tag, encoding))
- if items or xmlns_items:
- items.sort() # lexical order
- for k, v in items:
- try:
- if isinstance(k, QName) or k[:1] == "{":
- k, xmlns = fixtag(k, namespaces)
- if xmlns: xmlns_items.append(xmlns)
- except TypeError:
- _raise_serialization_error(k)
- try:
- if isinstance(v, QName):
- v, xmlns = fixtag(v, namespaces)
- if xmlns: xmlns_items.append(xmlns)
- except TypeError:
- _raise_serialization_error(v)
- file.write(_encode(" %s=\"%s\"" % (k, _escape_attrib(v)), encoding))
- for k, v in xmlns_items:
- file.write(_encode(" %s=\"%s\"" % (k, _escape_attrib(v)), encoding))
- if node.text or len(node):
- file.write(_encode(">", encoding))
- if node.text:
- file.write(_encode_cdata(node.text, encoding))
- for n in node:
- self._write(file, n, encoding, namespaces)
- file.write(_encode("" + tag + ">", encoding))
+ write = file.write
+ if not encoding:
+ if method == "c14n":
+ encoding = "utf-8"
else:
- file.write(_encode(" />", encoding))
- for k, v in xmlns_items:
- del namespaces[v]
- if node.tail:
- file.write(_encode_cdata(node.tail, encoding))
+ encoding = None
+ elif xml_declaration or (xml_declaration is None and
+ encoding not in ("utf-8", "us-ascii")):
+ if method == "xml":
+ encoding_ = encoding
+ if not encoding:
+ # Retrieve the default encoding for the xml declaration
+ import locale
+ encoding_ = locale.getpreferredencoding()
+ write("\n" % encoding_)
+ if method == "text":
+ _serialize_text(write, self._root)
+ else:
+ qnames, namespaces = _namespaces(self._root, default_namespace)
+ serialize = _serialize[method]
+ serialize(write, self._root, qnames, namespaces)
+ if file_or_filename is not file:
+ file.close()
+
+ def write_c14n(self, file):
+ # lxml.etree compatibility. use output method instead
+ return self.write(file, method="c14n")
# --------------------------------------------------------------------
-# helpers
+# serialization support
-##
-# Checks if an object appears to be a valid element object.
-#
-# @param An element instance.
-# @return A true value if this is an element object.
-# @defreturn flag
+def _namespaces(elem, default_namespace=None):
+ # identify namespaces used in this tree
-def iselement(element):
- # FIXME: not sure about this; might be a better idea to look
- # for tag/attrib/text attributes
- return isinstance(element, _ElementInterface) or hasattr(element, "tag")
+ # maps qnames to *encoded* prefix:local names
+ qnames = {None: None}
-##
-# Writes an element tree or element structure to sys.stdout. This
-# function should be used for debugging only.
-#
-# The exact output format is implementation dependent. In this
-# version, it's written as an ordinary XML file.
-#
-# @param elem An element tree or an individual element.
+ # maps uri:s to prefixes
+ namespaces = {}
+ if default_namespace:
+ namespaces[default_namespace] = ""
-def dump(elem):
- # debugging
- if not isinstance(elem, ElementTree):
- elem = ElementTree(elem)
- elem.write(sys.stdout)
- tail = elem.getroot().tail
- if not tail or tail[-1] != "\n":
- sys.stdout.write("\n")
+ def add_qname(qname):
+ # calculate serialized qname representation
+ try:
+ if qname[:1] == "{":
+ uri, tag = qname[1:].rsplit("}", 1)
+ prefix = namespaces.get(uri)
+ if prefix is None:
+ prefix = _namespace_map.get(uri)
+ if prefix is None:
+ prefix = "ns%d" % len(namespaces)
+ if prefix != "xml":
+ namespaces[uri] = prefix
+ if prefix:
+ qnames[qname] = "%s:%s" % (prefix, tag)
+ else:
+ qnames[qname] = tag # default element
+ else:
+ if default_namespace:
+ # FIXME: can this be handled in XML 1.0?
+ raise ValueError(
+ "cannot use non-qualified names with "
+ "default_namespace option"
+ )
+ qnames[qname] = qname
+ except TypeError:
+ _raise_serialization_error(qname)
-def _encode(s, encoding):
- if encoding:
- return s.encode(encoding)
+ # populate qname and namespaces table
+ try:
+ iterate = elem.iter
+ except AttributeError:
+ iterate = elem.getiterator # cET compatibility
+ for elem in iterate():
+ tag = elem.tag
+ if isinstance(tag, QName) and tag.text not in qnames:
+ add_qname(tag.text)
+ elif isinstance(tag, str):
+ if tag not in qnames:
+ add_qname(tag)
+ elif tag is not None and tag is not Comment and tag is not PI:
+ _raise_serialization_error(tag)
+ for key, value in elem.items():
+ if isinstance(key, QName):
+ key = key.text
+ if key not in qnames:
+ add_qname(key)
+ if isinstance(value, QName) and value.text not in qnames:
+ add_qname(value.text)
+ text = elem.text
+ if isinstance(text, QName) and text.text not in qnames:
+ add_qname(text.text)
+ return qnames, namespaces
+
+def _serialize_xml(write, elem, qnames, namespaces):
+ tag = elem.tag
+ text = elem.text
+ if tag is Comment:
+ write("" % text)
+ elif tag is ProcessingInstruction:
+ write("%s?>" % text)
else:
- return s
-
-_escape = re.compile(r"[&<>\"\u0080-\uffff]+")
-
-_escape_map = {
- "&": "&",
- "<": "<",
- ">": ">",
- '"': """,
+ tag = qnames[tag]
+ if tag is None:
+ if text:
+ write(_escape_cdata(text))
+ for e in elem:
+ _serialize_xml(write, e, qnames, None)
+ else:
+ write("<" + tag)
+ items = list(elem.items())
+ if items or namespaces:
+ if namespaces:
+ for v, k in sorted(namespaces.items(),
+ key=lambda x: x[1]): # sort on prefix
+ if k:
+ k = ":" + k
+ write(" xmlns%s=\"%s\"" % (
+ k,
+ _escape_attrib(v)
+ ))
+ for k, v in sorted(items): # lexical order
+ if isinstance(k, QName):
+ k = k.text
+ if isinstance(v, QName):
+ v = qnames[v.text]
+ else:
+ v = _escape_attrib(v)
+ write(" %s=\"%s\"" % (qnames[k], v))
+ if text or len(elem):
+ write(">")
+ if text:
+ write(_escape_cdata(text))
+ for e in elem:
+ _serialize_xml(write, e, qnames, None)
+ write("" + tag + ">")
+ else:
+ write(" />")
+ if elem.tail:
+ write(_escape_cdata(elem.tail))
+
+HTML_EMPTY = ("area", "base", "basefont", "br", "col", "frame", "hr",
+ "img", "input", "isindex", "link", "meta" "param")
+
+try:
+ HTML_EMPTY = set(HTML_EMPTY)
+except NameError:
+ pass
+
+def _serialize_html(write, elem, qnames, namespaces):
+ tag = elem.tag
+ text = elem.text
+ if tag is Comment:
+ write("" % _escape_cdata(text))
+ elif tag is ProcessingInstruction:
+ write("%s?>" % _escape_cdata(text))
+ else:
+ tag = qnames[tag]
+ if tag is None:
+ if text:
+ write(_escape_cdata(text))
+ for e in elem:
+ _serialize_html(write, e, qnames, None)
+ else:
+ write("<" + tag)
+ items = list(elem.items())
+ if items or namespaces:
+ if namespaces:
+ for v, k in sorted(namespaces.items(),
+ key=lambda x: x[1]): # sort on prefix
+ if k:
+ k = ":" + k
+ write(" xmlns%s=\"%s\"" % (
+ k,
+ _escape_attrib(v)
+ ))
+ for k, v in sorted(items): # lexical order
+ if isinstance(k, QName):
+ k = k.text
+ if isinstance(v, QName):
+ v = qnames[v.text]
+ else:
+ v = _escape_attrib_html(v)
+ # FIXME: handle boolean attributes
+ write(" %s=\"%s\"" % (qnames[k], v))
+ write(">")
+ tag = tag.lower()
+ if text:
+ if tag == "script" or tag == "style":
+ write(text)
+ else:
+ write(_escape_cdata(text))
+ for e in elem:
+ _serialize_html(write, e, qnames, None)
+ if tag not in HTML_EMPTY:
+ write("" + tag + ">")
+ if elem.tail:
+ write(_escape_cdata(elem.tail))
+
+def _serialize_text(write, elem):
+ for part in elem.itertext():
+ write(part)
+ if elem.tail:
+ write(elem.tail)
+
+_serialize = {
+ "xml": _serialize_xml,
+ "html": _serialize_html,
+ "text": _serialize_text,
+# this optional method is imported at the end of the module
+# "c14n": _serialize_c14n,
}
+##
+# Registers a namespace prefix. The registry is global, and any
+# existing mapping for either the given prefix or the namespace URI
+# will be removed.
+#
+# @param prefix Namespace prefix.
+# @param uri Namespace uri. Tags and attributes in this namespace
+# will be serialized with the given prefix, if at all possible.
+# @exception ValueError If the prefix is reserved, or is otherwise
+# invalid.
+
+def register_namespace(prefix, uri):
+ if re.match("ns\d+$", prefix):
+ raise ValueError("Prefix format reserved for internal use")
+ for k, v in _namespace_map.items():
+ if k == uri or v == prefix:
+ del _namespace_map[k]
+ _namespace_map[uri] = prefix
+
_namespace_map = {
# "well-known" namespace prefixes
"http://www.w3.org/XML/1998/namespace": "xml",
"http://www.w3.org/1999/xhtml": "html",
"http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
"http://schemas.xmlsoap.org/wsdl/": "wsdl",
+ # xml schema
+ "http://www.w3.org/2001/XMLSchema": "xs",
+ "http://www.w3.org/2001/XMLSchema-instance": "xsi",
+ # dublin core
+ "http://purl.org/dc/elements/1.1/": "dc",
}
def _raise_serialization_error(text):
@@ -768,77 +1077,127 @@
"cannot serialize %r (type %s)" % (text, type(text).__name__)
)
-def _encode_entity(text, pattern=_escape):
- # map reserved and non-ascii characters to numerical entities
- def escape_entities(m, map=_escape_map):
- out = []
- append = out.append
- for char in m.group():
- text = map.get(char)
- if text is None:
- text = "%d;" % ord(char)
- append(text)
- return "".join(out)
+def _escape_cdata(text):
+ # escape character data
try:
- return _encode(pattern.sub(escape_entities, text), "ascii")
- except TypeError:
+ # it's worth avoiding do-nothing calls for strings that are
+ # shorter than 500 character, or so. assume that's, by far,
+ # the most common case in most applications.
+ if "&" in text:
+ text = text.replace("&", "&")
+ if "<" in text:
+ text = text.replace("<", "<")
+ if ">" in text:
+ text = text.replace(">", ">")
+ return text
+ except (TypeError, AttributeError):
_raise_serialization_error(text)
-#
-# the following functions assume an ascii-compatible encoding
-# (or "utf-16")
-
-def _encode_cdata(text, encoding):
- # escape character data
+def _escape_attrib(text):
+ # escape attribute value
try:
- text = text.replace("&", "&")
- text = text.replace("<", "<")
- text = text.replace(">", ">")
- if encoding:
- return text.encode(encoding, "xmlcharrefreplace")
- else:
- return text
+ if "&" in text:
+ text = text.replace("&", "&")
+ if "<" in text:
+ text = text.replace("<", "<")
+ if ">" in text:
+ text = text.replace(">", ">")
+ if "\"" in text:
+ text = text.replace("\"", """)
+ if "\n" in text:
+ text = text.replace("\n", "
")
+ return text
except (TypeError, AttributeError):
_raise_serialization_error(text)
-def _escape_attrib(text):
+def _escape_attrib_html(text):
# escape attribute value
try:
- text = text.replace("&", "&")
- text = text.replace("'", "'") # FIXME: overkill
- text = text.replace("\"", """)
- text = text.replace("<", "<")
- text = text.replace(">", ">")
+ if "&" in text:
+ text = text.replace("&", "&")
+ if ">" in text:
+ text = text.replace(">", ">")
+ if "\"" in text:
+ text = text.replace("\"", """)
return text
except (TypeError, AttributeError):
_raise_serialization_error(text)
-def fixtag(tag, namespaces):
- # given a decorated tag (of the form {uri}tag), return prefixed
- # tag and namespace declaration, if any
- if isinstance(tag, QName):
- tag = tag.text
- namespace_uri, tag = tag[1:].split("}", 1)
- prefix = namespaces.get(namespace_uri)
- if prefix is None:
- prefix = _namespace_map.get(namespace_uri)
- if prefix is None:
- prefix = "ns%d" % len(namespaces)
- namespaces[namespace_uri] = prefix
- if prefix == "xml":
- xmlns = None
- else:
- xmlns = ("xmlns:%s" % prefix, namespace_uri)
+# --------------------------------------------------------------------
+
+##
+# Generates a string representation of an XML element, including all
+# subelements. If encoding is None, the return type is a string;
+# otherwise it is a bytes array.
+#
+# @param element An Element instance.
+# @keyparam encoding Optional output encoding (default is None).
+# @keyparam method Optional output method ("xml", "html", "text" or
+# "c14n"; default is "xml").
+# @return An (optionally) encoded string containing the XML data.
+# @defreturn string
+
+def tostring(element, encoding=None, method=None):
+ class dummy:
+ pass
+ data = []
+ file = dummy()
+ file.write = data.append
+ ElementTree(element).write(file, encoding, method=method)
+ if encoding:
+ return b"".join(data)
else:
- xmlns = None
- return "%s:%s" % (prefix, tag), xmlns
+ return "".join(data)
+
+##
+# Generates a string representation of an XML element, including all
+# subelements. The string is returned as a sequence of string fragments.
+#
+# @param element An Element instance.
+# @keyparam encoding Optional output encoding (default is US-ASCII).
+# @keyparam method Optional output method ("xml", "html", "text" or
+# "c14n"; default is "xml").
+# @return A sequence object containing the XML data.
+# @defreturn sequence
+# @since 1.3
+
+def tostringlist(element, encoding=None, method=None):
+ class dummy:
+ pass
+ data = []
+ file = dummy()
+ file.write = data.append
+ ElementTree(element).write(file, encoding, method=method)
+ # FIXME: merge small fragments into larger parts
+ return data
+
+##
+# Writes an element tree or element structure to sys.stdout. This
+# function should be used for debugging only.
+#
+# The exact output format is implementation dependent. In this
+# version, it's written as an ordinary XML file.
+#
+# @param elem An element tree or an individual element.
+
+def dump(elem):
+ # debugging
+ if not isinstance(elem, ElementTree):
+ elem = ElementTree(elem)
+ elem.write(sys.stdout)
+ tail = elem.getroot().tail
+ if not tail or tail[-1] != "\n":
+ sys.stdout.write("\n")
+
+# --------------------------------------------------------------------
+# parsing
##
# Parses an XML document into an element tree.
#
# @param source A filename or file object containing XML data.
# @param parser An optional parser instance. If not given, the
-# standard {@link XMLTreeBuilder} parser is used.
+# standard {@link XMLParser} parser is used.
# @return An ElementTree instance
def parse(source, parser=None):
@@ -853,18 +1212,25 @@
# @param source A filename or file object containing XML data.
# @param events A list of events to report back. If omitted, only "end"
# events are reported.
+# @param parser An optional parser instance. If not given, the
+# standard {@link XMLParser} parser is used.
# @return A (event, elem) iterator.
-class iterparse:
+def iterparse(source, events=None, parser=None):
+ if not hasattr(source, "read"):
+ source = open(source, "rb")
+ if not parser:
+ parser = XMLParser(target=TreeBuilder())
+ return _IterParseIterator(source, events, parser)
- def __init__(self, source, events=None):
- if not hasattr(source, "read"):
- source = open(source, "rb")
+class _IterParseIterator:
+
+ def __init__(self, source, events, parser):
self._file = source
self._events = []
self._index = 0
self.root = self._root = None
- self._parser = XMLTreeBuilder()
+ self._parser = parser
# wire up the parser for event reporting
parser = self._parser._parser
append = self._events.append
@@ -891,16 +1257,14 @@
parser.EndElementHandler = handler
elif event == "start-ns":
def handler(prefix, uri, event=event, append=append):
- try:
- uri = _encode(uri, "ascii")
- except UnicodeError:
- pass
- append((event, (prefix or "", uri)))
+ append((event, (prefix or "", uri or "")))
parser.StartNamespaceDeclHandler = handler
elif event == "end-ns":
def handler(prefix, event=event, append=append):
append((event, None))
parser.EndNamespaceDeclHandler = handler
+ else:
+ raise ValueError("unknown event %r" % event)
def __next__(self):
while 1:
@@ -909,10 +1273,7 @@
except IndexError:
if self._parser is None:
self.root = self._root
- try:
- raise StopIteration
- except NameError:
- raise IndexError
+ raise StopIteration
# load event buffer
del self._events[:]
self._index = 0
@@ -926,24 +1287,22 @@
self._index = self._index + 1
return item
- try:
- iter
- def __iter__(self):
- return self
- except NameError:
- def __getitem__(self, index):
- return self.__next__()
+ def __iter__(self):
+ return self
##
# Parses an XML document from a string constant. This function can
# be used to embed "XML literals" in Python code.
#
# @param source A string containing XML data.
+# @param parser An optional parser instance. If not given, the
+# standard {@link XMLParser} parser is used.
# @return An Element instance.
# @defreturn Element
-def XML(text):
- parser = XMLTreeBuilder()
+def XML(text, parser=None):
+ if not parser:
+ parser = XMLParser(target=TreeBuilder())
parser.feed(text)
return parser.close()
@@ -952,15 +1311,18 @@
# a dictionary which maps from element id:s to elements.
#
# @param source A string containing XML data.
+# @param parser An optional parser instance. If not given, the
+# standard {@link XMLParser} parser is used.
# @return A tuple containing an Element instance and a dictionary.
# @defreturn (Element, dictionary)
-def XMLID(text):
- parser = XMLTreeBuilder()
+def XMLID(text, parser=None):
+ if not parser:
+ parser = XMLParser(target=TreeBuilder())
parser.feed(text)
tree = parser.close()
ids = {}
- for elem in tree.getiterator():
+ for elem in tree.iter():
id = elem.get("id")
if id:
ids[id] = elem
@@ -977,25 +1339,23 @@
fromstring = XML
##
-# Generates a string representation of an XML element, including all
-# subelements. If encoding is None, the return type is a string;
-# otherwise it is a bytes array.
+# Parses an XML document from a sequence of string fragments.
#
-# @param element An Element instance.
-# @return An (optionally) encoded string containing the XML data.
-# @defreturn string
+# @param sequence A list or other sequence containing XML data fragments.
+# @param parser An optional parser instance. If not given, the
+# standard {@link XMLParser} parser is used.
+# @return An Element instance.
+# @defreturn Element
+# @since 1.3
-def tostring(element, encoding=None):
- class dummy:
- pass
- data = []
- file = dummy()
- file.write = data.append
- ElementTree(element).write(file, encoding)
- if encoding:
- return b"".join(data)
- else:
- return "".join(data)
+def fromstringlist(sequence, parser=None):
+ if not parser:
+ parser = XMLParser(target=TreeBuilder())
+ for text in sequence:
+ parser.feed(text)
+ return parser.close()
+
+# --------------------------------------------------------------------
##
# Generic element structure builder. This builder converts a sequence
@@ -1016,11 +1376,11 @@
self._last = None # last element
self._tail = None # true if we're after an end tag
if element_factory is None:
- element_factory = _ElementInterface
+ element_factory = Element
self._factory = element_factory
##
- # Flushes the parser buffers, and returns the toplevel documen
+ # Flushes the builder buffers, and returns the toplevel document
# element.
#
# @return An Element instance.
@@ -1028,7 +1388,7 @@
def close(self):
assert len(self._elem) == 0, "missing end tags"
- assert self._last != None, "missing toplevel element"
+ assert self._last is not None, "missing toplevel element"
return self._last
def _flush(self):
@@ -1093,28 +1453,39 @@
# instance of the standard {@link #TreeBuilder} class.
# @keyparam html Predefine HTML entities. This flag is not supported
# by the current implementation.
+# @keyparam encoding Optional encoding. If given, the value overrides
+# the encoding specified in the XML file.
# @see #ElementTree
# @see #TreeBuilder
-class XMLTreeBuilder:
+class XMLParser:
- def __init__(self, html=0, target=None):
+ def __init__(self, html=0, target=None, encoding=None):
try:
from xml.parsers import expat
except ImportError:
- raise ImportError(
- "No module named expat; use SimpleXMLTreeBuilder instead"
- )
- self._parser = parser = expat.ParserCreate(None, "}")
+ try:
+ import pyexpat as expat
+ except ImportError:
+ raise ImportError(
+ "No module named expat; use SimpleXMLTreeBuilder instead"
+ )
+ parser = expat.ParserCreate(encoding, "}")
if target is None:
target = TreeBuilder()
- self._target = target
+ # underscored names are provided for compatibility only
+ self.parser = self._parser = parser
+ self.target = self._target = target
+ self._error = expat.error
self._names = {} # name memo cache
# callbacks
parser.DefaultHandlerExpand = self._default
parser.StartElementHandler = self._start
parser.EndElementHandler = self._end
parser.CharacterDataHandler = self._data
+ # optional callbacks
+ parser.CommentHandler = self._comment
+ parser.ProcessingInstructionHandler = self._pi
# let expat do the buffering, if supported
try:
self._parser.buffer_text = 1
@@ -1127,10 +1498,18 @@
parser.StartElementHandler = self._start_list
except AttributeError:
pass
- encoding = "utf-8"
- # target.xml(encoding, None)
self._doctype = None
self.entity = {}
+ try:
+ self.version = "Expat %d.%d.%d" % expat.version_info
+ except AttributeError:
+ pass # unknown
+
+ def _raiseerror(self, value):
+ err = ParseError(value)
+ err.code = value.code
+ err.position = value.lineno, value.offset
+ raise err
def _fixname(self, key):
# expand qname, and convert name string to ascii, if possible
@@ -1149,7 +1528,7 @@
attrib = {}
for key, value in attrib_in.items():
attrib[fixname(key)] = value
- return self._target.start(tag, attrib)
+ return self.target.start(tag, attrib)
def _start_list(self, tag, attrib_in):
fixname = self._fixname
@@ -1158,27 +1537,47 @@
if attrib_in:
for i in range(0, len(attrib_in), 2):
attrib[fixname(attrib_in[i])] = attrib_in[i+1]
- return self._target.start(tag, attrib)
+ return self.target.start(tag, attrib)
def _data(self, text):
- return self._target.data(text)
+ return self.target.data(text)
def _end(self, tag):
- return self._target.end(self._fixname(tag))
+ return self.target.end(self._fixname(tag))
+
+ def _comment(self, data):
+ try:
+ comment = self.target.comment
+ except AttributeError:
+ pass
+ else:
+ return comment(data)
+
+ def _pi(self, target, data):
+ try:
+ pi = self.target.pi
+ except AttributeError:
+ pass
+ else:
+ return pi(target, data)
def _default(self, text):
prefix = text[:1]
if prefix == "&":
# deal with undefined entities
try:
- self._target.data(self.entity[text[1:-1]])
+ self.target.data(self.entity[text[1:-1]])
except KeyError:
from xml.parsers import expat
- raise expat.error(
+ err = expat.error(
"undefined entity %s: line %d, column %d" %
(text, self._parser.ErrorLineNumber,
self._parser.ErrorColumnNumber)
)
+ err.code = 11 # XML_ERROR_UNDEFINED_ENTITY
+ err.lineno = self._parser.ErrorLineNumber
+ err.offset = self._parser.ErrorColumnNumber
+ raise err
elif prefix == "<" and text[:9] == "= 100:
- _cache.clear()
- _cache[path] = p
- return p
+def iterfind(elem, path, namespaces=None):
+ # compile selector pattern
+ if path[-1:] == "/":
+ path = path + "*" # implicit all (FIXME: keep this?)
+ try:
+ selector = _cache[path]
+ except KeyError:
+ if len(_cache) > 100:
+ _cache.clear()
+ if path[:1] == "/":
+ raise SyntaxError("cannot use absolute path on element")
+ next = iter(xpath_tokenizer(path, namespaces)).__next__
+ token = next()
+ selector = []
+ while 1:
+ try:
+ selector.append(ops[token[0]](next, token))
+ except StopIteration:
+ raise SyntaxError("invalid path")
+ try:
+ token = next()
+ if token[0] == "/":
+ token = next()
+ except StopIteration:
+ break
+ _cache[path] = selector
+ # execute selector pattern
+ result = [elem]
+ context = _SelectorContext(elem)
+ for select in selector:
+ result = select(context, result)
+ return result
##
# Find first matching object.
-def find(element, path):
- return _compile(path).find(element)
+def find(elem, path, namespaces=None):
+ try:
+ return next(iterfind(elem, path, namespaces))
+ except StopIteration:
+ return None
##
-# Find text for first matching object.
+# Find all matching objects.
-def findtext(element, path, default=None):
- return _compile(path).findtext(element, default)
+def findall(elem, path, namespaces=None):
+ return list(iterfind(elem, path, namespaces))
##
-# Find all matching objects.
+# Find text for first matching object.
-def findall(element, path):
- return _compile(path).findall(element)
+def findtext(elem, path, default=None, namespaces=None):
+ try:
+ elem = next(iterfind(elem, path, namespaces))
+ return elem.text or ""
+ except StopIteration:
+ return default
Index: Modules/_elementtree.c
===================================================================
--- Modules/_elementtree.c (revision 78865)
+++ Modules/_elementtree.c (working copy)
@@ -1,21 +1,15 @@
/*
* ElementTree
- * $Id: _elementtree.c 2657 2006-03-12 20:50:32Z fredrik $
+ * $Id: _elementtree.c 3473 2009-01-11 22:53:55Z fredrik $
*
* elementtree accelerator
*
* History:
* 1999-06-20 fl created (as part of sgmlop)
* 2001-05-29 fl effdom edition
- * 2001-06-05 fl backported to unix; fixed bogus free in clear
- * 2001-07-10 fl added findall helper
* 2003-02-27 fl elementtree edition (alpha)
* 2004-06-03 fl updates for elementtree 1.2
- * 2005-01-05 fl added universal name cache, Element/SubElement factories
- * 2005-01-06 fl moved python helpers into C module; removed 1.5.2 support
- * 2005-01-07 fl added 2.1 support; work around broken __copy__ in 2.3
- * 2005-01-08 fl added makeelement method; fixed path support
- * 2005-01-10 fl optimized memory usage
+ * 2005-01-05 fl major optimization effort
* 2005-01-11 fl first public release (cElementTree 0.8)
* 2005-01-12 fl split element object into base and extras
* 2005-01-13 fl use tagged pointers for tail/text (cElementTree 0.9)
@@ -35,16 +29,23 @@
* 2005-12-16 fl added support for non-standard encodings
* 2006-03-08 fl fixed a couple of potential null-refs and leaks
* 2006-03-12 fl merge in 2.5 ssize_t changes
+ * 2007-08-25 fl call custom builder's close method from XMLParser
+ * 2007-08-31 fl added iter, extend from ET 1.3
+ * 2007-09-01 fl fixed ParseError exception, setslice source type, etc
+ * 2007-09-03 fl fixed handling of negative insert indexes
+ * 2007-09-04 fl added itertext from ET 1.3
+ * 2007-09-06 fl added position attribute to ParseError exception
+ * 2008-06-06 fl delay error reporting in iterparse (from Hrvoje Niksic)
*
- * Copyright (c) 1999-2006 by Secret Labs AB. All rights reserved.
- * Copyright (c) 1999-2006 by Fredrik Lundh.
+ * Copyright (c) 1999-2009 by Secret Labs AB. All rights reserved.
+ * Copyright (c) 1999-2009 by Fredrik Lundh.
*
* info@pythonware.com
* http://www.pythonware.com
*/
/* Licensed to PSF under a Contributor Agreement. */
-/* See http://www.python.org/2.4/license for licensing details. */
+/* See http://www.python.org/psf/license for licensing details. */
#include "Python.h"
@@ -56,7 +57,7 @@
/* Leave defined to include the expat-based XMLParser type */
#define USE_EXPAT
-/* Define to to all expat calls via pyexpat's embedded expat library */
+/* Define to do all expat calls via pyexpat's embedded expat library */
/* #define USE_PYEXPAT_CAPI */
/* An element can hold this many children without extra memory
@@ -93,6 +94,25 @@
#define LOCAL(type) static type
#endif
+/* compatibility macros */
+#if (PY_VERSION_HEX < 0x02060000)
+#define Py_REFCNT(ob) (((PyObject*)(ob))->ob_refcnt)
+#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
+#endif
+
+#if (PY_VERSION_HEX < 0x02050000)
+typedef int Py_ssize_t;
+#define lenfunc inquiry
+#endif
+
+#if (PY_VERSION_HEX < 0x02040000)
+#define PyDict_CheckExact PyDict_Check
+
+#if !defined(Py_RETURN_NONE)
+#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#endif
+#endif
+
/* macros used to store 'join' flags in string object pointers. note
that all use of text and tail as object pointers must be wrapped in
JOIN_OBJ. see comments in the ElementObject definition for more
@@ -102,9 +122,11 @@
#define JOIN_OBJ(p) ((PyObject*) ((Py_uintptr_t) (p) & ~1))
/* glue functions (see the init function for details) */
+static PyObject* elementtree_parseerror_obj;
static PyObject* elementtree_copyelement_obj;
static PyObject* elementtree_deepcopy_obj;
-static PyObject* elementtree_getiterator_obj;
+static PyObject* elementtree_iter_obj;
+static PyObject* elementtree_itertext_obj;
static PyObject* elementpath_obj;
/* helpers */
@@ -188,23 +210,6 @@
return result;
}
-#if (PY_VERSION_HEX < 0x02020000)
-LOCAL(int)
-PyDict_Update(PyObject* dict, PyObject* other)
-{
- /* PyDict_Update emulation for 2.1 and earlier */
-
- PyObject* res;
-
- res = PyObject_CallMethod(dict, "update", "O", other);
- if (!res)
- return -1;
-
- Py_DECREF(res);
- return 0;
-}
-#endif
-
/* -------------------------------------------------------------------- */
/* the element type */
@@ -309,7 +314,7 @@
if (element_new_extra(self, attrib) < 0) {
PyObject_Del(self);
return NULL;
- }
+ }
self->extra->length = 0;
self->extra->allocated = STATIC_CHILDREN;
@@ -407,6 +412,7 @@
PyObject* res = self->extra->attrib;
if (res == Py_None) {
+ Py_DECREF(res);
/* create missing dictionary */
res = PyDict_New();
if (!res)
@@ -688,6 +694,8 @@
/* add object to memo dictionary (so deepcopy won't visit it again) */
id = PyLong_FromLong((Py_uintptr_t) self);
+ if (!id)
+ goto error;
i = PyDict_SetItem(memo, id, (PyObject*) element);
@@ -711,7 +719,8 @@
/* check if a tag contains an xpath character */
-#define PATHCHAR(ch) (ch == '/' || ch == '*' || ch == '[' || ch == '@')
+#define PATHCHAR(ch) \
+ (ch == '/' || ch == '*' || ch == '[' || ch == '@' || ch == '.')
if (PyUnicode_Check(tag)) {
Py_UNICODE *p = PyUnicode_AS_UNICODE(tag);
@@ -742,17 +751,51 @@
}
static PyObject*
+element_extend(ElementObject* self, PyObject* args)
+{
+ PyObject* seq;
+ Py_ssize_t i, seqlen = 0;
+
+ PyObject* seq_in;
+ if (!PyArg_ParseTuple(args, "O:extend", &seq_in))
+ return NULL;
+
+ seq = PySequence_Fast(seq_in, "");
+ if (!seq) {
+ PyErr_Format(
+ PyExc_TypeError,
+ "expected sequence, not \"%.200s\"", Py_TYPE(seq_in)->tp_name
+ );
+ return NULL;
+ }
+
+ seqlen = PySequence_Size(seq);
+ for (i = 0; i < seqlen; i++) {
+ PyObject* element = PySequence_Fast_GET_ITEM(seq, i);
+ if (element_add_subelement(self, element) < 0) {
+ Py_DECREF(seq);
+ return NULL;
+ }
+ }
+
+ Py_DECREF(seq);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject*
element_find(ElementObject* self, PyObject* args)
{
int i;
PyObject* tag;
- if (!PyArg_ParseTuple(args, "O:find", &tag))
+ PyObject* namespaces = Py_None;
+ if (!PyArg_ParseTuple(args, "O|O:find", &tag, &namespaces))
return NULL;
- if (checkpath(tag))
+ if (checkpath(tag) || namespaces != Py_None)
return PyObject_CallMethod(
- elementpath_obj, "find", "OO", self, tag
+ elementpath_obj, "find", "OOO", self, tag, namespaces
);
if (!self->extra)
@@ -777,12 +820,13 @@
PyObject* tag;
PyObject* default_value = Py_None;
- if (!PyArg_ParseTuple(args, "O|O:findtext", &tag, &default_value))
+ PyObject* namespaces = Py_None;
+ if (!PyArg_ParseTuple(args, "O|OO:findtext", &tag, &default_value, &namespaces))
return NULL;
- if (checkpath(tag))
+ if (checkpath(tag) || namespaces != Py_None)
return PyObject_CallMethod(
- elementpath_obj, "findtext", "OOO", self, tag, default_value
+ elementpath_obj, "findtext", "OOOO", self, tag, default_value, namespaces
);
if (!self->extra) {
@@ -813,12 +857,13 @@
PyObject* out;
PyObject* tag;
- if (!PyArg_ParseTuple(args, "O:findall", &tag))
+ PyObject* namespaces = Py_None;
+ if (!PyArg_ParseTuple(args, "O|O:findall", &tag, &namespaces))
return NULL;
- if (checkpath(tag))
+ if (checkpath(tag) || namespaces != Py_None)
return PyObject_CallMethod(
- elementpath_obj, "findall", "OO", self, tag
+ elementpath_obj, "findall", "OOO", self, tag, namespaces
);
out = PyList_New(0);
@@ -843,6 +888,19 @@
}
static PyObject*
+element_iterfind(ElementObject* self, PyObject* args)
+{
+ PyObject* tag;
+ PyObject* namespaces = Py_None;
+ if (!PyArg_ParseTuple(args, "O|O:iterfind", &tag, &namespaces))
+ return NULL;
+
+ return PyObject_CallMethod(
+ elementpath_obj, "iterfind", "OOO", self, tag, namespaces
+ );
+}
+
+static PyObject*
element_get(ElementObject* self, PyObject* args)
{
PyObject* value;
@@ -870,6 +928,8 @@
int i;
PyObject* list;
+ /* FIXME: report as deprecated? */
+
if (!PyArg_ParseTuple(args, ":getchildren"))
return NULL;
@@ -890,18 +950,18 @@
}
static PyObject*
-element_getiterator(ElementObject* self, PyObject* args)
+element_iter(ElementObject* self, PyObject* args)
{
PyObject* result;
PyObject* tag = Py_None;
- if (!PyArg_ParseTuple(args, "|O:getiterator", &tag))
+ if (!PyArg_ParseTuple(args, "|O:iter", &tag))
return NULL;
- if (!elementtree_getiterator_obj) {
+ if (!elementtree_iter_obj) {
PyErr_SetString(
PyExc_RuntimeError,
- "getiterator helper not found"
+ "iter helper not found"
);
return NULL;
}
@@ -913,61 +973,58 @@
Py_INCREF(self); PyTuple_SET_ITEM(args, 0, (PyObject*) self);
Py_INCREF(tag); PyTuple_SET_ITEM(args, 1, (PyObject*) tag);
- result = PyObject_CallObject(elementtree_getiterator_obj, args);
+ result = PyObject_CallObject(elementtree_iter_obj, args);
Py_DECREF(args);
return result;
}
+
static PyObject*
-element_getitem(PyObject* self_, Py_ssize_t index)
+element_itertext(ElementObject* self, PyObject* args)
{
- ElementObject* self = (ElementObject*) self_;
+ PyObject* result;
+
+ if (!PyArg_ParseTuple(args, ":itertext"))
+ return NULL;
- if (!self->extra || index < 0 || index >= self->extra->length) {
+ if (!elementtree_itertext_obj) {
PyErr_SetString(
- PyExc_IndexError,
- "child index out of range"
+ PyExc_RuntimeError,
+ "itertext helper not found"
);
return NULL;
}
- Py_INCREF(self->extra->children[index]);
- return self->extra->children[index];
+ args = PyTuple_New(1);
+ if (!args)
+ return NULL;
+
+ Py_INCREF(self); PyTuple_SET_ITEM(args, 0, (PyObject*) self);
+
+ result = PyObject_CallObject(elementtree_itertext_obj, args);
+
+ Py_DECREF(args);
+
+ return result;
}
static PyObject*
-element_getslice(PyObject* self_, Py_ssize_t start, Py_ssize_t end)
+element_getitem(PyObject* self_, Py_ssize_t index)
{
ElementObject* self = (ElementObject*) self_;
- Py_ssize_t i;
- PyObject* list;
-
- if (!self->extra)
- return PyList_New(0);
-
- /* standard clamping */
- if (start < 0)
- start = 0;
- if (end < 0)
- end = 0;
- if (end > self->extra->length)
- end = self->extra->length;
- if (start > end)
- start = end;
- list = PyList_New(end - start);
- if (!list)
+ if (!self->extra || index < 0 || index >= self->extra->length) {
+ PyErr_SetString(
+ PyExc_IndexError,
+ "child index out of range"
+ );
return NULL;
-
- for (i = start; i < end; i++) {
- PyObject* item = self->extra->children[i];
- Py_INCREF(item);
- PyList_SET_ITEM(list, i - start, item);
}
- return list;
+ Py_INCREF(self->extra->children[index]);
+ return self->extra->children[index];
}
static PyObject*
@@ -984,8 +1041,11 @@
if (!self->extra)
element_new_extra(self, NULL);
- if (index < 0)
- index = 0;
+ if (index < 0) {
+ index += self->extra->length;
+ if (index < 0)
+ index = 0;
+ }
if (index > self->extra->length)
index = self->extra->length;
@@ -1156,77 +1216,6 @@
}
static int
-element_setslice(PyObject* self_, Py_ssize_t start, Py_ssize_t end, PyObject* item)
-{
- ElementObject* self = (ElementObject*) self_;
- Py_ssize_t i, new, old;
- PyObject* recycle = NULL;
-
- if (!self->extra)
- element_new_extra(self, NULL);
-
- /* standard clamping */
- if (start < 0)
- start = 0;
- if (end < 0)
- end = 0;
- if (end > self->extra->length)
- end = self->extra->length;
- if (start > end)
- start = end;
-
- old = end - start;
-
- if (item == NULL)
- new = 0;
- else if (PyList_CheckExact(item)) {
- new = PyList_GET_SIZE(item);
- } else {
- /* FIXME: support arbitrary sequences? */
- PyErr_Format(
- PyExc_TypeError,
- "expected list, not \"%.200s\"", Py_TYPE(item)->tp_name
- );
- return -1;
- }
-
- if (old > 0) {
- /* to avoid recursive calls to this method (via decref), move
- old items to the recycle bin here, and get rid of them when
- we're done modifying the element */
- recycle = PyList_New(old);
- for (i = 0; i < old; i++)
- PyList_SET_ITEM(recycle, i, self->extra->children[i + start]);
- }
-
- if (new < old) {
- /* delete slice */
- for (i = end; i < self->extra->length; i++)
- self->extra->children[i + new - old] = self->extra->children[i];
- } else if (new > old) {
- /* insert slice */
- if (element_resize(self, new - old) < 0)
- return -1;
- for (i = self->extra->length-1; i >= end; i--)
- self->extra->children[i + new - old] = self->extra->children[i];
- }
-
- /* replace the slice */
- for (i = 0; i < new; i++) {
- PyObject* element = PyList_GET_ITEM(item, i);
- Py_INCREF(element);
- self->extra->children[i + start] = element;
- }
-
- self->extra->length += new - old;
-
- /* discard the recycle bin, and everything in it */
- Py_XDECREF(recycle);
-
- return 0;
-}
-
-static int
element_setitem(PyObject* self_, Py_ssize_t index, PyObject* item)
{
ElementObject* self = (ElementObject*) self_;
@@ -1256,6 +1245,190 @@
return 0;
}
+static PyObject*
+element_subscr(PyObject* self_, PyObject* item)
+{
+ ElementObject* self = (ElementObject*) self_;
+
+#if (PY_VERSION_HEX < 0x02050000)
+ if (PyInt_Check(item) || PyLong_Check(item)) {
+ long i = PyInt_AsLong(item);
+#else
+ if (PyIndex_Check(item)) {
+ Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
+#endif
+
+ if (i == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ if (i < 0 && self->extra)
+ i += self->extra->length;
+ return element_getitem(self_, i);
+ }
+ else if (PySlice_Check(item)) {
+ Py_ssize_t start, stop, step, slicelen, cur, i;
+ PyObject* list;
+
+ if (!self->extra)
+ return PyList_New(0);
+
+ if (PySlice_GetIndicesEx((PySliceObject *)item,
+ self->extra->length,
+ &start, &stop, &step, &slicelen) < 0) {
+ return NULL;
+ }
+
+ if (slicelen <= 0)
+ return PyList_New(0);
+ else {
+ list = PyList_New(slicelen);
+ if (!list)
+ return NULL;
+
+ for (cur = start, i = 0; i < slicelen;
+ cur += step, i++) {
+ PyObject* item = self->extra->children[cur];
+ Py_INCREF(item);
+ PyList_SET_ITEM(list, i, item);
+ }
+
+ return list;
+ }
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "element indices must be integers");
+ return NULL;
+ }
+}
+
+static int
+element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value)
+{
+ ElementObject* self = (ElementObject*) self_;
+
+#if (PY_VERSION_HEX < 0x02050000)
+ if (PyInt_Check(item) || PyLong_Check(item)) {
+ long i = PyInt_AsLong(item);
+#else
+ if (PyIndex_Check(item)) {
+ Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
+#endif
+
+ if (i == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+ if (i < 0 && self->extra)
+ i += self->extra->length;
+ return element_setitem(self_, i, value);
+ }
+ else if (PySlice_Check(item)) {
+ Py_ssize_t start, stop, step, slicelen, newlen, cur, i;
+
+ PyObject* recycle = NULL;
+ PyObject* seq = NULL;
+
+ if (!self->extra)
+ element_new_extra(self, NULL);
+
+ if (PySlice_GetIndicesEx((PySliceObject *)item,
+ self->extra->length,
+ &start, &stop, &step, &slicelen) < 0) {
+ return -1;
+ }
+
+ if (value == NULL)
+ newlen = 0;
+ else {
+ seq = PySequence_Fast(value, "");
+ if (!seq) {
+ PyErr_Format(
+ PyExc_TypeError,
+ "expected sequence, not \"%.200s\"", Py_TYPE(value)->tp_name
+ );
+ return -1;
+ }
+ newlen = PySequence_Size(seq);
+ }
+
+ if (step != 1 && newlen != slicelen)
+ {
+ PyErr_Format(PyExc_ValueError,
+#if (PY_VERSION_HEX < 0x02050000)
+ "attempt to assign sequence of size %d "
+ "to extended slice of size %d",
+#else
+ "attempt to assign sequence of size %zd "
+ "to extended slice of size %zd",
+#endif
+ newlen, slicelen
+ );
+ return -1;
+ }
+
+
+ /* Resize before creating the recycle bin, to prevent refleaks. */
+ if (newlen > slicelen) {
+ if (element_resize(self, newlen - slicelen) < 0) {
+ if (seq) {
+ Py_DECREF(seq);
+ }
+ return -1;
+ }
+ }
+
+ if (slicelen > 0) {
+ /* to avoid recursive calls to this method (via decref), move
+ old items to the recycle bin here, and get rid of them when
+ we're done modifying the element */
+ recycle = PyList_New(slicelen);
+ if (!recycle) {
+ if (seq) {
+ Py_DECREF(seq);
+ }
+ return -1;
+ }
+ for (cur = start, i = 0; i < slicelen;
+ cur += step, i++)
+ PyList_SET_ITEM(recycle, i, self->extra->children[cur]);
+ }
+
+ if (newlen < slicelen) {
+ /* delete slice */
+ for (i = stop; i < self->extra->length; i++)
+ self->extra->children[i + newlen - slicelen] = self->extra->children[i];
+ } else if (newlen > slicelen) {
+ /* insert slice */
+ for (i = self->extra->length-1; i >= stop; i--)
+ self->extra->children[i + newlen - slicelen] = self->extra->children[i];
+ }
+
+ /* replace the slice */
+ for (cur = start, i = 0; i < newlen;
+ cur += step, i++) {
+ PyObject* element = PySequence_Fast_GET_ITEM(seq, i);
+ Py_INCREF(element);
+ self->extra->children[cur] = element;
+ }
+
+ self->extra->length += newlen - slicelen;
+
+ if (seq) {
+ Py_DECREF(seq);
+ }
+
+ /* discard the recycle bin, and everything in it */
+ Py_XDECREF(recycle);
+
+ return 0;
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "element indices must be integers");
+ return -1;
+ }
+}
+
static PyMethodDef element_methods[] = {
{"clear", (PyCFunction) element_clear, METH_VARARGS},
@@ -1268,10 +1441,15 @@
{"findall", (PyCFunction) element_findall, METH_VARARGS},
{"append", (PyCFunction) element_append, METH_VARARGS},
+ {"extend", (PyCFunction) element_extend, METH_VARARGS},
{"insert", (PyCFunction) element_insert, METH_VARARGS},
{"remove", (PyCFunction) element_remove, METH_VARARGS},
- {"getiterator", (PyCFunction) element_getiterator, METH_VARARGS},
+ {"iter", (PyCFunction) element_iter, METH_VARARGS},
+ {"itertext", (PyCFunction) element_itertext, METH_VARARGS},
+ {"iterfind", (PyCFunction) element_iterfind, METH_VARARGS},
+
+ {"getiterator", (PyCFunction) element_iter, METH_VARARGS},
{"getchildren", (PyCFunction) element_getchildren, METH_VARARGS},
{"items", (PyCFunction) element_items, METH_VARARGS},
@@ -1297,30 +1475,46 @@
{NULL, NULL}
};
-static PyObject*
+static PyObject*
element_getattro(ElementObject* self, PyObject* nameobj)
{
PyObject* res;
char *name = "";
if (PyUnicode_Check(nameobj))
- name = _PyUnicode_AsString(nameobj);
+ name = _PyUnicode_AsString(nameobj);
- if (strcmp(name, "tag") == 0)
- res = self->tag;
- else if (strcmp(name, "text") == 0)
+ /* handle common attributes first */
+ if (strcmp(name, "tag") == 0) {
+ res = self->tag;
+ Py_INCREF(res);
+ return res;
+ } else if (strcmp(name, "text") == 0) {
res = element_get_text(self);
- else if (strcmp(name, "tail") == 0) {
+ Py_INCREF(res);
+ return res;
+ }
+
+ /* methods */
+ res = PyObject_GenericGetAttr((PyObject*) self, nameobj);
+ if (res)
+ return res;
+
+ /* less common attributes */
+ if (strcmp(name, "tail") == 0) {
+ PyErr_Clear();
res = element_get_tail(self);
} else if (strcmp(name, "attrib") == 0) {
+ PyErr_Clear();
if (!self->extra)
element_new_extra(self, NULL);
- res = element_get_attrib(self);
- } else {
- return PyObject_GenericGetAttr((PyObject*) self, nameobj);
+ res = element_get_attrib(self);
}
- Py_XINCREF(res);
+ if (!res)
+ return NULL;
+
+ Py_INCREF(res);
return res;
}
@@ -1366,9 +1560,15 @@
0, /* sq_concat */
0, /* sq_repeat */
element_getitem,
- element_getslice,
+ 0,
element_setitem,
- element_setslice,
+ 0,
+};
+
+static PyMappingMethods element_as_mapping = {
+ (lenfunc) element_length,
+ (binaryfunc) element_subscr,
+ (objobjargproc) element_ass_subscr,
};
static PyTypeObject Element_Type = {
@@ -1383,7 +1583,7 @@
(reprfunc)element_repr, /* tp_repr */
0, /* tp_as_number */
&element_as_sequence, /* tp_as_sequence */
- 0, /* tp_as_mapping */
+ &element_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
@@ -1537,7 +1737,7 @@
} else {
if (self->root) {
PyErr_SetString(
- PyExc_SyntaxError,
+ elementtree_parseerror_obj,
"multiple elements on top level"
);
goto error;
@@ -1678,7 +1878,7 @@
LOCAL(void)
treebuilder_handle_namespace(TreeBuilderObject* self, int start,
- const char* prefix, const char *uri)
+ PyObject *prefix, PyObject *uri)
{
PyObject* res;
PyObject* action;
@@ -1691,8 +1891,7 @@
if (!self->start_ns_event_obj)
return;
action = self->start_ns_event_obj;
- /* FIXME: prefix and uri use utf-8 encoding! */
- parcel = Py_BuildValue("ss", (prefix) ? prefix : "", uri);
+ parcel = Py_BuildValue("OO", prefix, uri);
if (!parcel)
return;
Py_INCREF(action);
@@ -1852,6 +2051,7 @@
PyObject* names;
PyObject* handle_xml;
+
PyObject* handle_start;
PyObject* handle_data;
PyObject* handle_end;
@@ -1859,6 +2059,8 @@
PyObject* handle_comment;
PyObject* handle_pi;
+ PyObject* handle_close;
+
} XMLParserObject;
static PyTypeObject XMLParser_Type;
@@ -1930,6 +2132,36 @@
return value;
}
+static void
+expat_set_error(const char* message, int line, int column)
+{
+ PyObject *error;
+ PyObject *position;
+ char buffer[256];
+
+ sprintf(buffer, "%s: line %d, column %d", message, line, column);
+
+ error = PyObject_CallFunction(elementtree_parseerror_obj, "s", buffer);
+ if (!error)
+ return;
+
+ /* add position attribute */
+ position = Py_BuildValue("(ii)", line, column);
+ if (!position) {
+ Py_DECREF(error);
+ return;
+ }
+ if (PyObject_SetAttrString(error, "position", position) == -1) {
+ Py_DECREF(error);
+ Py_DECREF(position);
+ return;
+ }
+ Py_DECREF(position);
+
+ PyErr_SetObject(elementtree_parseerror_obj, error);
+ Py_DECREF(error);
+}
+
/* -------------------------------------------------------------------- */
/* handlers */
@@ -1960,10 +2192,12 @@
else
res = NULL;
Py_XDECREF(res);
- } else {
- PyErr_Format(
- PyExc_SyntaxError, "undefined entity &%s;: line %ld, column %ld",
- PyBytes_AS_STRING(key),
+ } else if (!PyErr_Occurred()) {
+ /* Report the first error, not the last */
+ char message[128];
+ sprintf(message, "undefined entity &%.100s;", _PyUnicode_AsString(key));
+ expat_set_error(
+ message,
EXPAT(GetErrorLineNumber)(self->parser),
EXPAT(GetErrorColumnNumber)(self->parser)
);
@@ -2018,9 +2252,15 @@
/* shortcut */
res = treebuilder_handle_start((TreeBuilderObject*) self->target,
tag, attrib);
- else if (self->handle_start)
+ else if (self->handle_start) {
+ if (attrib == Py_None) {
+ Py_DECREF(attrib);
+ attrib = PyDict_New();
+ if (!attrib)
+ return;
+ }
res = PyObject_CallFunction(self->handle_start, "OO", tag, attrib);
- else
+ } else
res = NULL;
Py_DECREF(tag);
@@ -2080,9 +2320,28 @@
expat_start_ns_handler(XMLParserObject* self, const XML_Char* prefix,
const XML_Char *uri)
{
+ PyObject* sprefix = NULL;
+ PyObject* suri = NULL;
+
+ suri = PyUnicode_DecodeUTF8(uri, strlen(uri), "strict");
+ if (!suri)
+ return;
+
+ if (prefix)
+ sprefix = PyUnicode_DecodeUTF8(prefix, strlen(prefix), "strict");
+ else
+ sprefix = PyUnicode_FromString("");
+ if (!sprefix) {
+ Py_DECREF(suri);
+ return;
+ }
+
treebuilder_handle_namespace(
- (TreeBuilderObject*) self->target, 1, prefix, uri
+ (TreeBuilderObject*) self->target, 1, sprefix, suri
);
+
+ Py_DECREF(sprefix);
+ Py_DECREF(suri);
}
static void
@@ -2158,10 +2417,10 @@
p = PyUnicode_AS_UNICODE(u);
for (i = 0; i < 256; i++) {
- if (p[i] != Py_UNICODE_REPLACEMENT_CHARACTER)
- info->map[i] = p[i];
+ if (p[i] != Py_UNICODE_REPLACEMENT_CHARACTER)
+ info->map[i] = p[i];
else
- info->map[i] = -1;
+ info->map[i] = -1;
}
Py_DECREF(u);
@@ -2245,6 +2504,7 @@
self->handle_end = PyObject_GetAttrString(target, "end");
self->handle_comment = PyObject_GetAttrString(target, "comment");
self->handle_pi = PyObject_GetAttrString(target, "pi");
+ self->handle_close = PyObject_GetAttrString(target, "close");
PyErr_Clear();
@@ -2288,6 +2548,7 @@
{
EXPAT(ParserFree)(self->parser);
+ Py_XDECREF(self->handle_close);
Py_XDECREF(self->handle_pi);
Py_XDECREF(self->handle_comment);
Py_XDECREF(self->handle_end);
@@ -2318,8 +2579,7 @@
return NULL;
if (!ok) {
- PyErr_Format(
- PyExc_SyntaxError, "%s: line %ld, column %ld",
+ expat_set_error(
EXPAT(ErrorString)(EXPAT(GetErrorCode)(self->parser)),
EXPAT(GetErrorLineNumber)(self->parser),
EXPAT(GetErrorColumnNumber)(self->parser)
@@ -2340,13 +2600,17 @@
return NULL;
res = expat_parse(self, "", 0, 1);
+ if (!res)
+ return NULL;
- if (res && TreeBuilder_CheckExact(self->target)) {
+ if (TreeBuilder_CheckExact(self->target)) {
Py_DECREF(res);
return treebuilder_done((TreeBuilderObject*) self->target);
- }
-
- return res;
+ } if (self->handle_close) {
+ Py_DECREF(res);
+ return PyObject_CallFunction(self->handle_close, "");
+ } else
+ return res;
}
static PyObject*
@@ -2458,7 +2722,7 @@
if (event_set == Py_None) {
/* default is "end" only */
- target->end_event_obj = PyBytes_FromString("end");
+ target->end_event_obj = PyUnicode_FromString("end");
Py_RETURN_NONE;
}
@@ -2468,9 +2732,13 @@
for (i = 0; i < PyTuple_GET_SIZE(event_set); i++) {
PyObject* item = PyTuple_GET_ITEM(event_set, i);
char* event;
- if (!PyBytes_Check(item))
+ if (PyUnicode_Check(item)) {
+ event = _PyUnicode_AsString(item);
+ } else if (PyBytes_Check(item))
+ event = PyBytes_AS_STRING(item);
+ else {
goto error;
- event = PyBytes_AS_STRING(item);
+ }
if (strcmp(event, "start") == 0) {
Py_INCREF(item);
target->start_event_obj = item;
@@ -2530,19 +2798,19 @@
char *name = "";
if (PyUnicode_Check(nameobj))
- name = _PyUnicode_AsString(nameobj);
+ name = _PyUnicode_AsString(nameobj);
PyErr_Clear();
if (strcmp(name, "entity") == 0)
- res = self->entity;
+ res = self->entity;
else if (strcmp(name, "target") == 0)
- res = self->target;
+ res = self->target;
else if (strcmp(name, "version") == 0) {
char buffer[100];
sprintf(buffer, "Expat %d.%d.%d", XML_MAJOR_VERSION,
XML_MINOR_VERSION, XML_MICRO_VERSION);
- return PyBytes_FromString(buffer);
+ return PyUnicode_DecodeUTF8(buffer, strlen(buffer), "strict");
} else {
return PyObject_GenericGetAttr((PyObject*) self, nameobj);
}
@@ -2617,9 +2885,6 @@
PyObject* m;
PyObject* g;
char* bootstrap;
-#if defined(USE_PYEXPAT_CAPI)
- struct PyExpat_CAPI* capi;
-#endif
/* Initialize object types */
if (PyType_Ready(&TreeBuilder_Type) < 0)
@@ -2651,10 +2916,6 @@
bootstrap = (
-#if (PY_VERSION_HEX >= 0x02020000 && PY_VERSION_HEX < 0x02030000)
- "from __future__ import generators\n" /* enable yield under 2.2 */
-#endif
-
"from copy import copy, deepcopy\n"
"try:\n"
@@ -2672,11 +2933,16 @@
" def copyelement(elem):\n"
" return elem\n"
- "def Comment(text=None):\n" /* public */
+ "class CommentProxy:\n"
+ " def __call__(self, text=None):\n"
" element = cElementTree.Element(ET.Comment)\n"
" element.text = text\n"
" return element\n"
- "cElementTree.Comment = Comment\n"
+ " def __hash__(self):\n"
+ " return hash(ET.Comment)\n"
+ " def __eq__(self, other):\n"
+ " return ET.Comment == other\n"
+ "cElementTree.Comment = CommentProxy()\n"
"class ElementTree(ET.ElementTree):\n" /* public */
" def parse(self, source, parser=None):\n"
@@ -2695,23 +2961,23 @@
" return self._root\n"
"cElementTree.ElementTree = ElementTree\n"
- "def getiterator(node, tag=None):\n" /* helper */
+ "def iter(node, tag=None):\n" /* helper */
" if tag == '*':\n"
" tag = None\n"
-#if (PY_VERSION_HEX < 0x02020000)
- " nodes = []\n" /* 2.1 doesn't have yield */
- " if tag is None or node.tag == tag:\n"
- " nodes.append(node)\n"
- " for node in node:\n"
- " nodes.extend(getiterator(node, tag))\n"
- " return nodes\n"
-#else
" if tag is None or node.tag == tag:\n"
" yield node\n"
" for node in node:\n"
- " for node in getiterator(node, tag):\n"
+ " for node in iter(node, tag):\n"
" yield node\n"
-#endif
+
+ "def itertext(node):\n" /* helper */
+ " if node.text:\n"
+ " yield node.text\n"
+ " for e in node:\n"
+ " for s in e.itertext():\n"
+ " yield s\n"
+ " if e.tail:\n"
+ " yield e.tail\n"
"def parse(source, parser=None):\n" /* public */
" tree = ElementTree()\n"
@@ -2719,48 +2985,54 @@
" return tree\n"
"cElementTree.parse = parse\n"
-#if (PY_VERSION_HEX < 0x02020000)
- "if hasattr(ET, 'iterparse'):\n"
- " cElementTree.iterparse = ET.iterparse\n" /* delegate on 2.1 */
-#else
- "class iterparse(object):\n"
+ "class iterparse:\n"
" root = None\n"
" def __init__(self, file, events=None):\n"
" if not hasattr(file, 'read'):\n"
" file = open(file, 'rb')\n"
" self._file = file\n"
- " self._events = events\n"
- " def __iter__(self):\n"
- " events = []\n"
+ " self._events = []\n"
+ " self._index = 0\n"
+ " self.root = self._root = None\n"
" b = cElementTree.TreeBuilder()\n"
- " p = cElementTree.XMLParser(b)\n"
- " p._setevents(events, self._events)\n"
+ " self._parser = cElementTree.XMLParser(b)\n"
+ " self._parser._setevents(self._events, events)\n"
+ " def __next__(self):\n"
" while 1:\n"
- " data = self._file.read(16384)\n"
- " if not data:\n"
- " break\n"
- " p.feed(data)\n"
- " for event in events:\n"
- " yield event\n"
- " del events[:]\n"
- " root = p.close()\n"
- " for event in events:\n"
- " yield event\n"
- " self.root = root\n"
+ " try:\n"
+ " item = self._events[self._index]\n"
+ " except IndexError:\n"
+ " if self._parser is None:\n"
+ " self.root = self._root\n"
+ " raise StopIteration\n"
+ " # load event buffer\n"
+ " del self._events[:]\n"
+ " self._index = 0\n"
+ " data = self._file.read(16384)\n"
+ " if data:\n"
+ " self._parser.feed(data)\n"
+ " else:\n"
+ " self._root = self._parser.close()\n"
+ " self._parser = None\n"
+ " else:\n"
+ " self._index = self._index + 1\n"
+ " return item\n"
+ " def __iter__(self):\n"
+ " return self\n"
"cElementTree.iterparse = iterparse\n"
-#endif
- "def PI(target, text=None):\n" /* public */
- " element = cElementTree.Element(ET.ProcessingInstruction)\n"
+ "class PIProxy:\n"
+ " def __call__(self, target, text=None):\n"
+ " element = cElementTree.Element(ET.PI)\n"
" element.text = target\n"
" if text:\n"
" element.text = element.text + ' ' + text\n"
" return element\n"
-
- " elem = cElementTree.Element(ET.PI)\n"
- " elem.text = text\n"
- " return elem\n"
- "cElementTree.PI = cElementTree.ProcessingInstruction = PI\n"
+ " def __hash__(self):\n"
+ " return hash(ET.PI)\n"
+ " def __eq__(self, other):\n"
+ " return ET.PI == other\n"
+ "cElementTree.PI = cElementTree.ProcessingInstruction = PIProxy()\n"
"def XML(text):\n" /* public */
" parser = cElementTree.XMLParser()\n"
@@ -2771,25 +3043,34 @@
"def XMLID(text):\n" /* public */
" tree = XML(text)\n"
" ids = {}\n"
- " for elem in tree.getiterator():\n"
+ " for elem in tree.iter():\n"
" id = elem.get('id')\n"
" if id:\n"
" ids[id] = elem\n"
" return tree, ids\n"
"cElementTree.XMLID = XMLID\n"
+ "try:\n"
+ " register_namespace = ET.register_namespace\n"
+ "except AttributeError:\n"
+ " def register_namespace(prefix, uri):\n"
+ " ET._namespace_map[uri] = prefix\n"
+ "cElementTree.register_namespace = register_namespace\n"
+
"cElementTree.dump = ET.dump\n"
"cElementTree.ElementPath = ElementPath = ET.ElementPath\n"
"cElementTree.iselement = ET.iselement\n"
"cElementTree.QName = ET.QName\n"
"cElementTree.tostring = ET.tostring\n"
+ "cElementTree.fromstringlist = ET.fromstringlist\n"
+ "cElementTree.tostringlist = ET.tostringlist\n"
"cElementTree.VERSION = '" VERSION "'\n"
"cElementTree.__version__ = '" VERSION "'\n"
- "cElementTree.XMLParserError = SyntaxError\n"
);
- PyRun_String(bootstrap, Py_file_input, g, NULL);
+ if (!PyRun_String(bootstrap, Py_file_input, g, NULL))
+ return NULL;
elementpath_obj = PyDict_GetItemString(g, "ElementPath");
@@ -2804,22 +3085,30 @@
}
} else
PyErr_Clear();
+
elementtree_deepcopy_obj = PyDict_GetItemString(g, "deepcopy");
- elementtree_getiterator_obj = PyDict_GetItemString(g, "getiterator");
+ elementtree_iter_obj = PyDict_GetItemString(g, "iter");
+ elementtree_itertext_obj = PyDict_GetItemString(g, "itertext");
#if defined(USE_PYEXPAT_CAPI)
/* link against pyexpat, if possible */
- capi = PyCapsule_Import(PyExpat_CAPSULE_NAME, 0);
- if (capi &&
- strcmp(capi->magic, PyExpat_CAPI_MAGIC) == 0 &&
- capi->size <= sizeof(*expat_capi) &&
- capi->MAJOR_VERSION == XML_MAJOR_VERSION &&
- capi->MINOR_VERSION == XML_MINOR_VERSION &&
- capi->MICRO_VERSION == XML_MICRO_VERSION)
- expat_capi = capi;
- else
- expat_capi = NULL;
+ expat_capi = PyCapsule_Import(PyExpat_CAPSULE_NAME, 0);
+ if (expat_capi) {
+ /* check that it's usable */
+ if (strcmp(expat_capi->magic, PyExpat_CAPI_MAGIC) != 0 ||
+ expat_capi->size < sizeof(struct PyExpat_CAPI) ||
+ expat_capi->MAJOR_VERSION != XML_MAJOR_VERSION ||
+ expat_capi->MINOR_VERSION != XML_MINOR_VERSION ||
+ expat_capi->MICRO_VERSION != XML_MICRO_VERSION)
+ expat_capi = NULL;
+ }
#endif
- return m;
+ elementtree_parseerror_obj = PyErr_NewException(
+ "cElementTree.ParseError", PyExc_SyntaxError, NULL
+ );
+ Py_INCREF(elementtree_parseerror_obj);
+ PyModule_AddObject(m, "ParseError", elementtree_parseerror_obj);
+
+ return m;
}