diff -r bf3371119d2c Lib/test/test_xml_etree.py --- a/Lib/test/test_xml_etree.py Sun Dec 06 21:22:32 2009 +0100 +++ b/Lib/test/test_xml_etree.py Sun Dec 06 21:43:24 2009 +0100 @@ -7,6 +7,12 @@ from test import test_support +# Ignore warning about search path starting with "/". +import warnings +warnings.filterwarnings("ignore", + "This search is broken in 1.3 and earlier; if you rely on the " + "current behaviour, change it to '.*'", FutureWarning) + SAMPLE_XML = """ text @@ -40,7 +46,7 @@ if not callable(method): print method, "not callable" -def serialize(ET, elem, encoding=None): +def serialize(ET, elem, encoding=None, getvalue=True): import StringIO file = StringIO.StringIO() tree = ET.ElementTree(elem) @@ -48,7 +54,11 @@ tree.write(file, encoding) else: tree.write(file) - return file.getvalue() + if getvalue: + return file.getvalue() + else: + file.seek(0) + return file def summarize(elem): return elem.tag @@ -81,6 +91,12 @@ >>> check_method(element.items) >>> check_method(element.getiterator) + These methods return an iterable. See bug 6472. + + >>> check_method(element.getiterator("tag").next) + >>> check_method(element.findall("tag").next) + >>> check_method(element.findall("*").next) + Basic method sanity checks. >>> serialize(ET, element) # 1 @@ -103,6 +119,19 @@ ValueError: list.remove(x): x not in list >>> serialize(ET, element) # 6 '' + >>> element[0:0] = [subelement, subelement, subelement] + >>> serialize(ET, element[1]) + '' + >>> assert element[1:9] == [element[1], element[2]] + >>> del element[1:2] + >>> serialize(ET, element) + '' + + Method iterparse should return an iterator. See bug 6472. + + >>> next(ET.iterparse(serialize(ET, element, getvalue=False))) + ... # doctest: +ELLIPSIS + ('end', ) """ def find(): @@ -161,7 +190,7 @@ ['tag', 'tag', 'tag'] >>> summarize_list(elem.findall("././tag")) ['tag', 'tag'] - >>> summarize_list(ET.ElementTree(elem).findall("/tag")) + >>> summarize_list(ET.ElementTree(elem).findall("/tag")) # Ignore warning ['tag', 'tag'] >>> summarize_list(ET.ElementTree(elem).findall("./tag")) ['tag', 'tag'] diff -r bf3371119d2c Lib/test/test_xml_etree_c.py --- a/Lib/test/test_xml_etree_c.py Sun Dec 06 21:22:32 2009 +0100 +++ b/Lib/test/test_xml_etree_c.py Sun Dec 06 21:43:24 2009 +0100 @@ -5,6 +5,12 @@ from test import test_support +# Ignore warning about search path starting with "/". +import warnings +warnings.filterwarnings("ignore", + "This search is broken in 1.3 and earlier; if you rely on the " + "current behaviour, change it to '.*'", FutureWarning) + ET = test_support.import_module('xml.etree.cElementTree') SAMPLE_XML = """ @@ -38,7 +44,7 @@ if not callable(method): print method, "not callable" -def serialize(ET, elem, encoding=None): +def serialize(ET, elem, encoding=None, getvalue=True): import StringIO file = StringIO.StringIO() tree = ET.ElementTree(elem) @@ -46,7 +52,11 @@ tree.write(file, encoding) else: tree.write(file) - return file.getvalue() + if getvalue: + return file.getvalue() + else: + file.seek(0) + return file def summarize(elem): return elem.tag @@ -77,6 +87,12 @@ >>> check_method(element.items) >>> check_method(element.getiterator) + These methods return an iterable. See bug 6472. + + >>> check_method(element.getiterator("tag").next) + >>> check_method(element.findall("tag").next) + >>> check_method(element.findall("*").next) + Basic method sanity checks. >>> serialize(ET, element) # 1 @@ -99,6 +115,19 @@ ValueError: list.remove(x): x not in list >>> serialize(ET, element) # 6 '' + >>> element[0:0] = [subelement, subelement, subelement] + >>> serialize(ET, element[1]) + '' + >>> assert element[1:9] == [element[1], element[2]] + >>> del element[1:2] + >>> serialize(ET, element) + '' + + Method iterparse should return an iterator. See bug 6472. + + >>> next(ET.iterparse(serialize(ET, element, getvalue=False))) + ... # doctest: +ELLIPSIS + ('end', ) """ def find(): @@ -155,7 +184,7 @@ ['tag', 'tag', 'tag'] >>> summarize_list(elem.findall("././tag")) ['tag', 'tag'] - >>> summarize_list(ET.ElementTree(elem).findall("/tag")) + >>> summarize_list(ET.ElementTree(elem).findall("/tag")) # Ignore warning ['tag', 'tag'] >>> summarize_list(ET.ElementTree(elem).findall("./tag")) ['tag', 'tag'] diff -r bf3371119d2c Lib/xml/etree/ElementInclude.py --- a/Lib/xml/etree/ElementInclude.py Sun Dec 06 21:22:32 2009 +0100 +++ b/Lib/xml/etree/ElementInclude.py Sun Dec 06 21:43:24 2009 +0100 @@ -1,6 +1,6 @@ # # ElementTree -# $Id: ElementInclude.py 1862 2004-06-18 07:31:02Z Fredrik $ +# $Id: ElementInclude.py 3265 2007-09-06 20:42:00Z fredrik $ # # limited xinclude support for element trees # @@ -16,7 +16,7 @@ # -------------------------------------------------------------------- # The ElementTree toolkit is # -# Copyright (c) 1999-2004 by Fredrik Lundh +# Copyright (c) 1999-2007 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, @@ -42,7 +42,7 @@ # -------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. +# See http://www.python.org/2.6/license for licensing details. ## # Limited XInclude support for the ElementTree package. diff -r bf3371119d2c Lib/xml/etree/ElementPath.py --- a/Lib/xml/etree/ElementPath.py Sun Dec 06 21:22:32 2009 +0100 +++ b/Lib/xml/etree/ElementPath.py Sun Dec 06 21:43:24 2009 +0100 @@ -1,6 +1,6 @@ # # ElementTree -# $Id: ElementPath.py 1858 2004-06-17 21:31:41Z Fredrik $ +# $Id: ElementPath.py 3276 2007-09-12 06:52:30Z fredrik $ # # limited xpath support for element trees # @@ -8,8 +8,9 @@ # 2003-05-23 fl created # 2003-05-28 fl added support for // etc # 2003-08-27 fl fixed parsing of periods in element names +# 2007-09-10 fl new selection engine # -# Copyright (c) 2003-2004 by Fredrik Lundh. All rights reserved. +# Copyright (c) 2003-2007 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com @@ -17,7 +18,7 @@ # -------------------------------------------------------------------- # The ElementTree toolkit is # -# Copyright (c) 1999-2004 by Fredrik Lundh +# Copyright (c) 1999-2007 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, @@ -43,7 +44,7 @@ # -------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. +# See http://www.python.org/2.6/license for licensing details. ## # Implementation module for XPath support. There's usually no reason @@ -54,145 +55,175 @@ import re xpath_tokenizer = re.compile( - "(::|\.\.|\(\)|[/.*:\[\]\(\)@=])|((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|\s+" + "(" + "'[^']*'|\"[^\"]*\"|" + "::|" + "//?|" + "\.\.|" + "\(\)|" + "[/.*:\[\]\(\)@=])|" + "((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|" + "\s+" ).findall -class xpath_descendant_or_self: - pass +def prepare_tag(next, token): + tag = token[1] + def select(context, result): + for elem in result: + for e in elem: + if e.tag == tag: + yield e + return select -## -# Wrapper for a compiled XPath. +def prepare_star(next, token): + def select(context, result): + for elem in result: + for e in elem: + yield e + return select -class Path: +def prepare_dot(next, token): + def select(context, result): + for elem in result: + yield elem + return select - ## - # Create an Path instance from an XPath expression. +def prepare_iter(next, token): + token = next() + if token[0] == "*": + tag = "*" + elif not token[0]: + tag = token[1] + else: + raise SyntaxError + def select(context, result): + for elem in result: + for e in elem.iter(tag): + if e is not elem: + yield e + return select - def __init__(self, path): - tokens = xpath_tokenizer(path) - # the current version supports 'path/path'-style expressions only - self.path = [] - self.tag = None - if tokens and tokens[0][0] == "/": - raise SyntaxError("cannot use absolute path on element") - while tokens: - op, tag = tokens.pop(0) - if tag or op == "*": - self.path.append(tag or op) - elif op == ".": - pass - elif op == "/": - self.path.append(xpath_descendant_or_self()) - continue +def prepare_dot_dot(next, token): + def select(context, result): + parent_map = context.parent_map + if parent_map is None: + context.parent_map = parent_map = {} + for p in context.root.iter(): + for e in p: + parent_map[e] = p + for elem in result: + if elem in parent_map: + yield parent_map[elem] + return select + +def prepare_predicate(next, token): + # this one should probably be refactored... + token = next() + if token[0] == "@": + # attribute + token = next() + if token[0]: + raise SyntaxError("invalid attribute predicate") + key = token[1] + token = next() + if token[0] == "]": + def select(context, result): + for elem in result: + if elem.get(key) is not None: + yield elem + elif token[0] == "=": + value = next()[0] + if value[:1] == "'" or value[:1] == '"': + value = value[1:-1] else: - raise SyntaxError("unsupported path syntax (%s)" % op) - if tokens: - op, tag = tokens.pop(0) - if op != "/": - raise SyntaxError( - "expected path separator (%s)" % (op or tag) - ) - if self.path and isinstance(self.path[-1], xpath_descendant_or_self): - raise SyntaxError("path cannot end with //") - if len(self.path) == 1 and isinstance(self.path[0], type("")): - self.tag = self.path[0] + raise SyntaxError("invalid comparision target") + token = next() + def select(context, result): + for elem in result: + if elem.get(key) == value: + yield elem + if token[0] != "]": + raise SyntaxError("invalid attribute predicate") + elif not token[0]: + tag = token[1] + token = next() + if token[0] != "]": + raise SyntaxError("invalid node predicate") + def select(context, result): + for elem in result: + if elem.find(tag) is not None: + yield elem + else: + raise SyntaxError("invalid predicate") + return select - ## - # Find first matching object. - - def find(self, element): - tag = self.tag - if tag is None: - nodeset = self.findall(element) - if not nodeset: - return None - return nodeset[0] - for elem in element: - if elem.tag == tag: - return elem - return None - - ## - # Find text for first matching object. - - def findtext(self, element, default=None): - tag = self.tag - if tag is None: - nodeset = self.findall(element) - if not nodeset: - return default - return nodeset[0].text or "" - for elem in element: - if elem.tag == tag: - return elem.text or "" - return default - - ## - # Find all matching objects. - - def findall(self, element): - nodeset = [element] - index = 0 - while 1: - try: - path = self.path[index] - index = index + 1 - except IndexError: - return nodeset - set = [] - if isinstance(path, xpath_descendant_or_self): - try: - tag = self.path[index] - if not isinstance(tag, type("")): - tag = None - else: - index = index + 1 - except IndexError: - tag = None # invalid path - for node in nodeset: - new = list(node.getiterator(tag)) - if new and new[0] is node: - set.extend(new[1:]) - else: - set.extend(new) - else: - for node in nodeset: - for node in node: - if path == "*" or node.tag == path: - set.append(node) - if not set: - return [] - nodeset = set +ops = { + "": prepare_tag, + "*": prepare_star, + ".": prepare_dot, + "..": prepare_dot_dot, + "//": prepare_iter, + "[": prepare_predicate, + } _cache = {} -## -# (Internal) Compile path. +class _SelectorContext: + parent_map = None + def __init__(self, root): + self.root = root -def _compile(path): - p = _cache.get(path) - if p is not None: - return p - p = Path(path) - if len(_cache) >= 100: - _cache.clear() - _cache[path] = p - return p +# -------------------------------------------------------------------- ## # Find first matching object. -def find(element, path): - return _compile(path).find(element) +def find(elem, path): + try: + return findall(elem, path).next() + except StopIteration: + return None + +## +# Find all matching objects. + +def findall(elem, path): + # compile selector pattern + try: + selector = _cache[path] + except KeyError: + if len(_cache) > 100: + _cache.clear() + if path[:1] == "/": + raise SyntaxError("cannot use absolute path on element") + stream = iter(xpath_tokenizer(path)) + next = stream.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 text for first matching object. -def findtext(element, path, default=None): - return _compile(path).findtext(element, default) - -## -# Find all matching objects. - -def findall(element, path): - return _compile(path).findall(element) +def findtext(elem, path, default=None): + try: + elem = findall(elem, path).next() + return elem.text + except StopIteration: + return default diff -r bf3371119d2c Lib/xml/etree/ElementTree.py --- a/Lib/xml/etree/ElementTree.py Sun Dec 06 21:22:32 2009 +0100 +++ b/Lib/xml/etree/ElementTree.py Sun Dec 06 21:43:24 2009 +0100 @@ -1,8 +1,8 @@ # # ElementTree -# $Id: ElementTree.py 2326 2005-03-17 07:45:21Z fredrik $ +# $Id: ElementTree.py 3276 2007-09-12 06:52:30Z fredrik $ # -# light-weight XML support for Python 1.5.2 and later. +# light-weight XML support for Python 2.2 and later. # # history: # 2001-10-20 fl created (from various sources) @@ -31,10 +31,21 @@ # 2004-06-02 fl added default support to findtext # 2004-06-08 fl fixed encoding of non-ascii element/attribute names # 2004-08-23 fl take advantage of post-2.1 expat features +# 2004-09-03 fl made Element class visible; removed factory # 2005-02-01 fl added iterparse implementation # 2005-03-02 fl fixed iterparse support for pre-2.2 versions +# 2005-11-12 fl added tostringlist/fromstringlist helpers +# 2006-07-05 fl merged in selected changes from the 1.3 sandbox +# 2006-07-05 fl removed support for 2.1 and earlier +# 2007-06-21 fl added deprecation/future warnings +# 2007-08-25 fl added doctype hook, added parser version attribute etc +# 2007-08-26 fl added new serializer code (better namespace handling, etc) +# 2007-08-27 fl warn for broken /tag searches on tree level +# 2007-09-02 fl added html/text methods to serializer (experimental) +# 2007-09-05 fl added method argument to tostring/tostringlist +# 2007-09-06 fl improved error handling # -# Copyright (c) 1999-2005 by Fredrik Lundh. All rights reserved. +# Copyright (c) 1999-2007 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com @@ -42,7 +53,7 @@ # -------------------------------------------------------------------- # The ElementTree toolkit is # -# Copyright (c) 1999-2005 by Fredrik Lundh +# Copyright (c) 1999-2007 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, @@ -68,22 +79,25 @@ # -------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. +# See http://www.python.org/2.6/license for licensing details. + +from __future__ import generators __all__ = [ # public symbols "Comment", "dump", "Element", "ElementTree", - "fromstring", + "fromstring", "fromstringlist", "iselement", "iterparse", - "parse", + "parse", "ParseError", "PI", "ProcessingInstruction", "QName", "SubElement", - "tostring", + "tostring", "tostringlist", "TreeBuilder", - "VERSION", "XML", + "VERSION", + "XML", "XMLParser", "XMLTreeBuilder", ] @@ -102,16 +116,16 @@ #
  • a number of child elements, stored in a Python sequence
  • # # -# To create an element instance, use the {@link #Element} or {@link -# #SubElement} factory functions. +# To create an element instance, use the {@link #Element} constructor +# or the {@link #SubElement} factory function. #

    # The {@link #ElementTree} class can be used to wrap an element # structure, and convert it from and to XML. ## -import string, sys, re +import sys, re -class _SimpleElementPath: +class _SimpleElementPath(object): # emulate pre-1.2 find/findtext/findall behaviour def find(self, element, tag): for elem in element: @@ -138,25 +152,41 @@ # FIXME: issue warning in this case? ElementPath = _SimpleElementPath() -# TODO: add support for custom namespace resolvers/default namespaces -# TODO: add improved support for incremental parsing +VERSION = "1.3a2" -VERSION = "1.2.6" +class ParseError(SyntaxError): + pass + +# -------------------------------------------------------------------- ## -# Internal element class. This class defines the Element interface, -# and provides a reference implementation of this interface. +# 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 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") + +## +# 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 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. # @see Element # @see SubElement # @see Comment # @see ProcessingInstruction -class _ElementInterface: +class Element(object): # text...tail ## @@ -166,10 +196,10 @@ ## # (Attribute) Element attribute dictionary. Where possible, use - # {@link #_ElementInterface.get}, - # {@link #_ElementInterface.set}, - # {@link #_ElementInterface.keys}, and - # {@link #_ElementInterface.items} to access + # {@link #Element.get}, + # {@link #Element.set}, + # {@link #Element.keys}, and + # {@link #Element.items} to access # element attributes. attrib = None @@ -187,13 +217,15 @@ tail = None # text after end tag, if any - def __init__(self, tag, attrib): + def __init__(self, tag, attrib={}, **extra): + attrib = attrib.copy() + attrib.update(extra) self.tag = tag self.attrib = attrib self._children = [] def __repr__(self): - return "" % (self.tag, id(self)) + return "" % (repr(self.tag), id(self)) ## # Creates a new element object of the same type as this element. @@ -213,6 +245,15 @@ def __len__(self): return len(self._children) + def __nonzero__(self): + import warnings + warnings.warn( + "The behavior of this method will change in future versions. " + "Use specific 'len(elem)' or 'elem is not None' test instead.", + FutureWarning + ) + return len(self._children) != 0 # emulate old behaviour + ## # Returns the given subelement. # @@ -287,6 +328,18 @@ self._children.append(element) ## + # Appends subelements from a sequence. + # + # @param elements A sequence object with zero or more elements. + # @exception AssertionError If a subelement is not a valid object. + # @since 1.3 + + def extend(self, elements): + for element in elements: + assert iselement(element) + self._children.extend(elements) + + ## # Inserts a subelement at the given position in this element. # # @param index Where to insert the new subelement. @@ -310,13 +363,19 @@ self._children.remove(element) ## - # Returns all subelements. The elements are returned in document - # order. + # (Deprecated) Returns all subelements. The elements are returned + # in document order. # # @return A list of subelements. # @defreturn list of Element instances def getchildren(self): + import warnings + warnings.warn( + "This method will be removed in future versions. " + "Use 'list(elem)' or iteration over elem instead.", + DeprecationWarning + ) return self._children ## @@ -409,45 +468,48 @@ # and all subelements, in document order, and returns all elements # with a matching tag. #

    - # 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 (FIXME: preserve list behaviour too? see below) + getiterator = iter + + # def getiterator(self, tag=None): + # return list(tag) + + ## + # 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 itertext(self): + 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 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 - -def Element(tag, attrib={}, **extra): - attrib = attrib.copy() - attrib.update(extra) - return _ElementInterface(tag, attrib) +_Element = _ElementInterface = Element ## # Subelement factory. This function creates an element instance, and @@ -472,7 +534,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. @@ -488,7 +551,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. @@ -514,7 +578,7 @@ # an URI, and this argument is interpreted as a local name. # @return An opaque object, representing the QName. -class QName: +class QName(object): def __init__(self, text_or_uri, tag=None): if tag: text_or_uri = "{%s}%s" % (text_or_uri, tag) @@ -528,16 +592,18 @@ return cmp(self.text, other.text) return cmp(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: +class ElementTree(object): def __init__(self, element=None, file=None): assert element is None or iselement(element) @@ -569,8 +635,8 @@ # 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. + # @keyparam parser An optional parser instance. If not given, the + # standard {@link XMLParser} parser is used. # @return The document root element. # @defreturn Element @@ -578,7 +644,7 @@ if not hasattr(source, "read"): source = open(source, "rb") if not parser: - parser = XMLTreeBuilder() + parser = XMLParser(target=TreeBuilder()) while 1: data = source.read(32768) if not data: @@ -595,9 +661,11 @@ # @return An iterator. # @defreturn iterator - def getiterator(self, tag=None): + def iter(self, tag=None): assert self._root is not None - return self._root.getiterator(tag) + return self._root.iter(tag) + + getiterator = iter ## # Finds the first toplevel element with given tag. @@ -611,6 +679,12 @@ assert self._root is not None if path[:1] == "/": path = "." + path + import warnings + warnings.warn( + "This search is broken in 1.3 and earlier; if you rely " + "on the current behaviour, change it to %r" % path, + FutureWarning + ) return self._root.find(path) ## @@ -629,6 +703,12 @@ assert self._root is not None if path[:1] == "/": path = "." + path + import warnings + warnings.warn( + "This search is broken in 1.3 and earlier; if you rely " + "on the current behaviour, change it to %r" % path, + FutureWarning + ) return self._root.findtext(path, default) ## @@ -644,89 +724,370 @@ assert self._root is not None if path[:1] == "/": path = "." + path + import warnings + warnings.warn( + "This search is broken in 1.3 and earlier; if you rely " + "on the current behaviour, change it to %r" % path, + FutureWarning + ) return self._root.findall(path) ## # Writes the element tree to a file, as XML. # # @param file A file name, or a file object opened for writing. - # @param encoding Optional output encoding (default is US-ASCII). + # @keyparam encoding Optional output encoding (default is US-ASCII). + # @keyparam method Optional output method ("xml" or "html"; 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, encoding="us-ascii"): + def write(self, file, + # keyword arguments + encoding="us-ascii", + xml_declaration=None, + default_namespace=None, + method=None): assert self._root is not None if not hasattr(file, "write"): file = open(file, "wb") + write = file.write + if not method: + method = "xml" if not encoding: encoding = "us-ascii" - elif encoding != "utf-8" and encoding != "us-ascii": - file.write("\n" % encoding) - self._write(file, self._root, encoding, {}) + elif xml_declaration or (xml_declaration is None and + encoding not in ("utf-8", "us-ascii")): + write("\n" % encoding) + if method == "text": + _serialize_text(write, self._root, encoding) + else: + qnames, namespaces = _namespaces( + self._root, encoding, default_namespace + ) + if method == "xml": + _serialize_xml( + write, self._root, encoding, qnames, namespaces + ) + elif method == "html": + _serialize_html( + write, self._root, encoding, qnames, namespaces + ) + else: + raise ValueError("unknown method %r" % method) - def _write(self, file, node, encoding, namespaces): - # write XML to file - tag = node.tag - if tag is Comment: - file.write("" % _escape_cdata(node.text, encoding)) - elif tag is ProcessingInstruction: - file.write("" % _escape_cdata(node.text, encoding)) +# -------------------------------------------------------------------- +# serialization support + +def _namespaces(elem, encoding, default_namespace=None): + # identify namespaces used in this tree + + # maps qnames to *encoded* prefix:local names + qnames = {None: None} + + # maps uri:s to prefixes + namespaces = {} + if default_namespace: + namespaces[default_namespace] = "" + + def encode(text): + return text.encode(encoding) + + def add_qname(qname): + # calculate serialized qname representation + try: + if qname[:1] == "{": + uri, tag = qname[1:].split("}", 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] = encode("%s:%s" % (prefix, tag)) + else: + qnames[qname] = encode(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] = encode(qname) + except TypeError: + _raise_serialization_error(qname) + + # 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, basestring): + 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, encoding, qnames, namespaces): + tag = elem.tag + text = elem.text + if tag is Comment: + write("" % _escape_cdata(text, encoding)) + elif tag is ProcessingInstruction: + write("" % _escape_cdata(text, encoding)) + else: + tag = qnames[tag] + if tag is None: + if text: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_xml(write, e, encoding, qnames, None) else: - items = 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: + write("<" + tag) + items = elem.items() + if items or namespaces: 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(" %s=\"%s\"" % (_encode(k, encoding), - _escape_attrib(v, encoding))) - for k, v in xmlns_items: - file.write(" %s=\"%s\"" % (_encode(k, encoding), - _escape_attrib(v, encoding))) - if node.text or len(node): - file.write(">") - if node.text: - file.write(_escape_cdata(node.text, encoding)) - for n in node: - self._write(file, n, encoding, namespaces) - file.write("") + if isinstance(k, QName): + k = k.text + if isinstance(v, QName): + v = qnames[v.text] + else: + v = _escape_attrib(v, encoding) + write(" %s=\"%s\"" % (qnames[k], v)) + if namespaces: + items = namespaces.items() + items.sort(key=lambda x: x[1]) # sort on prefix + for v, k in items: + if k: + k = ":" + k + write(" xmlns%s=\"%s\"" % ( + k.encode(encoding), + _escape_attrib(v, encoding) + )) + if text or len(elem): + write(">") + if text: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_xml(write, e, encoding, qnames, None) + write("") else: - file.write(" />") - for k, v in xmlns_items: - del namespaces[v] - if node.tail: - file.write(_escape_cdata(node.tail, encoding)) + write(" />") + if elem.tail: + write(_escape_cdata(elem.tail, encoding)) + +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, encoding, qnames, namespaces): + tag = elem.tag + text = elem.text + if tag is Comment: + write("" % _escape_cdata(text, encoding)) + elif tag is ProcessingInstruction: + write("" % _escape_cdata(text, encoding)) + else: + tag = qnames[tag] + if tag is None: + if text: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_html(write, e, encoding, qnames, None) + else: + write("<" + tag) + items = elem.items() + if items or namespaces: + items.sort() # lexical order + for k, v in items: + if isinstance(k, QName): + k = k.text + if isinstance(v, QName): + v = qnames[v.text] + else: + v = _escape_attrib_html(v, encoding) + # FIXME: handle boolean attributes + write(" %s=\"%s\"" % (qnames[k], v)) + if namespaces: + items = namespaces.items() + items.sort(key=lambda x: x[1]) # sort on prefix + for v, k in items: + if k: + k = ":" + k + write(" xmlns%s=\"%s\"" % ( + k.encode(encoding), + _escape_attrib(v, encoding) + )) + write(">") + tag = tag.lower() + if text: + if tag == "script" or tag == "style": + write(_encode(text, encoding)) + else: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_html(write, e, encoding, qnames, None) + if tag not in HTML_EMPTY: + write("") + if elem.tail: + write(_escape_cdata(elem.tail, encoding)) + +def _serialize_text(write, elem, encoding): + for part in elem.itertext(): + write(part.encode(encoding)) + if elem.tail: + write(elem.tail.encode(encoding)) + +## +# 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. +# @raise 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", + # dublic core + "http://purl.org/dc/elements/1.1/": "dc", +} + +def _raise_serialization_error(text): + raise TypeError( + "cannot serialize %r (type %s)" % (text, type(text).__name__) + ) + +def _encode(text, encoding): + try: + return text.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) + +def _escape_cdata(text, encoding): + # escape character data + try: + # 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.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) + +def _escape_attrib(text, encoding): + # escape attribute value + try: + 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.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) + +def _escape_attrib_html(text, encoding): + # escape attribute value + try: + if "&" in text: + text = text.replace("&", "&") + if ">" in text: + text = text.replace(">", ">") + if "\"" in text: + text = text.replace("\"", """) + return text.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) # -------------------------------------------------------------------- -# helpers ## -# Checks if an object appears to be a valid element object. +# Generates a string representation of an XML element, including all +# subelements. # -# @param An element instance. -# @return A true value if this is an element object. -# @defreturn flag +# @param element An Element instance. +# @return An encoded string containing the XML data. +# @defreturn string -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") +def tostring(element, encoding=None, method=None): + class dummy: + pass + data = [] + file = dummy() + file.write = data.append + ElementTree(element).write(file, encoding, method=method) + 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. +# @return A sequence object containing the XML data. +# @defreturn sequence +# @since 1.3 + +def tostringlist(element, encoding=None): + class dummy: + pass + data = [] + file = dummy() + file.write = data.append + ElementTree(element).write(file, encoding) + # FIXME: merge small fragments into larger parts + return data ## # Writes an element tree or element structure to sys.stdout. This @@ -746,115 +1107,15 @@ if not tail or tail[-1] != "\n": sys.stdout.write("\n") -def _encode(s, encoding): - try: - return s.encode(encoding) - except AttributeError: - return s # 1.5.2: assume the string uses the right encoding - -if sys.version[:3] == "1.5": - _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2 -else: - _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"')) - -_escape_map = { - "&": "&", - "<": "<", - ">": ">", - '"': """, -} - -_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", -} - -def _raise_serialization_error(text): - raise TypeError( - "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 string.join(out, "") - try: - return _encode(pattern.sub(escape_entities, text), "ascii") - except TypeError: - _raise_serialization_error(text) - -# -# the following functions assume an ascii-compatible encoding -# (or "utf-16") - -def _escape_cdata(text, encoding=None, replace=string.replace): - # escape character data - try: - if encoding: - try: - text = _encode(text, encoding) - except UnicodeError: - return _encode_entity(text) - text = replace(text, "&", "&") - text = replace(text, "<", "<") - text = replace(text, ">", ">") - return text - except (TypeError, AttributeError): - _raise_serialization_error(text) - -def _escape_attrib(text, encoding=None, replace=string.replace): - # escape attribute value - try: - if encoding: - try: - text = _encode(text, encoding) - except UnicodeError: - return _encode_entity(text) - text = replace(text, "&", "&") - text = replace(text, "'", "'") # FIXME: overkill - text = replace(text, "\"", """) - text = replace(text, "<", "<") - text = replace(text, ">", ">") - 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 = string.split(tag[1:], "}", 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) - else: - xmlns = None - return "%s:%s" % (prefix, tag), xmlns +# -------------------------------------------------------------------- +# 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): @@ -869,18 +1130,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(object): + + 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 @@ -908,7 +1176,7 @@ elif event == "start-ns": def handler(prefix, uri, event=event, append=append): try: - uri = _encode(uri, "ascii") + uri = uri.encode("ascii") except UnicodeError: pass append((event, (prefix or "", uri))) @@ -925,10 +1193,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 @@ -942,24 +1207,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() @@ -968,11 +1231,14 @@ # 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 = {} @@ -993,21 +1259,23 @@ fromstring = XML ## -# Generates a string representation of an XML element, including all -# subelements. +# Parses an XML document from a sequence of string fragments. # -# @param element An Element instance. -# @return An 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) - return string.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 @@ -1020,7 +1288,7 @@ # @param element_factory Optional element factory. This factory # is called to create new Element instances, as necessary. -class TreeBuilder: +class TreeBuilder(object): def __init__(self, element_factory=None): self._data = [] # data collector @@ -1028,11 +1296,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. @@ -1046,7 +1314,7 @@ def _flush(self): if self._data: if self._last is not None: - text = string.join(self._data, "") + text = "".join(self._data) if self._tail: assert self._last.tail is None, "internal error (tail)" self._last.tail = text @@ -1105,22 +1373,30 @@ # 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(object): - 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; expat = pyexpat + 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 @@ -1139,17 +1415,23 @@ parser.StartElementHandler = self._start_list except AttributeError: pass - encoding = None - if not parser.returns_unicode: - 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 _fixtext(self, text): # convert text string to ascii, if possible try: - return _encode(text, "ascii") + return text.encode("ascii") except UnicodeError: return text @@ -1166,40 +1448,46 @@ def _start(self, tag, attrib_in): fixname = self._fixname + fixtext = self._fixtext tag = fixname(tag) attrib = {} for key, value in attrib_in.items(): - attrib[fixname(key)] = self._fixtext(value) - return self._target.start(tag, attrib) + attrib[fixname(key)] = fixtext(value) + return self.target.start(tag, attrib) def _start_list(self, tag, attrib_in): fixname = self._fixname + fixtext = self._fixtext tag = fixname(tag) attrib = {} if attrib_in: for i in range(0, len(attrib_in), 2): - attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1]) - return self._target.start(tag, attrib) + attrib[fixname(attrib_in[i])] = fixtext(attrib_in[i+1]) + return self.target.start(tag, attrib) def _data(self, text): - return self._target.data(self._fixtext(text)) + return self.target.data(self._fixtext(text)) def _end(self, tag): - return self._target.end(self._fixname(tag)) + return self.target.end(self._fixname(tag)) 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] == "": self._doctype = None return - text = string.strip(text) + text = text.strip() if not text: return self._doctype.append(text) @@ -1223,26 +1511,20 @@ return if pubid: pubid = pubid[1:-1] - self.doctype(name, pubid, system[1:-1]) + if hasattr(self.target, "doctype"): + self.target.doctype(name, pubid, system[1:-1]) self._doctype = None ## - # Handles a doctype declaration. - # - # @param name Doctype name. - # @param pubid Public identifier. - # @param system System identifier. - - def doctype(self, name, pubid, system): - pass - - ## # Feeds data to the parser. # # @param data Encoded data. def feed(self, data): - self._parser.Parse(data, 0) + try: + self._parser.Parse(data, 0) + except self._error, v: + self._raiseerror(v) ## # Finishes feeding data to the parser. @@ -1251,10 +1533,13 @@ # @defreturn Element def close(self): - self._parser.Parse("", 1) # end of data - tree = self._target.close() - del self._target, self._parser # get rid of circular references + try: + self._parser.Parse("", 1) # end of data + except self._error, v: + self._raiseerror(v) + tree = self.target.close() + del self.target, self._parser # get rid of circular references return tree # compatibility -XMLParser = XMLTreeBuilder +XMLTreeBuilder = XMLParser diff -r bf3371119d2c Lib/xml/etree/__init__.py --- a/Lib/xml/etree/__init__.py Sun Dec 06 21:22:32 2009 +0100 +++ b/Lib/xml/etree/__init__.py Sun Dec 06 21:43:24 2009 +0100 @@ -1,10 +1,10 @@ -# $Id: __init__.py 1821 2004-06-03 16:57:49Z fredrik $ +# $Id: __init__.py 3265 2007-09-06 20:42:00Z fredrik $ # elementtree package # -------------------------------------------------------------------- # The ElementTree toolkit is # -# Copyright (c) 1999-2004 by Fredrik Lundh +# Copyright (c) 1999-2007 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, @@ -30,4 +30,4 @@ # -------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. +# See http://www.python.org/2.6/license for licensing details. diff -r bf3371119d2c Modules/_elementtree.c --- a/Modules/_elementtree.c Sun Dec 06 21:22:32 2009 +0100 +++ b/Modules/_elementtree.c Sun Dec 06 21:43:24 2009 +0100 @@ -1,14 +1,12 @@ /* * ElementTree - * $Id: _elementtree.c 2657 2006-03-12 20:50:32Z fredrik $ + * $Id: _elementtree.c 3469 2009-01-10 14:30:13Z 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 @@ -35,20 +33,30 @@ * 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) + * 2009-01-10 fl added experimental CAPI interface * - * 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/2.6/license for licensing details. */ #include "Python.h" -#define VERSION "1.0.6" +#include "celementtree.h" /* CAPI stuff */ + +#define VERSION "1.0.6a2" /* -------------------------------------------------------------------- */ /* configuration */ @@ -56,7 +64,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 @@ -123,9 +131,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 */ @@ -330,7 +340,7 @@ if (element_new_extra(self, attrib) < 0) { PyObject_Del(self); return NULL; - } + } self->extra->length = 0; self->extra->allocated = STATIC_CHILDREN; @@ -419,6 +429,14 @@ return 0; } +static PyObject* +element_get_type(void) +{ + /* return borrowed reference to type object */ + + return (PyObject*) &Element_Type; +} + LOCAL(PyObject*) element_get_attrib(ElementObject* self) { @@ -709,6 +727,8 @@ /* add object to memo dictionary (so deepcopy won't visit it again) */ id = PyInt_FromLong((Py_uintptr_t) self); + if (!id) + goto error; i = PyDict_SetItem(memo, id, (PyObject*) element); @@ -732,7 +752,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 defined(Py_USING_UNICODE) if (PyUnicode_Check(tag)) { @@ -765,6 +786,34 @@ } 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) + 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; @@ -831,18 +880,24 @@ static PyObject* element_findall(ElementObject* self, PyObject* args) { - int i; - PyObject* out; + /* int i; + PyObject* out; */ PyObject* tag; if (!PyArg_ParseTuple(args, "O:findall", &tag)) return NULL; - if (checkpath(tag)) + /* Use the Python implementation without condition. + The method will return an iterator in all cases. + See bug 6472. */ + + /* if (checkpath(tag)) */ + return PyObject_CallMethod( elementpath_obj, "findall", "OO", self, tag ); + /* out = PyList_New(0); if (!out) return NULL; @@ -862,6 +917,7 @@ } return out; + */ } static PyObject* @@ -912,18 +968,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; } @@ -935,7 +991,37 @@ 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_itertext(ElementObject* self, PyObject* args) +{ + PyObject* result; + + if (!PyArg_ParseTuple(args, ":itertext")) + return NULL; + + if (!elementtree_itertext_obj) { + PyErr_SetString( + PyExc_RuntimeError, + "itertext helper not found" + ); + return NULL; + } + + 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); @@ -1006,8 +1092,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; @@ -1188,11 +1277,12 @@ } static int -element_setslice(PyObject* self_, Py_ssize_t start, Py_ssize_t end, PyObject* item) +element_setslice(PyObject* self_, Py_ssize_t start, Py_ssize_t end, PyObject* seq_in) { ElementObject* self = (ElementObject*) self_; Py_ssize_t i, new, old; PyObject* recycle = NULL; + PyObject* seq = NULL; if (!self->extra) element_new_extra(self, NULL); @@ -1209,17 +1299,18 @@ old = end - start; - if (item == NULL) + if (seq_in == 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; + else { + seq = PySequence_Fast(seq_in, ""); + if (!seq) { + PyErr_Format( + PyExc_TypeError, + "expected sequence, not \"%.200s\"", Py_TYPE(seq_in)->tp_name + ); + return -1; + } + new = PySequence_Size(seq); } if (old > 0) { @@ -1237,21 +1328,27 @@ self->extra->children[i + new - old] = self->extra->children[i]; } else if (new > old) { /* insert slice */ - if (element_resize(self, new - old) < 0) + if (element_resize(self, new - old) < 0) { + if (seq) + Py_DECREF(seq); 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); + PyObject* element = PySequence_Fast_GET_ITEM(seq, i); Py_INCREF(element); self->extra->children[i + start] = element; } self->extra->length += new - old; + if (seq) + Py_DECREF(seq); + /* discard the recycle bin, and everything in it */ Py_XDECREF(recycle); @@ -1300,10 +1397,14 @@ {"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}, + + {"getiterator", (PyCFunction) element_iter, METH_VARARGS}, {"getchildren", (PyCFunction) element_getchildren, METH_VARARGS}, {"items", (PyCFunction) element_items, METH_VARARGS}, @@ -1336,12 +1437,12 @@ res = Py_FindMethod(element_methods, (PyObject*) self, name); if (res) - return res; + return res; PyErr_Clear(); if (strcmp(name, "tag") == 0) - res = self->tag; + res = self->tag; else if (strcmp(name, "text") == 0) res = element_get_text(self); else if (strcmp(name, "tail") == 0) { @@ -1349,7 +1450,7 @@ } else if (strcmp(name, "attrib") == 0) { if (!self->extra) element_new_extra(self, NULL); - res = element_get_attrib(self); + res = element_get_attrib(self); } else { PyErr_SetString(PyExc_AttributeError, name); return NULL; @@ -1423,6 +1524,71 @@ &element_as_sequence, /* tp_as_sequence */ }; +/* -------------------------------------------------------------------- */ +/* CAPI interface */ + +static int +element_capi_assert(PyObject* elem) +{ + if (Element_CheckExact(elem)) + return 0; + PyErr_Format( + PyExc_TypeError, + "expected cElementTree.Element, not \"%.200s\"", + elem->ob_type->tp_name + ); + return -1; +} + +static PyObject* +element_capi_getitem(PyObject* self, int index) +{ + ElementObject* elem = (ElementObject*) self; + + if (!elem->extra || index < 0 || index >= elem->extra->length) + Py_RETURN_NONE; + + Py_INCREF(elem->extra->children[index]); + return elem->extra->children[index]; +} + +static int +element_capi_snapshot(PyObject* self, struct cElementTree_Snapshot* snapshot, + int mode) +{ + ElementObject* elem = (ElementObject*) self; + + snapshot->tag = elem->tag; + Py_INCREF(snapshot->tag); + + snapshot->text = element_get_text(elem); + Py_INCREF(snapshot->text); + + snapshot->tail = element_get_tail(elem); + Py_INCREF(snapshot->tail); + + if (elem->extra) + snapshot->attrib = element_get_attrib(elem); + else + snapshot->attrib = Py_None; + Py_INCREF(snapshot->attrib); + + if (!mode) + return 0; + + if (elem->extra) { + snapshot->children = element_getslice( + (PyObject*) elem, 0, elem->extra->length + ); + /* FIXME: clean up if slice fails */ + } else { + snapshot->children = Py_None; + Py_INCREF(snapshot->children); + } + + return 0; +} + /* ==================================================================== */ /* the tree builder type */ @@ -1558,7 +1724,7 @@ } else { if (self->root) { PyErr_SetString( - PyExc_SyntaxError, + elementtree_parseerror_obj, "multiple elements on top level" ); goto error; @@ -1857,6 +2023,7 @@ PyObject* names; PyObject* handle_xml; + PyObject* handle_start; PyObject* handle_data; PyObject* handle_end; @@ -1864,6 +2031,8 @@ PyObject* handle_comment; PyObject* handle_pi; + PyObject* handle_close; + } XMLParserObject; staticforward PyTypeObject XMLParser_Type; @@ -1971,6 +2140,32 @@ 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) + return; + if (PyObject_SetAttrString(error, "position", position) == -1) { + Py_DECREF(position); + return; + } + Py_DECREF(position); + + PyErr_SetObject(elementtree_parseerror_obj, error); +} + /* -------------------------------------------------------------------- */ /* handlers */ @@ -2002,9 +2197,10 @@ res = NULL; Py_XDECREF(res); } else { - PyErr_Format( - PyExc_SyntaxError, "undefined entity &%s;: line %ld, column %ld", - PyString_AS_STRING(key), + char message[128]; + sprintf(message, "undefined entity &%.100s;", PyString_AS_STRING(key)); + expat_set_error( + message, EXPAT(GetErrorLineNumber)(self->parser), EXPAT(GetErrorColumnNumber)(self->parser) ); @@ -2059,9 +2255,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); @@ -2200,10 +2402,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); @@ -2288,6 +2490,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(); @@ -2333,6 +2536,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); @@ -2363,8 +2567,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) @@ -2385,13 +2588,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* @@ -2575,14 +2782,14 @@ res = Py_FindMethod(xmlparser_methods, (PyObject*) self, name); if (res) - return res; + return res; 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, @@ -2628,9 +2835,8 @@ PyObject* m; PyObject* g; char* bootstrap; -#if defined(USE_PYEXPAT_CAPI) - struct PyExpat_CAPI* capi; -#endif + static struct cElementTree_CAPI capi; + PyObject* capi_object; /* Patch object type */ Py_TYPE(&Element_Type) = Py_TYPE(&TreeBuilder_Type) = &PyType_Type; @@ -2673,11 +2879,14 @@ " 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 __cmp__(self, other):\n" + " return cmp(ET.Comment, other)\n" + "cElementTree.Comment = CommentProxy()\n" "class ElementTree(ET.ElementTree):\n" /* public */ " def parse(self, source, parser=None):\n" @@ -2696,7 +2905,7 @@ " 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) @@ -2704,16 +2913,25 @@ " if tag is None or node.tag == tag:\n" " nodes.append(node)\n" " for node in node:\n" - " nodes.extend(getiterator(node, tag))\n" + " nodes.extend(iter(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" " tree.parse(source, parser)\n" @@ -2730,38 +2948,47 @@ " 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 __cmp__(self, other):\n" + " return cmp(ET.PI, other)\n" + "cElementTree.PI = cElementTree.ProcessingInstruction = PIProxy()\n" "def XML(text):\n" /* public */ " parser = cElementTree.XMLParser()\n" @@ -2779,6 +3006,13 @@ " 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" @@ -2786,11 +3020,11 @@ "cElementTree.tostring = ET.tostring\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; elementpath_obj = PyDict_GetItemString(g, "ElementPath"); @@ -2805,21 +3039,41 @@ } } 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 = PyCObject_Import("pyexpat", "expat_CAPI"); - 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 = PyCObject_Import("pyexpat", "expat_CAPI"); + 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 + elementtree_parseerror_obj = PyErr_NewException( + "cElementTree.ParseError", PyExc_SyntaxError, NULL + ); + Py_INCREF(elementtree_parseerror_obj); + PyModule_AddObject(m, "ParseError", elementtree_parseerror_obj); + + capi.size = sizeof(capi); + capi.magic = cElementTree_CAPI_MAGIC; + capi.version = cElementTree_CAPI_VERSION; + capi.type = (PyObject*) &Element_Type; + capi.assert = element_capi_assert; + capi.snapshot = element_capi_snapshot; + capi.getitem = element_capi_getitem; + + /* export as cobject */ + capi_object = PyCObject_FromVoidPtr(&capi, NULL); + if (capi_object) + PyModule_AddObject(m, "CAPI", capi_object); } diff -r bf3371119d2c Modules/celementtree.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Modules/celementtree.h Sun Dec 06 21:43:24 2009 +0100 @@ -0,0 +1,77 @@ +/* Stuff to export relevant entry points from cElementTree */ + +/* To use this API, do + + struct cElementTree_CAPI *capi; + capi = PyCObject_Import("cElementTree", "CAPI"); + + Also check that capi->magic and capi->size matches the values from + this header, e.g: + + if (strcmp(capi->magic, cElementTree_CAPI_MAGIC) != 0 || + capi->size < sizeof(struct cElementTree_CAPI) || + capi->version < cElementTree_CAPI_VERSION) + ... cannot use this version ... + + See below for a summary of the available API functions. + +*/ + +/* major version */ +#define cElementTree_CAPI_MAGIC "cElementTree.CAPI 1.0" + +/* minor version. new minor versions should always be backwards compatible */ +#define cElementTree_CAPI_VERSION 1 + +/* struct to store a snapshot of a given Element */ +struct cElementTree_Snapshot +{ + /* filled in by snapshot method. members may be Py_None if not present + in the internal structure. all references are new; use DECREF to clean + up */ + PyObject* tag; + PyObject* attrib; + PyObject* text; + PyObject* tail; + PyObject* children; /* only filled in if mode > 0 */ + + /* always add new stuff to the end! */ +}; + +struct cElementTree_CAPI +{ + char* magic; /* set to cElementTree_CAPI_MAGIC */ + int size; /* set to sizeof(struct cElementTree_CAPI) */ + int version; /* set to cElementTree_CAPI_VERSION (API version) */ + + /* pointers to selected cElementTree helpers. add new functions at + the end, if needed */ + + /* Element type (to be used for exact type comparisions) */ + PyObject* type; + + /* Check if object is a cElementTree Element, and raises a TypeError + if not. Use this before calling other methods. */ + /* Returns 0 if ok, -1 if error. */ + int (*assert)(PyObject* elem); + + /* Get Element snapshot. Use mode=1 to get children too. */ + /* The 'elem' argument must point to a cElementTree Element. Use + 'assert' first when in doubt. */ + /* All references are new, and point to stable versions (that is, + changing the original Element won't affect the snapshot). */ + /* Py_None may be used for non-existent/empty fields. */ + /* Returns 0 if ok, -1 if error. */ + int (*snapshot)( + PyObject* elem, struct cElementTree_Snapshot* snapshot, int mode + ); + + /* Get subelement. */ + /* The 'elem' argument must point to a cElementTree Element. Use + 'assert' first when in doubt. */ + /* Returns a borrowed reference, or Py_None if the element does not + exist. */ + PyObject* (*getitem)(PyObject* elem, int index); + + /* always add new stuff to the end! */ +};