ÿØÿà JFIF    ÿÛ „ ( %!1!%*+...983,7(-.- lib64/python3.8/xml/etree/ElementTree.py000064400000216331152357731610013745 0ustar00"""Lightweight XML support for Python. XML is an inherently hierarchical data format, and the most natural way to represent it is with a tree. This module has two classes for this purpose: 1. ElementTree represents the whole XML document as a tree and 2. Element represents a single node in this tree. Interactions with the whole document (reading and writing to/from files) are usually done on the ElementTree level. Interactions with a single XML element and its sub-elements are done on the Element level. Element is a flexible container object designed to store hierarchical data structures in memory. It can be described as a cross between a list and a dictionary. Each Element has a number of properties associated with it: 'tag' - a string containing the element's name. 'attributes' - a Python dictionary storing the element's attributes. 'text' - a string containing the element's text content. 'tail' - an optional string containing text after the element's end tag. And a number of child elements stored in a Python sequence. To create an element instance, use the Element constructor, or the SubElement factory function. You can also use the ElementTree class to wrap an element structure and convert it to and from XML. """ #--------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/psf/license for licensing details. # # ElementTree # Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com # -------------------------------------------------------------------- # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- __all__ = [ # public symbols "Comment", "dump", "Element", "ElementTree", "fromstring", "fromstringlist", "iselement", "iterparse", "parse", "ParseError", "PI", "ProcessingInstruction", "QName", "SubElement", "tostring", "tostringlist", "TreeBuilder", "VERSION", "XML", "XMLID", "XMLParser", "XMLPullParser", "register_namespace", "canonicalize", "C14NWriterTarget", ] VERSION = "1.3.0" import sys import re import warnings import io import collections import collections.abc import contextlib from . import ElementPath class ParseError(SyntaxError): """An error when parsing an XML document. In addition to its exception value, a ParseError contains two extra attributes: 'code' - the specific exception code 'position' - the line and column of the error """ pass # -------------------------------------------------------------------- def iselement(element): """Return True if *element* appears to be an Element.""" return hasattr(element, 'tag') class Element: """An XML element. This class is the reference implementation of the Element interface. An element's length is its number of subelements. That means if you want to check if an element is truly empty, you should check BOTH its length AND its text attribute. The element tag, attribute names, and attribute values can be either bytes or strings. *tag* is the element name. *attrib* is an optional dictionary containing element attributes. *extra* are additional element attributes given as keyword arguments. Example form: text...tail """ tag = None """The element's name.""" attrib = None """Dictionary of the element's attributes.""" text = None """ Text before first subelement. This is either a string or the value None. Note that if there is no text, this attribute may be either None or the empty string, depending on the parser. """ tail = None """ Text after this element's end tag, but before the next sibling element's start tag. This is either a string or the value None. Note that if there was no text, this attribute may be either None or an empty string, depending on the parser. """ def __init__(self, tag, attrib={}, **extra): if not isinstance(attrib, dict): raise TypeError("attrib must be dict, not %s" % ( attrib.__class__.__name__,)) self.tag = tag self.attrib = {**attrib, **extra} self._children = [] def __repr__(self): return "<%s %r at %#x>" % (self.__class__.__name__, self.tag, id(self)) def makeelement(self, tag, attrib): """Create a new element with the same type. *tag* is a string containing the element name. *attrib* is a dictionary containing the element attributes. Do not call this method, use the SubElement factory function instead. """ return self.__class__(tag, attrib) def copy(self): """Return copy of current element. This creates a shallow copy. Subelements will be shared with the original tree. """ elem = self.makeelement(self.tag, self.attrib) elem.text = self.text elem.tail = self.tail elem[:] = self return elem def __len__(self): return len(self._children) def __bool__(self): warnings.warn( "The behavior of this method will change in future versions. " "Use specific 'len(elem)' or 'elem is not None' test instead.", FutureWarning, stacklevel=2 ) return len(self._children) != 0 # emulate old behaviour, for now def __getitem__(self, index): return self._children[index] def __setitem__(self, index, element): if isinstance(index, slice): for elt in element: self._assert_is_element(elt) else: self._assert_is_element(element) self._children[index] = element def __delitem__(self, index): del self._children[index] def append(self, subelement): """Add *subelement* to the end of this element. The new element will appear in document order after the last existing subelement (or directly after the text, if it's the first subelement), but before the end tag for this element. """ self._assert_is_element(subelement) self._children.append(subelement) def extend(self, elements): """Append subelements from a sequence. *elements* is a sequence with zero or more elements. """ for element in elements: self._assert_is_element(element) self._children.append(element) def insert(self, index, subelement): """Insert *subelement* at position *index*.""" self._assert_is_element(subelement) self._children.insert(index, subelement) def _assert_is_element(self, e): # Need to refer to the actual Python implementation, not the # shadowing C implementation. if not isinstance(e, _Element_Py): raise TypeError('expected an Element, not %s' % type(e).__name__) def remove(self, subelement): """Remove matching subelement. Unlike the find methods, this method compares elements based on identity, NOT ON tag value or contents. To remove subelements by other means, the easiest way is to use a list comprehension to select what elements to keep, and then use slice assignment to update the parent element. ValueError is raised if a matching element could not be found. """ # assert iselement(element) self._children.remove(subelement) def getchildren(self): """(Deprecated) Return all subelements. Elements are returned in document order. """ warnings.warn( "This method will be removed in future versions. " "Use 'list(elem)' or iteration over elem instead.", DeprecationWarning, stacklevel=2 ) return self._children def find(self, path, namespaces=None): """Find first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ return ElementPath.find(self, path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find text for first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *default* is the value to return if the element was not found, *namespaces* is an optional mapping from namespace prefix to full name. Return text content of first matching element, or default value if none was found. Note that if an element is found having no text content, the empty string is returned. """ return ElementPath.findtext(self, path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Returns list containing all matching elements in document order. """ return ElementPath.findall(self, path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ return ElementPath.iterfind(self, path, namespaces) def clear(self): """Reset element. This function removes all subelements, clears all attributes, and sets the text and tail attributes to None. """ self.attrib.clear() self._children = [] self.text = self.tail = None def get(self, key, default=None): """Get element attribute. Equivalent to attrib.get, but some implementations may handle this a bit more efficiently. *key* is what attribute to look for, and *default* is what to return if the attribute was not found. Returns a string containing the attribute value, or the default if attribute was not found. """ return self.attrib.get(key, default) def set(self, key, value): """Set element attribute. Equivalent to attrib[key] = value, but some implementations may handle this a bit more efficiently. *key* is what attribute to set, and *value* is the attribute value to set it to. """ self.attrib[key] = value def keys(self): """Get list of attribute names. Names are returned in an arbitrary order, just like an ordinary Python dict. Equivalent to attrib.keys() """ return self.attrib.keys() def items(self): """Get element attributes as a sequence. The attributes are returned in arbitrary order. Equivalent to attrib.items(). Return a list of (name, value) tuples. """ return self.attrib.items() def iter(self, tag=None): """Create tree iterator. The iterator loops over the element and all subelements in document order, returning all elements with a matching tag. 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. *tag* is what tags to look for (default is to return all elements) Return an iterator containing all the matching elements. """ if tag == "*": tag = None if tag is None or self.tag == tag: yield self for e in self._children: yield from e.iter(tag) # compatibility def getiterator(self, tag=None): warnings.warn( "This method will be removed in future versions. " "Use 'elem.iter()' or 'list(elem.iter())' instead.", DeprecationWarning, stacklevel=2 ) return list(self.iter(tag)) def itertext(self): """Create text iterator. The iterator loops over the element and all subelements in document order, returning all inner text. """ tag = self.tag if not isinstance(tag, str) and tag is not None: return t = self.text if t: yield t for e in self: yield from e.itertext() t = e.tail if t: yield t def SubElement(parent, tag, attrib={}, **extra): """Subelement factory which creates an element instance, and appends it to an existing parent. The element tag, attribute names, and attribute values can be either bytes or Unicode strings. *parent* is the parent element, *tag* is the subelements name, *attrib* is an optional directory containing element attributes, *extra* are additional attributes given as keyword arguments. """ attrib = {**attrib, **extra} element = parent.makeelement(tag, attrib) parent.append(element) return element def Comment(text=None): """Comment element factory. This function creates a special element which the standard serializer serializes as an XML comment. *text* is a string containing the comment string. """ element = Element(Comment) element.text = text return element def ProcessingInstruction(target, text=None): """Processing Instruction element factory. This function creates a special element which the standard serializer serializes as an XML comment. *target* is a string containing the processing instruction, *text* is a string containing the processing instruction contents, if any. """ element = Element(ProcessingInstruction) element.text = target if text: element.text = element.text + " " + text return element PI = ProcessingInstruction class QName: """Qualified name wrapper. This class can be used to wrap a QName attribute value in order to get proper namespace handing on output. *text_or_uri* is a string containing the QName value either in the form {uri}local, or if the tag argument is given, the URI part of a QName. *tag* is an optional argument which if given, will make the first argument (text_or_uri) be interpreted as a URI, and this argument (tag) be interpreted as a local name. """ def __init__(self, text_or_uri, tag=None): if tag: text_or_uri = "{%s}%s" % (text_or_uri, tag) self.text = text_or_uri def __str__(self): return self.text def __repr__(self): return '<%s %r>' % (self.__class__.__name__, self.text) def __hash__(self): return hash(self.text) def __le__(self, other): if isinstance(other, QName): return self.text <= other.text return self.text <= other def __lt__(self, other): if isinstance(other, QName): return self.text < other.text return self.text < other def __ge__(self, other): if isinstance(other, QName): return self.text >= other.text return self.text >= other def __gt__(self, other): if isinstance(other, QName): return self.text > other.text return self.text > other def __eq__(self, other): if isinstance(other, QName): return self.text == other.text return self.text == other # -------------------------------------------------------------------- class ElementTree: """An XML element hierarchy. This class also provides support for serialization to and from standard XML. *element* is an optional root element node, *file* is an optional file handle or file name of an XML file whose contents will be used to initialize the tree with. """ def __init__(self, element=None, file=None): # assert element is None or iselement(element) self._root = element # first node if file: self.parse(file) def getroot(self): """Return root element of this tree.""" return self._root def _setroot(self, element): """Replace root element of this tree. This will discard the current contents of the tree and replace it with the given element. Use with care! """ # assert iselement(element) self._root = element def parse(self, source, parser=None): """Load external XML document into element tree. *source* is a file name or file object, *parser* is an optional parser instance that defaults to XMLParser. ParseError is raised if the parser fails to parse the document. Returns the root element of the given source document. """ close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if parser is None: # If no parser was specified, create a default XMLParser parser = XMLParser() if hasattr(parser, '_parse_whole'): # The default XMLParser, when it comes from an accelerator, # can define an internal _parse_whole API for efficiency. # It can be used to parse the whole source without feeding # it with chunks. self._root = parser._parse_whole(source) return self._root while True: data = source.read(65536) if not data: break parser.feed(data) self._root = parser.close() return self._root finally: if close_source: source.close() def iter(self, tag=None): """Create and return tree iterator for the root element. The iterator loops over all elements in this tree, in document order. *tag* is a string with the tag name to iterate over (default is to return all elements). """ # assert self._root is not None return self._root.iter(tag) # compatibility def getiterator(self, tag=None): warnings.warn( "This method will be removed in future versions. " "Use 'tree.iter()' or 'list(tree.iter())' instead.", DeprecationWarning, stacklevel=2 ) return list(self.iter(tag)) def find(self, path, namespaces=None): """Find first matching element by tag name or path. Same as getroot().find(path), which is Element.find() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.find(path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find first matching element by tag name or path. Same as getroot().findtext(path), which is Element.findtext() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.findtext(path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().findall(path), which is Element.findall(). *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return list containing all matching elements in document order. """ # 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.findall(path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().iterfind(path), which is element.iterfind() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ # 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) def write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True): """Write element tree to a file as XML. Arguments: *file_or_filename* -- file name or a file object opened for writing *encoding* -- the output encoding (default: US-ASCII) *xml_declaration* -- bool indicating if an XML declaration should be added to the output. If None, an XML declaration is added if encoding IS NOT either of: US-ASCII, UTF-8, or Unicode *default_namespace* -- sets the default XML namespace (for "xmlns") *method* -- either "xml" (default), "html, "text", or "c14n" *short_empty_elements* -- controls the formatting of elements that contain no content. If True (default) they are emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags """ if not method: method = "xml" elif method not in _serialize: raise ValueError("unknown method %r" % method) if not encoding: if method == "c14n": encoding = "utf-8" else: encoding = "us-ascii" enc_lower = encoding.lower() with _get_writer(file_or_filename, enc_lower) as write: if method == "xml" and (xml_declaration or (xml_declaration is None and enc_lower not in ("utf-8", "us-ascii", "unicode"))): declared_encoding = encoding if enc_lower == "unicode": # Retrieve the default encoding for the xml declaration import locale declared_encoding = locale.getpreferredencoding() write("\n" % ( declared_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, short_empty_elements=short_empty_elements) def write_c14n(self, file): # lxml.etree compatibility. use output method instead return self.write(file, method="c14n") # -------------------------------------------------------------------- # serialization support @contextlib.contextmanager def _get_writer(file_or_filename, encoding): # returns text write method and release all resources after using try: write = file_or_filename.write except AttributeError: # file_or_filename is a file name if encoding == "unicode": file = open(file_or_filename, "w") else: file = open(file_or_filename, "w", encoding=encoding, errors="xmlcharrefreplace") with file: yield file.write else: # file_or_filename is a file-like object # encoding determines if it is a text or binary writer if encoding == "unicode": # use a text writer as is yield write else: # wrap a binary writer with TextIOWrapper with contextlib.ExitStack() as stack: if isinstance(file_or_filename, io.BufferedIOBase): file = file_or_filename elif isinstance(file_or_filename, io.RawIOBase): file = io.BufferedWriter(file_or_filename) # Keep the original file open when the BufferedWriter is # destroyed stack.callback(file.detach) else: # This is to handle passed objects that aren't in the # IOBase hierarchy, but just have a write method file = io.BufferedIOBase() file.writable = lambda: True file.write = write try: # TextIOWrapper uses this methods to determine # if BOM (for UTF-16, etc) should be added file.seekable = file_or_filename.seekable file.tell = file_or_filename.tell except AttributeError: pass file = io.TextIOWrapper(file, encoding=encoding, errors="xmlcharrefreplace", newline="\n") # Keep the original file open when the TextIOWrapper is # destroyed stack.callback(file.detach) yield file.write def _namespaces(elem, 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 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) # populate qname and namespaces table for elem in elem.iter(): tag = elem.tag if isinstance(tag, QName): if 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, short_empty_elements, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % text) elif tag is ProcessingInstruction: write("" % text) else: tag = qnames[tag] if tag is None: if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) 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 items: 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) or not short_empty_elements: write(">") if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) write("") 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, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % _escape_cdata(text)) elif tag is ProcessingInstruction: write("" % _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 items: 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(">") ltag = tag.lower() if text: if ltag == "script" or ltag == "style": write(text) else: write(_escape_cdata(text)) for e in elem: _serialize_html(write, e, qnames, None) if ltag not in HTML_EMPTY: write("") 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, } def register_namespace(prefix, uri): """Register a namespace prefix. The registry is global, and any existing mapping for either the given prefix or the namespace URI will be removed. *prefix* is the namespace prefix, *uri* is a namespace uri. Tags and attributes in this namespace will be serialized with prefix if possible. ValueError is raised if prefix is reserved or is invalid. """ if re.match(r"ns\d+$", prefix): raise ValueError("Prefix format reserved for internal use") for k, v in list(_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", } # For tests and troubleshooting register_namespace._namespace_map = _namespace_map def _raise_serialization_error(text): raise TypeError( "cannot serialize %r (type %s)" % (text, type(text).__name__) ) def _escape_cdata(text): # escape character data try: # it's worth avoiding do-nothing calls for strings that are # shorter than 500 characters, 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) def _escape_attrib(text): # 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("\"", """) # The following business with carriage returns is to satisfy # Section 2.11 of the XML specification, stating that # CR or CR LN should be replaced with just LN # http://www.w3.org/TR/REC-xml/#sec-line-ends if "\r\n" in text: text = text.replace("\r\n", "\n") if "\r" in text: text = text.replace("\r", "\n") #The following four lines are issue 17582 if "\n" in text: text = text.replace("\n", " ") if "\t" in text: text = text.replace("\t", " ") return text except (TypeError, AttributeError): _raise_serialization_error(text) def _escape_attrib_html(text): # escape attribute value try: 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 tostring(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True): """Generate string representation of XML element. All subelements are included. If encoding is "unicode", a string is returned. Otherwise a bytestring is returned. *element* is an Element instance, *encoding* is an optional output encoding defaulting to US-ASCII, *method* is an optional output which can be one of "xml" (default), "html", "text" or "c14n", *default_namespace* sets the default XML namespace (for "xmlns"). Returns an (optionally) encoded string containing the XML data. """ stream = io.StringIO() if encoding == 'unicode' else io.BytesIO() ElementTree(element).write(stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements) return stream.getvalue() class _ListDataStream(io.BufferedIOBase): """An auxiliary stream accumulating into a list reference.""" def __init__(self, lst): self.lst = lst def writable(self): return True def seekable(self): return True def write(self, b): self.lst.append(b) def tell(self): return len(self.lst) def tostringlist(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True): lst = [] stream = _ListDataStream(lst) ElementTree(element).write(stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements) return lst def dump(elem): """Write element tree or element structure to sys.stdout. This function should be used for debugging only. *elem* is either an ElementTree, or a single Element. The exact output format is implementation dependent. In this version, it's written as an ordinary XML file. """ # debugging if not isinstance(elem, ElementTree): elem = ElementTree(elem) elem.write(sys.stdout, encoding="unicode") tail = elem.getroot().tail if not tail or tail[-1] != "\n": sys.stdout.write("\n") # -------------------------------------------------------------------- # parsing def parse(source, parser=None): """Parse XML document into element tree. *source* is a filename or file object containing XML data, *parser* is an optional parser instance defaulting to XMLParser. Return an ElementTree instance. """ tree = ElementTree() tree.parse(source, parser) return tree def iterparse(source, events=None, parser=None): """Incrementally parse XML document into ElementTree. This class also reports what's going on to the user based on the *events* it is initialized with. The supported events are the strings "start", "end", "start-ns" and "end-ns" (the "ns" events are used to get detailed namespace information). If *events* is omitted, only "end" events are reported. *source* is a filename or file object containing XML data, *events* is a list of events to report back, *parser* is an optional parser instance. Returns an iterator providing (event, elem) pairs. """ # Use the internal, undocumented _parser argument for now; When the # parser argument of iterparse is removed, this can be killed. pullparser = XMLPullParser(events=events, _parser=parser) def iterator(): try: while True: yield from pullparser.read_events() # load event buffer data = source.read(16 * 1024) if not data: break pullparser.feed(data) root = pullparser._close_and_return_root() yield from pullparser.read_events() it.root = root finally: if close_source: source.close() class IterParseIterator(collections.abc.Iterator): __next__ = iterator().__next__ it = IterParseIterator() it.root = None del iterator, IterParseIterator close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True return it class XMLPullParser: def __init__(self, events=None, *, _parser=None): # The _parser argument is for internal use only and must not be relied # upon in user code. It will be removed in a future release. # See http://bugs.python.org/issue17741 for more details. self._events_queue = collections.deque() self._parser = _parser or XMLParser(target=TreeBuilder()) # wire up the parser for event reporting if events is None: events = ("end",) self._parser._setevents(self._events_queue, events) def feed(self, data): """Feed encoded data to parser.""" if self._parser is None: raise ValueError("feed() called after end of stream") if data: try: self._parser.feed(data) except SyntaxError as exc: self._events_queue.append(exc) def _close_and_return_root(self): # iterparse needs this to set its root attribute properly :( root = self._parser.close() self._parser = None return root def close(self): """Finish feeding data to parser. Unlike XMLParser, does not return the root element. Use read_events() to consume elements from XMLPullParser. """ self._close_and_return_root() def read_events(self): """Return an iterator over currently available (event, elem) pairs. Events are consumed from the internal event queue as they are retrieved from the iterator. """ events = self._events_queue while events: event = events.popleft() if isinstance(event, Exception): raise event else: yield event def XML(text, parser=None): """Parse XML document from string constant. This function can be used to embed "XML Literals" in Python code. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) return parser.close() def XMLID(text, parser=None): """Parse XML document from string constant for its IDs. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an (Element, dict) tuple, in which the dict maps element id:s to elements. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) tree = parser.close() ids = {} for elem in tree.iter(): id = elem.get("id") if id: ids[id] = elem return tree, ids # Parse XML document from string constant. Alias for XML(). fromstring = XML def fromstringlist(sequence, parser=None): """Parse XML document from sequence of string fragments. *sequence* is a list of other sequence, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) for text in sequence: parser.feed(text) return parser.close() # -------------------------------------------------------------------- class TreeBuilder: """Generic element structure builder. This builder converts a sequence of start, data, and end method calls to a well-formed element structure. You can use this class to build an element structure using a custom XML parser, or a parser for some other XML-like format. *element_factory* is an optional element factory which is called to create new Element instances, as necessary. *comment_factory* is a factory to create comments to be used instead of the standard factory. If *insert_comments* is false (the default), comments will not be inserted into the tree. *pi_factory* is a factory to create processing instructions to be used instead of the standard factory. If *insert_pis* is false (the default), processing instructions will not be inserted into the tree. """ def __init__(self, element_factory=None, *, comment_factory=None, pi_factory=None, insert_comments=False, insert_pis=False): self._data = [] # data collector self._elem = [] # element stack self._last = None # last element self._root = None # root element self._tail = None # true if we're after an end tag if comment_factory is None: comment_factory = Comment self._comment_factory = comment_factory self.insert_comments = insert_comments if pi_factory is None: pi_factory = ProcessingInstruction self._pi_factory = pi_factory self.insert_pis = insert_pis if element_factory is None: element_factory = Element self._factory = element_factory def close(self): """Flush builder buffers and return toplevel document Element.""" assert len(self._elem) == 0, "missing end tags" assert self._root is not None, "missing toplevel element" return self._root def _flush(self): if self._data: if self._last is not None: text = "".join(self._data) if self._tail: assert self._last.tail is None, "internal error (tail)" self._last.tail = text else: assert self._last.text is None, "internal error (text)" self._last.text = text self._data = [] def data(self, data): """Add text to current element.""" self._data.append(data) def start(self, tag, attrs): """Open new element and return it. *tag* is the element name, *attrs* is a dict containing element attributes. """ self._flush() self._last = elem = self._factory(tag, attrs) if self._elem: self._elem[-1].append(elem) elif self._root is None: self._root = elem self._elem.append(elem) self._tail = 0 return elem def end(self, tag): """Close and return current Element. *tag* is the element name. """ self._flush() self._last = self._elem.pop() assert self._last.tag == tag,\ "end tag mismatch (expected %s, got %s)" % ( self._last.tag, tag) self._tail = 1 return self._last def comment(self, text): """Create a comment using the comment_factory. *text* is the text of the comment. """ return self._handle_single( self._comment_factory, self.insert_comments, text) def pi(self, target, text=None): """Create a processing instruction using the pi_factory. *target* is the target name of the processing instruction. *text* is the data of the processing instruction, or ''. """ return self._handle_single( self._pi_factory, self.insert_pis, target, text) def _handle_single(self, factory, insert, *args): elem = factory(*args) if insert: self._flush() self._last = elem if self._elem: self._elem[-1].append(elem) self._tail = 1 return elem # also see ElementTree and TreeBuilder class XMLParser: """Element structure builder for XML source data based on the expat parser. *target* is an optional target object which defaults to an instance of the standard TreeBuilder class, *encoding* is an optional encoding string which if given, overrides the encoding specified in the XML file: http://www.iana.org/assignments/character-sets """ def __init__(self, *, target=None, encoding=None): try: from xml.parsers import expat except ImportError: 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() # 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 # main callbacks parser.DefaultHandlerExpand = self._default if hasattr(target, 'start'): parser.StartElementHandler = self._start if hasattr(target, 'end'): parser.EndElementHandler = self._end if hasattr(target, 'start_ns'): parser.StartNamespaceDeclHandler = self._start_ns if hasattr(target, 'end_ns'): parser.EndNamespaceDeclHandler = self._end_ns if hasattr(target, 'data'): parser.CharacterDataHandler = target.data # miscellaneous callbacks if hasattr(target, 'comment'): parser.CommentHandler = target.comment if hasattr(target, 'pi'): parser.ProcessingInstructionHandler = target.pi # Configure pyexpat: buffering, new-style attribute handling. parser.buffer_text = 1 parser.ordered_attributes = 1 parser.specified_attributes = 1 self._doctype = None self.entity = {} try: self.version = "Expat %d.%d.%d" % expat.version_info except AttributeError: pass # unknown def _setevents(self, events_queue, events_to_report): # Internal API for XMLPullParser # events_to_report: a list of events to report during parsing (same as # the *events* of XMLPullParser's constructor. # events_queue: a list of actual parsing events that will be populated # by the underlying parser. # parser = self._parser append = events_queue.append for event_name in events_to_report: if event_name == "start": parser.ordered_attributes = 1 parser.specified_attributes = 1 def handler(tag, attrib_in, event=event_name, append=append, start=self._start): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler elif event_name == "end": def handler(tag, event=event_name, append=append, end=self._end): append((event, end(tag))) parser.EndElementHandler = handler elif event_name == "start-ns": # TreeBuilder does not implement .start_ns() if hasattr(self.target, "start_ns"): def handler(prefix, uri, event=event_name, append=append, start_ns=self._start_ns): append((event, start_ns(prefix, uri))) else: def handler(prefix, uri, event=event_name, append=append): append((event, (prefix or '', uri or ''))) parser.StartNamespaceDeclHandler = handler elif event_name == "end-ns": # TreeBuilder does not implement .end_ns() if hasattr(self.target, "end_ns"): def handler(prefix, event=event_name, append=append, end_ns=self._end_ns): append((event, end_ns(prefix))) else: def handler(prefix, event=event_name, append=append): append((event, None)) parser.EndNamespaceDeclHandler = handler elif event_name == 'comment': def handler(text, event=event_name, append=append, self=self): append((event, self.target.comment(text))) parser.CommentHandler = handler elif event_name == 'pi': def handler(pi_target, data, event=event_name, append=append, self=self): append((event, self.target.pi(pi_target, data))) parser.ProcessingInstructionHandler = handler else: raise ValueError("unknown event %r" % event_name) 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 try: name = self._names[key] except KeyError: name = key if "}" in name: name = "{" + name self._names[key] = name return name def _start_ns(self, prefix, uri): return self.target.start_ns(prefix or '', uri or '') def _end_ns(self, prefix): return self.target.end_ns(prefix or '') def _start(self, tag, attr_list): # Handler for expat's StartElementHandler. Since ordered_attributes # is set, the attributes are reported as a list of alternating # attribute name,value. fixname = self._fixname tag = fixname(tag) attrib = {} if attr_list: for i in range(0, len(attr_list), 2): attrib[fixname(attr_list[i])] = attr_list[i+1] return self.target.start(tag, attrib) def _end(self, tag): return self.target.end(self._fixname(tag)) def _default(self, text): prefix = text[:1] if prefix == "&": # deal with undefined entities try: data_handler = self.target.data except AttributeError: return try: data_handler(self.entity[text[1:-1]]) except KeyError: from xml.parsers import expat 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 = text.strip() if not text: return self._doctype.append(text) n = len(self._doctype) if n > 2: type = self._doctype[1] if type == "PUBLIC" and n == 4: name, type, pubid, system = self._doctype if pubid: pubid = pubid[1:-1] elif type == "SYSTEM" and n == 3: name, type, system = self._doctype pubid = None else: return if hasattr(self.target, "doctype"): self.target.doctype(name, pubid, system[1:-1]) elif hasattr(self, "doctype"): warnings.warn( "The doctype() method of XMLParser is ignored. " "Define doctype() method on the TreeBuilder target.", RuntimeWarning) self._doctype = None def feed(self, data): """Feed encoded data to parser.""" try: self.parser.Parse(data, 0) except self._error as v: self._raiseerror(v) def close(self): """Finish feeding data to parser and return element structure.""" try: self.parser.Parse("", 1) # end of data except self._error as v: self._raiseerror(v) try: close_handler = self.target.close except AttributeError: pass else: return close_handler() finally: # get rid of circular references del self.parser, self._parser del self.target, self._target # -------------------------------------------------------------------- # C14N 2.0 def canonicalize(xml_data=None, *, out=None, from_file=None, **options): """Convert XML to its C14N 2.0 serialised form. If *out* is provided, it must be a file or file-like object that receives the serialised canonical XML output (text, not bytes) through its ``.write()`` method. To write to a file, open it in text mode with encoding "utf-8". If *out* is not provided, this function returns the output as text string. Either *xml_data* (an XML string) or *from_file* (a file path or file-like object) must be provided as input. The configuration options are the same as for the ``C14NWriterTarget``. """ if xml_data is None and from_file is None: raise ValueError("Either 'xml_data' or 'from_file' must be provided as input") sio = None if out is None: sio = out = io.StringIO() parser = XMLParser(target=C14NWriterTarget(out.write, **options)) if xml_data is not None: parser.feed(xml_data) parser.close() elif from_file is not None: parse(from_file, parser=parser) return sio.getvalue() if sio is not None else None _looks_like_prefix_name = re.compile(r'^\w+:\w+$', re.UNICODE).match class C14NWriterTarget: """ Canonicalization writer target for the XMLParser. Serialises parse events to XML C14N 2.0. The *write* function is used for writing out the resulting data stream as text (not bytes). To write to a file, open it in text mode with encoding "utf-8" and pass its ``.write`` method. Configuration options: - *with_comments*: set to true to include comments - *strip_text*: set to true to strip whitespace before and after text content - *rewrite_prefixes*: set to true to replace namespace prefixes by "n{number}" - *qname_aware_tags*: a set of qname aware tag names in which prefixes should be replaced in text content - *qname_aware_attrs*: a set of qname aware attribute names in which prefixes should be replaced in text content - *exclude_attrs*: a set of attribute names that should not be serialised - *exclude_tags*: a set of tag names that should not be serialised """ def __init__(self, write, *, with_comments=False, strip_text=False, rewrite_prefixes=False, qname_aware_tags=None, qname_aware_attrs=None, exclude_attrs=None, exclude_tags=None): self._write = write self._data = [] self._with_comments = with_comments self._strip_text = strip_text self._exclude_attrs = set(exclude_attrs) if exclude_attrs else None self._exclude_tags = set(exclude_tags) if exclude_tags else None self._rewrite_prefixes = rewrite_prefixes if qname_aware_tags: self._qname_aware_tags = set(qname_aware_tags) else: self._qname_aware_tags = None if qname_aware_attrs: self._find_qname_aware_attrs = set(qname_aware_attrs).intersection else: self._find_qname_aware_attrs = None # Stack with globally and newly declared namespaces as (uri, prefix) pairs. self._declared_ns_stack = [[ ("http://www.w3.org/XML/1998/namespace", "xml"), ]] # Stack with user declared namespace prefixes as (uri, prefix) pairs. self._ns_stack = [] if not rewrite_prefixes: self._ns_stack.append(list(_namespace_map.items())) self._ns_stack.append([]) self._prefix_map = {} self._preserve_space = [False] self._pending_start = None self._root_seen = False self._root_done = False self._ignored_depth = 0 def _iter_namespaces(self, ns_stack, _reversed=reversed): for namespaces in _reversed(ns_stack): if namespaces: # almost no element declares new namespaces yield from namespaces def _resolve_prefix_name(self, prefixed_name): prefix, name = prefixed_name.split(':', 1) for uri, p in self._iter_namespaces(self._ns_stack): if p == prefix: return f'{{{uri}}}{name}' raise ValueError(f'Prefix {prefix} of QName "{prefixed_name}" is not declared in scope') def _qname(self, qname, uri=None): if uri is None: uri, tag = qname[1:].rsplit('}', 1) if qname[:1] == '{' else ('', qname) else: tag = qname prefixes_seen = set() for u, prefix in self._iter_namespaces(self._declared_ns_stack): if u == uri and prefix not in prefixes_seen: return f'{prefix}:{tag}' if prefix else tag, tag, uri prefixes_seen.add(prefix) # Not declared yet => add new declaration. if self._rewrite_prefixes: if uri in self._prefix_map: prefix = self._prefix_map[uri] else: prefix = self._prefix_map[uri] = f'n{len(self._prefix_map)}' self._declared_ns_stack[-1].append((uri, prefix)) return f'{prefix}:{tag}', tag, uri if not uri and '' not in prefixes_seen: # No default namespace declared => no prefix needed. return tag, tag, uri for u, prefix in self._iter_namespaces(self._ns_stack): if u == uri: self._declared_ns_stack[-1].append((uri, prefix)) return f'{prefix}:{tag}' if prefix else tag, tag, uri if not uri: # As soon as a default namespace is defined, # anything that has no namespace (and thus, no prefix) goes there. return tag, tag, uri raise ValueError(f'Namespace "{uri}" is not declared in scope') def data(self, data): if not self._ignored_depth: self._data.append(data) def _flush(self, _join_text=''.join): data = _join_text(self._data) del self._data[:] if self._strip_text and not self._preserve_space[-1]: data = data.strip() if self._pending_start is not None: args, self._pending_start = self._pending_start, None qname_text = data if data and _looks_like_prefix_name(data) else None self._start(*args, qname_text) if qname_text is not None: return if data and self._root_seen: self._write(_escape_cdata_c14n(data)) def start_ns(self, prefix, uri): if self._ignored_depth: return # we may have to resolve qnames in text content if self._data: self._flush() self._ns_stack[-1].append((uri, prefix)) def start(self, tag, attrs): if self._exclude_tags is not None and ( self._ignored_depth or tag in self._exclude_tags): self._ignored_depth += 1 return if self._data: self._flush() new_namespaces = [] self._declared_ns_stack.append(new_namespaces) if self._qname_aware_tags is not None and tag in self._qname_aware_tags: # Need to parse text first to see if it requires a prefix declaration. self._pending_start = (tag, attrs, new_namespaces) return self._start(tag, attrs, new_namespaces) def _start(self, tag, attrs, new_namespaces, qname_text=None): if self._exclude_attrs is not None and attrs: attrs = {k: v for k, v in attrs.items() if k not in self._exclude_attrs} qnames = {tag, *attrs} resolved_names = {} # Resolve prefixes in attribute and tag text. if qname_text is not None: qname = resolved_names[qname_text] = self._resolve_prefix_name(qname_text) qnames.add(qname) if self._find_qname_aware_attrs is not None and attrs: qattrs = self._find_qname_aware_attrs(attrs) if qattrs: for attr_name in qattrs: value = attrs[attr_name] if _looks_like_prefix_name(value): qname = resolved_names[value] = self._resolve_prefix_name(value) qnames.add(qname) else: qattrs = None else: qattrs = None # Assign prefixes in lexicographical order of used URIs. parse_qname = self._qname parsed_qnames = {n: parse_qname(n) for n in sorted( qnames, key=lambda n: n.split('}', 1))} # Write namespace declarations in prefix order ... if new_namespaces: attr_list = [ ('xmlns:' + prefix if prefix else 'xmlns', uri) for uri, prefix in new_namespaces ] attr_list.sort() else: # almost always empty attr_list = [] # ... followed by attributes in URI+name order if attrs: for k, v in sorted(attrs.items()): if qattrs is not None and k in qattrs and v in resolved_names: v = parsed_qnames[resolved_names[v]][0] attr_qname, attr_name, uri = parsed_qnames[k] # No prefix for attributes in default ('') namespace. attr_list.append((attr_qname if uri else attr_name, v)) # Honour xml:space attributes. space_behaviour = attrs.get('{http://www.w3.org/XML/1998/namespace}space') self._preserve_space.append( space_behaviour == 'preserve' if space_behaviour else self._preserve_space[-1]) # Write the tag. write = self._write write('<' + parsed_qnames[tag][0]) if attr_list: write(''.join([f' {k}="{_escape_attrib_c14n(v)}"' for k, v in attr_list])) write('>') # Write the resolved qname text content. if qname_text is not None: write(_escape_cdata_c14n(parsed_qnames[resolved_names[qname_text]][0])) self._root_seen = True self._ns_stack.append([]) def end(self, tag): if self._ignored_depth: self._ignored_depth -= 1 return if self._data: self._flush() self._write(f'') self._preserve_space.pop() self._root_done = len(self._preserve_space) == 1 self._declared_ns_stack.pop() self._ns_stack.pop() def comment(self, text): if not self._with_comments: return if self._ignored_depth: return if self._root_done: self._write('\n') elif self._root_seen and self._data: self._flush() self._write(f'') if not self._root_seen: self._write('\n') def pi(self, target, data): if self._ignored_depth: return if self._root_done: self._write('\n') elif self._root_seen and self._data: self._flush() self._write( f'' if data else f'') if not self._root_seen: self._write('\n') def _escape_cdata_c14n(text): # 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('>', '>') if '\r' in text: text = text.replace('\r', ' ') return text except (TypeError, AttributeError): _raise_serialization_error(text) def _escape_attrib_c14n(text): # escape attribute value try: if '&' in text: text = text.replace('&', '&') if '<' in text: text = text.replace('<', '<') if '"' in text: text = text.replace('"', '"') if '\t' in text: text = text.replace('\t', ' ') if '\n' in text: text = text.replace('\n', ' ') if '\r' in text: text = text.replace('\r', ' ') return text except (TypeError, AttributeError): _raise_serialization_error(text) # -------------------------------------------------------------------- # Import the C accelerators try: # Element is going to be shadowed by the C implementation. We need to keep # the Python version of it accessible for some "creative" by external code # (see tests) _Element_Py = Element # Element, SubElement, ParseError, TreeBuilder, XMLParser, _set_factories from _elementtree import * from _elementtree import _set_factories except ImportError: pass else: _set_factories(Comment, ProcessingInstruction) usr/lib64/python3.12/xml/etree/ElementTree.py000064400000220441152360111050014606 0ustar00"""Lightweight XML support for Python. XML is an inherently hierarchical data format, and the most natural way to represent it is with a tree. This module has two classes for this purpose: 1. ElementTree represents the whole XML document as a tree and 2. Element represents a single node in this tree. Interactions with the whole document (reading and writing to/from files) are usually done on the ElementTree level. Interactions with a single XML element and its sub-elements are done on the Element level. Element is a flexible container object designed to store hierarchical data structures in memory. It can be described as a cross between a list and a dictionary. Each Element has a number of properties associated with it: 'tag' - a string containing the element's name. 'attributes' - a Python dictionary storing the element's attributes. 'text' - a string containing the element's text content. 'tail' - an optional string containing text after the element's end tag. And a number of child elements stored in a Python sequence. To create an element instance, use the Element constructor, or the SubElement factory function. You can also use the ElementTree class to wrap an element structure and convert it to and from XML. """ #--------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See https://www.python.org/psf/license for licensing details. # # ElementTree # Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com # -------------------------------------------------------------------- # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- __all__ = [ # public symbols "Comment", "dump", "Element", "ElementTree", "fromstring", "fromstringlist", "indent", "iselement", "iterparse", "parse", "ParseError", "PI", "ProcessingInstruction", "QName", "SubElement", "tostring", "tostringlist", "TreeBuilder", "VERSION", "XML", "XMLID", "XMLParser", "XMLPullParser", "register_namespace", "canonicalize", "C14NWriterTarget", ] VERSION = "1.3.0" import sys import re import warnings import io import collections import collections.abc import contextlib import weakref from . import ElementPath class ParseError(SyntaxError): """An error when parsing an XML document. In addition to its exception value, a ParseError contains two extra attributes: 'code' - the specific exception code 'position' - the line and column of the error """ pass # -------------------------------------------------------------------- def iselement(element): """Return True if *element* appears to be an Element.""" return hasattr(element, 'tag') class Element: """An XML element. This class is the reference implementation of the Element interface. An element's length is its number of subelements. That means if you want to check if an element is truly empty, you should check BOTH its length AND its text attribute. The element tag, attribute names, and attribute values can be either bytes or strings. *tag* is the element name. *attrib* is an optional dictionary containing element attributes. *extra* are additional element attributes given as keyword arguments. Example form: text...tail """ tag = None """The element's name.""" attrib = None """Dictionary of the element's attributes.""" text = None """ Text before first subelement. This is either a string or the value None. Note that if there is no text, this attribute may be either None or the empty string, depending on the parser. """ tail = None """ Text after this element's end tag, but before the next sibling element's start tag. This is either a string or the value None. Note that if there was no text, this attribute may be either None or an empty string, depending on the parser. """ def __init__(self, tag, attrib={}, **extra): if not isinstance(attrib, dict): raise TypeError("attrib must be dict, not %s" % ( attrib.__class__.__name__,)) self.tag = tag self.attrib = {**attrib, **extra} self._children = [] def __repr__(self): return "<%s %r at %#x>" % (self.__class__.__name__, self.tag, id(self)) def makeelement(self, tag, attrib): """Create a new element with the same type. *tag* is a string containing the element name. *attrib* is a dictionary containing the element attributes. Do not call this method, use the SubElement factory function instead. """ return self.__class__(tag, attrib) def __copy__(self): elem = self.makeelement(self.tag, self.attrib) elem.text = self.text elem.tail = self.tail elem[:] = self return elem def __len__(self): return len(self._children) def __bool__(self): warnings.warn( "Testing an element's truth value will always return True in " "future versions. " "Use specific 'len(elem)' or 'elem is not None' test instead.", DeprecationWarning, stacklevel=2 ) return len(self._children) != 0 # emulate old behaviour, for now def __getitem__(self, index): return self._children[index] def __setitem__(self, index, element): if isinstance(index, slice): for elt in element: self._assert_is_element(elt) else: self._assert_is_element(element) self._children[index] = element def __delitem__(self, index): del self._children[index] def append(self, subelement): """Add *subelement* to the end of this element. The new element will appear in document order after the last existing subelement (or directly after the text, if it's the first subelement), but before the end tag for this element. """ self._assert_is_element(subelement) self._children.append(subelement) def extend(self, elements): """Append subelements from a sequence. *elements* is a sequence with zero or more elements. """ for element in elements: self._assert_is_element(element) self._children.append(element) def insert(self, index, subelement): """Insert *subelement* at position *index*.""" self._assert_is_element(subelement) self._children.insert(index, subelement) def _assert_is_element(self, e): # Need to refer to the actual Python implementation, not the # shadowing C implementation. if not isinstance(e, _Element_Py): raise TypeError('expected an Element, not %s' % type(e).__name__) def remove(self, subelement): """Remove matching subelement. Unlike the find methods, this method compares elements based on identity, NOT ON tag value or contents. To remove subelements by other means, the easiest way is to use a list comprehension to select what elements to keep, and then use slice assignment to update the parent element. ValueError is raised if a matching element could not be found. """ # assert iselement(element) self._children.remove(subelement) def find(self, path, namespaces=None): """Find first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ return ElementPath.find(self, path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find text for first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *default* is the value to return if the element was not found, *namespaces* is an optional mapping from namespace prefix to full name. Return text content of first matching element, or default value if none was found. Note that if an element is found having no text content, the empty string is returned. """ return ElementPath.findtext(self, path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Returns list containing all matching elements in document order. """ return ElementPath.findall(self, path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ return ElementPath.iterfind(self, path, namespaces) def clear(self): """Reset element. This function removes all subelements, clears all attributes, and sets the text and tail attributes to None. """ self.attrib.clear() self._children = [] self.text = self.tail = None def get(self, key, default=None): """Get element attribute. Equivalent to attrib.get, but some implementations may handle this a bit more efficiently. *key* is what attribute to look for, and *default* is what to return if the attribute was not found. Returns a string containing the attribute value, or the default if attribute was not found. """ return self.attrib.get(key, default) def set(self, key, value): """Set element attribute. Equivalent to attrib[key] = value, but some implementations may handle this a bit more efficiently. *key* is what attribute to set, and *value* is the attribute value to set it to. """ self.attrib[key] = value def keys(self): """Get list of attribute names. Names are returned in an arbitrary order, just like an ordinary Python dict. Equivalent to attrib.keys() """ return self.attrib.keys() def items(self): """Get element attributes as a sequence. The attributes are returned in arbitrary order. Equivalent to attrib.items(). Return a list of (name, value) tuples. """ return self.attrib.items() def iter(self, tag=None): """Create tree iterator. The iterator loops over the element and all subelements in document order, returning all elements with a matching tag. 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. *tag* is what tags to look for (default is to return all elements) Return an iterator containing all the matching elements. """ if tag == "*": tag = None if tag is None or self.tag == tag: yield self for e in self._children: yield from e.iter(tag) def itertext(self): """Create text iterator. The iterator loops over the element and all subelements in document order, returning all inner text. """ tag = self.tag if not isinstance(tag, str) and tag is not None: return t = self.text if t: yield t for e in self: yield from e.itertext() t = e.tail if t: yield t def SubElement(parent, tag, attrib={}, **extra): """Subelement factory which creates an element instance, and appends it to an existing parent. The element tag, attribute names, and attribute values can be either bytes or Unicode strings. *parent* is the parent element, *tag* is the subelements name, *attrib* is an optional directory containing element attributes, *extra* are additional attributes given as keyword arguments. """ attrib = {**attrib, **extra} element = parent.makeelement(tag, attrib) parent.append(element) return element def Comment(text=None): """Comment element factory. This function creates a special element which the standard serializer serializes as an XML comment. *text* is a string containing the comment string. """ element = Element(Comment) element.text = text return element def ProcessingInstruction(target, text=None): """Processing Instruction element factory. This function creates a special element which the standard serializer serializes as an XML comment. *target* is a string containing the processing instruction, *text* is a string containing the processing instruction contents, if any. """ element = Element(ProcessingInstruction) element.text = target if text: element.text = element.text + " " + text return element PI = ProcessingInstruction class QName: """Qualified name wrapper. This class can be used to wrap a QName attribute value in order to get proper namespace handing on output. *text_or_uri* is a string containing the QName value either in the form {uri}local, or if the tag argument is given, the URI part of a QName. *tag* is an optional argument which if given, will make the first argument (text_or_uri) be interpreted as a URI, and this argument (tag) be interpreted as a local name. """ def __init__(self, text_or_uri, tag=None): if tag: text_or_uri = "{%s}%s" % (text_or_uri, tag) self.text = text_or_uri def __str__(self): return self.text def __repr__(self): return '<%s %r>' % (self.__class__.__name__, self.text) def __hash__(self): return hash(self.text) def __le__(self, other): if isinstance(other, QName): return self.text <= other.text return self.text <= other def __lt__(self, other): if isinstance(other, QName): return self.text < other.text return self.text < other def __ge__(self, other): if isinstance(other, QName): return self.text >= other.text return self.text >= other def __gt__(self, other): if isinstance(other, QName): return self.text > other.text return self.text > other def __eq__(self, other): if isinstance(other, QName): return self.text == other.text return self.text == other # -------------------------------------------------------------------- class ElementTree: """An XML element hierarchy. This class also provides support for serialization to and from standard XML. *element* is an optional root element node, *file* is an optional file handle or file name of an XML file whose contents will be used to initialize the tree with. """ def __init__(self, element=None, file=None): # assert element is None or iselement(element) self._root = element # first node if file: self.parse(file) def getroot(self): """Return root element of this tree.""" return self._root def _setroot(self, element): """Replace root element of this tree. This will discard the current contents of the tree and replace it with the given element. Use with care! """ # assert iselement(element) self._root = element def parse(self, source, parser=None): """Load external XML document into element tree. *source* is a file name or file object, *parser* is an optional parser instance that defaults to XMLParser. ParseError is raised if the parser fails to parse the document. Returns the root element of the given source document. """ close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if parser is None: # If no parser was specified, create a default XMLParser parser = XMLParser() if hasattr(parser, '_parse_whole'): # The default XMLParser, when it comes from an accelerator, # can define an internal _parse_whole API for efficiency. # It can be used to parse the whole source without feeding # it with chunks. self._root = parser._parse_whole(source) return self._root while data := source.read(65536): parser.feed(data) self._root = parser.close() return self._root finally: if close_source: source.close() def iter(self, tag=None): """Create and return tree iterator for the root element. The iterator loops over all elements in this tree, in document order. *tag* is a string with the tag name to iterate over (default is to return all elements). """ # assert self._root is not None return self._root.iter(tag) def find(self, path, namespaces=None): """Find first matching element by tag name or path. Same as getroot().find(path), which is Element.find() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.find(path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find first matching element by tag name or path. Same as getroot().findtext(path), which is Element.findtext() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.findtext(path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().findall(path), which is Element.findall(). *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return list containing all matching elements in document order. """ # 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.findall(path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().iterfind(path), which is element.iterfind() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ # 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) def write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True): """Write element tree to a file as XML. Arguments: *file_or_filename* -- file name or a file object opened for writing *encoding* -- the output encoding (default: US-ASCII) *xml_declaration* -- bool indicating if an XML declaration should be added to the output. If None, an XML declaration is added if encoding IS NOT either of: US-ASCII, UTF-8, or Unicode *default_namespace* -- sets the default XML namespace (for "xmlns") *method* -- either "xml" (default), "html, "text", or "c14n" *short_empty_elements* -- controls the formatting of elements that contain no content. If True (default) they are emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags """ if not method: method = "xml" elif method not in _serialize: raise ValueError("unknown method %r" % method) if not encoding: if method == "c14n": encoding = "utf-8" else: encoding = "us-ascii" with _get_writer(file_or_filename, encoding) as (write, declared_encoding): if method == "xml" and (xml_declaration or (xml_declaration is None and encoding.lower() != "unicode" and declared_encoding.lower() not in ("utf-8", "us-ascii"))): write("\n" % ( declared_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, short_empty_elements=short_empty_elements) def write_c14n(self, file): # lxml.etree compatibility. use output method instead return self.write(file, method="c14n") # -------------------------------------------------------------------- # serialization support @contextlib.contextmanager def _get_writer(file_or_filename, encoding): # returns text write method and release all resources after using try: write = file_or_filename.write except AttributeError: # file_or_filename is a file name if encoding.lower() == "unicode": encoding="utf-8" with open(file_or_filename, "w", encoding=encoding, errors="xmlcharrefreplace") as file: yield file.write, encoding else: # file_or_filename is a file-like object # encoding determines if it is a text or binary writer if encoding.lower() == "unicode": # use a text writer as is yield write, getattr(file_or_filename, "encoding", None) or "utf-8" else: # wrap a binary writer with TextIOWrapper with contextlib.ExitStack() as stack: if isinstance(file_or_filename, io.BufferedIOBase): file = file_or_filename elif isinstance(file_or_filename, io.RawIOBase): file = io.BufferedWriter(file_or_filename) # Keep the original file open when the BufferedWriter is # destroyed stack.callback(file.detach) else: # This is to handle passed objects that aren't in the # IOBase hierarchy, but just have a write method file = io.BufferedIOBase() file.writable = lambda: True file.write = write try: # TextIOWrapper uses this methods to determine # if BOM (for UTF-16, etc) should be added file.seekable = file_or_filename.seekable file.tell = file_or_filename.tell except AttributeError: pass file = io.TextIOWrapper(file, encoding=encoding, errors="xmlcharrefreplace", newline="\n") # Keep the original file open when the TextIOWrapper is # destroyed stack.callback(file.detach) yield file.write, encoding def _namespaces(elem, 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 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) # populate qname and namespaces table for elem in elem.iter(): tag = elem.tag if isinstance(tag, QName): if 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, short_empty_elements, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % text) elif tag is ProcessingInstruction: write("" % text) else: tag = qnames[tag] if tag is None: if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) 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 items: 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) or not short_empty_elements: write(">") if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) write("") else: write(" />") if elem.tail: write(_escape_cdata(elem.tail)) HTML_EMPTY = {"area", "base", "basefont", "br", "col", "embed", "frame", "hr", "img", "input", "isindex", "link", "meta", "param", "source", "track", "wbr"} def _serialize_html(write, elem, qnames, namespaces, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % _escape_cdata(text)) elif tag is ProcessingInstruction: write("" % _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 items: 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(">") ltag = tag.lower() if text: if ltag == "script" or ltag == "style": write(text) else: write(_escape_cdata(text)) for e in elem: _serialize_html(write, e, qnames, None) if ltag not in HTML_EMPTY: write("") 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, } def register_namespace(prefix, uri): """Register a namespace prefix. The registry is global, and any existing mapping for either the given prefix or the namespace URI will be removed. *prefix* is the namespace prefix, *uri* is a namespace uri. Tags and attributes in this namespace will be serialized with prefix if possible. ValueError is raised if prefix is reserved or is invalid. """ if re.match(r"ns\d+$", prefix): raise ValueError("Prefix format reserved for internal use") for k, v in list(_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", } # For tests and troubleshooting register_namespace._namespace_map = _namespace_map def _raise_serialization_error(text): raise TypeError( "cannot serialize %r (type %s)" % (text, type(text).__name__) ) def _escape_cdata(text): # escape character data try: # it's worth avoiding do-nothing calls for strings that are # shorter than 500 characters, 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) def _escape_attrib(text): # 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("\"", """) # Although section 2.11 of the XML specification states that CR or # CR LN should be replaced with just LN, it applies only to EOLNs # which take part of organizing file into lines. Within attributes, # we are replacing these with entity numbers, so they do not count. # http://www.w3.org/TR/REC-xml/#sec-line-ends # The current solution, contained in following six lines, was # discussed in issue 17582 and 39011. if "\r" in text: text = text.replace("\r", " ") if "\n" in text: text = text.replace("\n", " ") if "\t" in text: text = text.replace("\t", " ") return text except (TypeError, AttributeError): _raise_serialization_error(text) def _escape_attrib_html(text): # escape attribute value try: 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 tostring(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True): """Generate string representation of XML element. All subelements are included. If encoding is "unicode", a string is returned. Otherwise a bytestring is returned. *element* is an Element instance, *encoding* is an optional output encoding defaulting to US-ASCII, *method* is an optional output which can be one of "xml" (default), "html", "text" or "c14n", *default_namespace* sets the default XML namespace (for "xmlns"). Returns an (optionally) encoded string containing the XML data. """ stream = io.StringIO() if encoding == 'unicode' else io.BytesIO() ElementTree(element).write(stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements) return stream.getvalue() class _ListDataStream(io.BufferedIOBase): """An auxiliary stream accumulating into a list reference.""" def __init__(self, lst): self.lst = lst def writable(self): return True def seekable(self): return True def write(self, b): self.lst.append(b) def tell(self): return len(self.lst) def tostringlist(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True): lst = [] stream = _ListDataStream(lst) ElementTree(element).write(stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements) return lst def dump(elem): """Write element tree or element structure to sys.stdout. This function should be used for debugging only. *elem* is either an ElementTree, or a single Element. The exact output format is implementation dependent. In this version, it's written as an ordinary XML file. """ # debugging if not isinstance(elem, ElementTree): elem = ElementTree(elem) elem.write(sys.stdout, encoding="unicode") tail = elem.getroot().tail if not tail or tail[-1] != "\n": sys.stdout.write("\n") def indent(tree, space=" ", level=0): """Indent an XML document by inserting newlines and indentation space after elements. *tree* is the ElementTree or Element to modify. The (root) element itself will not be changed, but the tail text of all elements in its subtree will be adapted. *space* is the whitespace to insert for each indentation level, two space characters by default. *level* is the initial indentation level. Setting this to a higher value than 0 can be used for indenting subtrees that are more deeply nested inside of a document. """ if isinstance(tree, ElementTree): tree = tree.getroot() if level < 0: raise ValueError(f"Initial indentation level must be >= 0, got {level}") if not len(tree): return # Reduce the memory consumption by reusing indentation strings. indentations = ["\n" + level * space] def _indent_children(elem, level): # Start a new indentation level for the first child. child_level = level + 1 try: child_indentation = indentations[child_level] except IndexError: child_indentation = indentations[level] + space indentations.append(child_indentation) if not elem.text or not elem.text.strip(): elem.text = child_indentation for child in elem: if len(child): _indent_children(child, child_level) if not child.tail or not child.tail.strip(): child.tail = child_indentation # Dedent after the last child by overwriting the previous indentation. if not child.tail.strip(): child.tail = indentations[level] _indent_children(tree, 0) # -------------------------------------------------------------------- # parsing def parse(source, parser=None): """Parse XML document into element tree. *source* is a filename or file object containing XML data, *parser* is an optional parser instance defaulting to XMLParser. Return an ElementTree instance. """ tree = ElementTree() tree.parse(source, parser) return tree def iterparse(source, events=None, parser=None): """Incrementally parse XML document into ElementTree. This class also reports what's going on to the user based on the *events* it is initialized with. The supported events are the strings "start", "end", "start-ns" and "end-ns" (the "ns" events are used to get detailed namespace information). If *events* is omitted, only "end" events are reported. *source* is a filename or file object containing XML data, *events* is a list of events to report back, *parser* is an optional parser instance. Returns an iterator providing (event, elem) pairs. """ # Use the internal, undocumented _parser argument for now; When the # parser argument of iterparse is removed, this can be killed. pullparser = XMLPullParser(events=events, _parser=parser) if not hasattr(source, "read"): source = open(source, "rb") close_source = True else: close_source = False def iterator(source): try: while True: yield from pullparser.read_events() # load event buffer data = source.read(16 * 1024) if not data: break pullparser.feed(data) root = pullparser._close_and_return_root() yield from pullparser.read_events() it = wr() if it is not None: it.root = root finally: if close_source: source.close() class IterParseIterator(collections.abc.Iterator): __next__ = iterator(source).__next__ def __del__(self): if close_source: source.close() it = IterParseIterator() it.root = None wr = weakref.ref(it) return it class XMLPullParser: def __init__(self, events=None, *, _parser=None): # The _parser argument is for internal use only and must not be relied # upon in user code. It will be removed in a future release. # See https://bugs.python.org/issue17741 for more details. self._events_queue = collections.deque() self._parser = _parser or XMLParser(target=TreeBuilder()) # wire up the parser for event reporting if events is None: events = ("end",) self._parser._setevents(self._events_queue, events) def feed(self, data): """Feed encoded data to parser.""" if self._parser is None: raise ValueError("feed() called after end of stream") if data: try: self._parser.feed(data) except SyntaxError as exc: self._events_queue.append(exc) def _close_and_return_root(self): # iterparse needs this to set its root attribute properly :( root = self._parser.close() self._parser = None return root def close(self): """Finish feeding data to parser. Unlike XMLParser, does not return the root element. Use read_events() to consume elements from XMLPullParser. """ self._close_and_return_root() def read_events(self): """Return an iterator over currently available (event, elem) pairs. Events are consumed from the internal event queue as they are retrieved from the iterator. """ events = self._events_queue while events: event = events.popleft() if isinstance(event, Exception): raise event else: yield event def flush(self): if self._parser is None: raise ValueError("flush() called after end of stream") self._parser.flush() def XML(text, parser=None): """Parse XML document from string constant. This function can be used to embed "XML Literals" in Python code. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) return parser.close() def XMLID(text, parser=None): """Parse XML document from string constant for its IDs. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an (Element, dict) tuple, in which the dict maps element id:s to elements. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) tree = parser.close() ids = {} for elem in tree.iter(): id = elem.get("id") if id: ids[id] = elem return tree, ids # Parse XML document from string constant. Alias for XML(). fromstring = XML def fromstringlist(sequence, parser=None): """Parse XML document from sequence of string fragments. *sequence* is a list of other sequence, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) for text in sequence: parser.feed(text) return parser.close() # -------------------------------------------------------------------- class TreeBuilder: """Generic element structure builder. This builder converts a sequence of start, data, and end method calls to a well-formed element structure. You can use this class to build an element structure using a custom XML parser, or a parser for some other XML-like format. *element_factory* is an optional element factory which is called to create new Element instances, as necessary. *comment_factory* is a factory to create comments to be used instead of the standard factory. If *insert_comments* is false (the default), comments will not be inserted into the tree. *pi_factory* is a factory to create processing instructions to be used instead of the standard factory. If *insert_pis* is false (the default), processing instructions will not be inserted into the tree. """ def __init__(self, element_factory=None, *, comment_factory=None, pi_factory=None, insert_comments=False, insert_pis=False): self._data = [] # data collector self._elem = [] # element stack self._last = None # last element self._root = None # root element self._tail = None # true if we're after an end tag if comment_factory is None: comment_factory = Comment self._comment_factory = comment_factory self.insert_comments = insert_comments if pi_factory is None: pi_factory = ProcessingInstruction self._pi_factory = pi_factory self.insert_pis = insert_pis if element_factory is None: element_factory = Element self._factory = element_factory def close(self): """Flush builder buffers and return toplevel document Element.""" assert len(self._elem) == 0, "missing end tags" assert self._root is not None, "missing toplevel element" return self._root def _flush(self): if self._data: if self._last is not None: text = "".join(self._data) if self._tail: assert self._last.tail is None, "internal error (tail)" self._last.tail = text else: assert self._last.text is None, "internal error (text)" self._last.text = text self._data = [] def data(self, data): """Add text to current element.""" self._data.append(data) def start(self, tag, attrs): """Open new element and return it. *tag* is the element name, *attrs* is a dict containing element attributes. """ self._flush() self._last = elem = self._factory(tag, attrs) if self._elem: self._elem[-1].append(elem) elif self._root is None: self._root = elem self._elem.append(elem) self._tail = 0 return elem def end(self, tag): """Close and return current Element. *tag* is the element name. """ self._flush() self._last = self._elem.pop() assert self._last.tag == tag,\ "end tag mismatch (expected %s, got %s)" % ( self._last.tag, tag) self._tail = 1 return self._last def comment(self, text): """Create a comment using the comment_factory. *text* is the text of the comment. """ return self._handle_single( self._comment_factory, self.insert_comments, text) def pi(self, target, text=None): """Create a processing instruction using the pi_factory. *target* is the target name of the processing instruction. *text* is the data of the processing instruction, or ''. """ return self._handle_single( self._pi_factory, self.insert_pis, target, text) def _handle_single(self, factory, insert, *args): elem = factory(*args) if insert: self._flush() self._last = elem if self._elem: self._elem[-1].append(elem) self._tail = 1 return elem # also see ElementTree and TreeBuilder class XMLParser: """Element structure builder for XML source data based on the expat parser. *target* is an optional target object which defaults to an instance of the standard TreeBuilder class, *encoding* is an optional encoding string which if given, overrides the encoding specified in the XML file: http://www.iana.org/assignments/character-sets """ def __init__(self, *, target=None, encoding=None): try: from xml.parsers import expat except ImportError: 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() # 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 # main callbacks parser.DefaultHandlerExpand = self._default if hasattr(target, 'start'): parser.StartElementHandler = self._start if hasattr(target, 'end'): parser.EndElementHandler = self._end if hasattr(target, 'start_ns'): parser.StartNamespaceDeclHandler = self._start_ns if hasattr(target, 'end_ns'): parser.EndNamespaceDeclHandler = self._end_ns if hasattr(target, 'data'): parser.CharacterDataHandler = target.data # miscellaneous callbacks if hasattr(target, 'comment'): parser.CommentHandler = target.comment if hasattr(target, 'pi'): parser.ProcessingInstructionHandler = target.pi # Configure pyexpat: buffering, new-style attribute handling. parser.buffer_text = 1 parser.ordered_attributes = 1 self._doctype = None self.entity = {} try: self.version = "Expat %d.%d.%d" % expat.version_info except AttributeError: pass # unknown def _setevents(self, events_queue, events_to_report): # Internal API for XMLPullParser # events_to_report: a list of events to report during parsing (same as # the *events* of XMLPullParser's constructor. # events_queue: a list of actual parsing events that will be populated # by the underlying parser. # parser = self._parser append = events_queue.append for event_name in events_to_report: if event_name == "start": parser.ordered_attributes = 1 def handler(tag, attrib_in, event=event_name, append=append, start=self._start): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler elif event_name == "end": def handler(tag, event=event_name, append=append, end=self._end): append((event, end(tag))) parser.EndElementHandler = handler elif event_name == "start-ns": # TreeBuilder does not implement .start_ns() if hasattr(self.target, "start_ns"): def handler(prefix, uri, event=event_name, append=append, start_ns=self._start_ns): append((event, start_ns(prefix, uri))) else: def handler(prefix, uri, event=event_name, append=append): append((event, (prefix or '', uri or ''))) parser.StartNamespaceDeclHandler = handler elif event_name == "end-ns": # TreeBuilder does not implement .end_ns() if hasattr(self.target, "end_ns"): def handler(prefix, event=event_name, append=append, end_ns=self._end_ns): append((event, end_ns(prefix))) else: def handler(prefix, event=event_name, append=append): append((event, None)) parser.EndNamespaceDeclHandler = handler elif event_name == 'comment': def handler(text, event=event_name, append=append, self=self): append((event, self.target.comment(text))) parser.CommentHandler = handler elif event_name == 'pi': def handler(pi_target, data, event=event_name, append=append, self=self): append((event, self.target.pi(pi_target, data))) parser.ProcessingInstructionHandler = handler else: raise ValueError("unknown event %r" % event_name) 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 try: name = self._names[key] except KeyError: name = key if "}" in name: name = "{" + name self._names[key] = name return name def _start_ns(self, prefix, uri): return self.target.start_ns(prefix or '', uri or '') def _end_ns(self, prefix): return self.target.end_ns(prefix or '') def _start(self, tag, attr_list): # Handler for expat's StartElementHandler. Since ordered_attributes # is set, the attributes are reported as a list of alternating # attribute name,value. fixname = self._fixname tag = fixname(tag) attrib = {} if attr_list: for i in range(0, len(attr_list), 2): attrib[fixname(attr_list[i])] = attr_list[i+1] return self.target.start(tag, attrib) def _end(self, tag): return self.target.end(self._fixname(tag)) def _default(self, text): prefix = text[:1] if prefix == "&": # deal with undefined entities try: data_handler = self.target.data except AttributeError: return try: data_handler(self.entity[text[1:-1]]) except KeyError: from xml.parsers import expat 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 = text.strip() if not text: return self._doctype.append(text) n = len(self._doctype) if n > 2: type = self._doctype[1] if type == "PUBLIC" and n == 4: name, type, pubid, system = self._doctype if pubid: pubid = pubid[1:-1] elif type == "SYSTEM" and n == 3: name, type, system = self._doctype pubid = None else: return if hasattr(self.target, "doctype"): self.target.doctype(name, pubid, system[1:-1]) elif hasattr(self, "doctype"): warnings.warn( "The doctype() method of XMLParser is ignored. " "Define doctype() method on the TreeBuilder target.", RuntimeWarning) self._doctype = None def feed(self, data): """Feed encoded data to parser.""" try: self.parser.Parse(data, False) except self._error as v: self._raiseerror(v) def close(self): """Finish feeding data to parser and return element structure.""" try: self.parser.Parse(b"", True) # end of data except self._error as v: self._raiseerror(v) try: close_handler = self.target.close except AttributeError: pass else: return close_handler() finally: # get rid of circular references del self.parser, self._parser del self.target, self._target def flush(self): was_enabled = self.parser.GetReparseDeferralEnabled() try: self.parser.SetReparseDeferralEnabled(False) self.parser.Parse(b"", False) except self._error as v: self._raiseerror(v) finally: self.parser.SetReparseDeferralEnabled(was_enabled) # -------------------------------------------------------------------- # C14N 2.0 def canonicalize(xml_data=None, *, out=None, from_file=None, **options): """Convert XML to its C14N 2.0 serialised form. If *out* is provided, it must be a file or file-like object that receives the serialised canonical XML output (text, not bytes) through its ``.write()`` method. To write to a file, open it in text mode with encoding "utf-8". If *out* is not provided, this function returns the output as text string. Either *xml_data* (an XML string) or *from_file* (a file path or file-like object) must be provided as input. The configuration options are the same as for the ``C14NWriterTarget``. """ if xml_data is None and from_file is None: raise ValueError("Either 'xml_data' or 'from_file' must be provided as input") sio = None if out is None: sio = out = io.StringIO() parser = XMLParser(target=C14NWriterTarget(out.write, **options)) if xml_data is not None: parser.feed(xml_data) parser.close() elif from_file is not None: parse(from_file, parser=parser) return sio.getvalue() if sio is not None else None _looks_like_prefix_name = re.compile(r'^\w+:\w+$', re.UNICODE).match class C14NWriterTarget: """ Canonicalization writer target for the XMLParser. Serialises parse events to XML C14N 2.0. The *write* function is used for writing out the resulting data stream as text (not bytes). To write to a file, open it in text mode with encoding "utf-8" and pass its ``.write`` method. Configuration options: - *with_comments*: set to true to include comments - *strip_text*: set to true to strip whitespace before and after text content - *rewrite_prefixes*: set to true to replace namespace prefixes by "n{number}" - *qname_aware_tags*: a set of qname aware tag names in which prefixes should be replaced in text content - *qname_aware_attrs*: a set of qname aware attribute names in which prefixes should be replaced in text content - *exclude_attrs*: a set of attribute names that should not be serialised - *exclude_tags*: a set of tag names that should not be serialised """ def __init__(self, write, *, with_comments=False, strip_text=False, rewrite_prefixes=False, qname_aware_tags=None, qname_aware_attrs=None, exclude_attrs=None, exclude_tags=None): self._write = write self._data = [] self._with_comments = with_comments self._strip_text = strip_text self._exclude_attrs = set(exclude_attrs) if exclude_attrs else None self._exclude_tags = set(exclude_tags) if exclude_tags else None self._rewrite_prefixes = rewrite_prefixes if qname_aware_tags: self._qname_aware_tags = set(qname_aware_tags) else: self._qname_aware_tags = None if qname_aware_attrs: self._find_qname_aware_attrs = set(qname_aware_attrs).intersection else: self._find_qname_aware_attrs = None # Stack with globally and newly declared namespaces as (uri, prefix) pairs. self._declared_ns_stack = [[ ("http://www.w3.org/XML/1998/namespace", "xml"), ]] # Stack with user declared namespace prefixes as (uri, prefix) pairs. self._ns_stack = [] if not rewrite_prefixes: self._ns_stack.append(list(_namespace_map.items())) self._ns_stack.append([]) self._prefix_map = {} self._preserve_space = [False] self._pending_start = None self._root_seen = False self._root_done = False self._ignored_depth = 0 def _iter_namespaces(self, ns_stack, _reversed=reversed): for namespaces in _reversed(ns_stack): if namespaces: # almost no element declares new namespaces yield from namespaces def _resolve_prefix_name(self, prefixed_name): prefix, name = prefixed_name.split(':', 1) for uri, p in self._iter_namespaces(self._ns_stack): if p == prefix: return f'{{{uri}}}{name}' raise ValueError(f'Prefix {prefix} of QName "{prefixed_name}" is not declared in scope') def _qname(self, qname, uri=None): if uri is None: uri, tag = qname[1:].rsplit('}', 1) if qname[:1] == '{' else ('', qname) else: tag = qname prefixes_seen = set() for u, prefix in self._iter_namespaces(self._declared_ns_stack): if u == uri and prefix not in prefixes_seen: return f'{prefix}:{tag}' if prefix else tag, tag, uri prefixes_seen.add(prefix) # Not declared yet => add new declaration. if self._rewrite_prefixes: if uri in self._prefix_map: prefix = self._prefix_map[uri] else: prefix = self._prefix_map[uri] = f'n{len(self._prefix_map)}' self._declared_ns_stack[-1].append((uri, prefix)) return f'{prefix}:{tag}', tag, uri if not uri and '' not in prefixes_seen: # No default namespace declared => no prefix needed. return tag, tag, uri for u, prefix in self._iter_namespaces(self._ns_stack): if u == uri: self._declared_ns_stack[-1].append((uri, prefix)) return f'{prefix}:{tag}' if prefix else tag, tag, uri if not uri: # As soon as a default namespace is defined, # anything that has no namespace (and thus, no prefix) goes there. return tag, tag, uri raise ValueError(f'Namespace "{uri}" is not declared in scope') def data(self, data): if not self._ignored_depth: self._data.append(data) def _flush(self, _join_text=''.join): data = _join_text(self._data) del self._data[:] if self._strip_text and not self._preserve_space[-1]: data = data.strip() if self._pending_start is not None: args, self._pending_start = self._pending_start, None qname_text = data if data and _looks_like_prefix_name(data) else None self._start(*args, qname_text) if qname_text is not None: return if data and self._root_seen: self._write(_escape_cdata_c14n(data)) def start_ns(self, prefix, uri): if self._ignored_depth: return # we may have to resolve qnames in text content if self._data: self._flush() self._ns_stack[-1].append((uri, prefix)) def start(self, tag, attrs): if self._exclude_tags is not None and ( self._ignored_depth or tag in self._exclude_tags): self._ignored_depth += 1 return if self._data: self._flush() new_namespaces = [] self._declared_ns_stack.append(new_namespaces) if self._qname_aware_tags is not None and tag in self._qname_aware_tags: # Need to parse text first to see if it requires a prefix declaration. self._pending_start = (tag, attrs, new_namespaces) return self._start(tag, attrs, new_namespaces) def _start(self, tag, attrs, new_namespaces, qname_text=None): if self._exclude_attrs is not None and attrs: attrs = {k: v for k, v in attrs.items() if k not in self._exclude_attrs} qnames = {tag, *attrs} resolved_names = {} # Resolve prefixes in attribute and tag text. if qname_text is not None: qname = resolved_names[qname_text] = self._resolve_prefix_name(qname_text) qnames.add(qname) if self._find_qname_aware_attrs is not None and attrs: qattrs = self._find_qname_aware_attrs(attrs) if qattrs: for attr_name in qattrs: value = attrs[attr_name] if _looks_like_prefix_name(value): qname = resolved_names[value] = self._resolve_prefix_name(value) qnames.add(qname) else: qattrs = None else: qattrs = None # Assign prefixes in lexicographical order of used URIs. parse_qname = self._qname parsed_qnames = {n: parse_qname(n) for n in sorted( qnames, key=lambda n: n.split('}', 1))} # Write namespace declarations in prefix order ... if new_namespaces: attr_list = [ ('xmlns:' + prefix if prefix else 'xmlns', uri) for uri, prefix in new_namespaces ] attr_list.sort() else: # almost always empty attr_list = [] # ... followed by attributes in URI+name order if attrs: for k, v in sorted(attrs.items()): if qattrs is not None and k in qattrs and v in resolved_names: v = parsed_qnames[resolved_names[v]][0] attr_qname, attr_name, uri = parsed_qnames[k] # No prefix for attributes in default ('') namespace. attr_list.append((attr_qname if uri else attr_name, v)) # Honour xml:space attributes. space_behaviour = attrs.get('{http://www.w3.org/XML/1998/namespace}space') self._preserve_space.append( space_behaviour == 'preserve' if space_behaviour else self._preserve_space[-1]) # Write the tag. write = self._write write('<' + parsed_qnames[tag][0]) if attr_list: write(''.join([f' {k}="{_escape_attrib_c14n(v)}"' for k, v in attr_list])) write('>') # Write the resolved qname text content. if qname_text is not None: write(_escape_cdata_c14n(parsed_qnames[resolved_names[qname_text]][0])) self._root_seen = True self._ns_stack.append([]) def end(self, tag): if self._ignored_depth: self._ignored_depth -= 1 return if self._data: self._flush() self._write(f'') self._preserve_space.pop() self._root_done = len(self._preserve_space) == 1 self._declared_ns_stack.pop() self._ns_stack.pop() def comment(self, text): if not self._with_comments: return if self._ignored_depth: return if self._root_done: self._write('\n') elif self._root_seen and self._data: self._flush() self._write(f'') if not self._root_seen: self._write('\n') def pi(self, target, data): if self._ignored_depth: return if self._root_done: self._write('\n') elif self._root_seen and self._data: self._flush() self._write( f'' if data else f'') if not self._root_seen: self._write('\n') def _escape_cdata_c14n(text): # 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('>', '>') if '\r' in text: text = text.replace('\r', ' ') return text except (TypeError, AttributeError): _raise_serialization_error(text) def _escape_attrib_c14n(text): # escape attribute value try: if '&' in text: text = text.replace('&', '&') if '<' in text: text = text.replace('<', '<') if '"' in text: text = text.replace('"', '"') if '\t' in text: text = text.replace('\t', ' ') if '\n' in text: text = text.replace('\n', ' ') if '\r' in text: text = text.replace('\r', ' ') return text except (TypeError, AttributeError): _raise_serialization_error(text) # -------------------------------------------------------------------- # Import the C accelerators try: # Element is going to be shadowed by the C implementation. We need to keep # the Python version of it accessible for some "creative" by external code # (see tests) _Element_Py = Element # Element, SubElement, ParseError, TreeBuilder, XMLParser, _set_factories from _elementtree import * from _elementtree import _set_factories except ImportError: pass else: _set_factories(Comment, ProcessingInstruction) lib64/python3.6/xml/etree/ElementTree.py000064400000157305152360312220013732 0ustar00"""Lightweight XML support for Python. XML is an inherently hierarchical data format, and the most natural way to represent it is with a tree. This module has two classes for this purpose: 1. ElementTree represents the whole XML document as a tree and 2. Element represents a single node in this tree. Interactions with the whole document (reading and writing to/from files) are usually done on the ElementTree level. Interactions with a single XML element and its sub-elements are done on the Element level. Element is a flexible container object designed to store hierarchical data structures in memory. It can be described as a cross between a list and a dictionary. Each Element has a number of properties associated with it: 'tag' - a string containing the element's name. 'attributes' - a Python dictionary storing the element's attributes. 'text' - a string containing the element's text content. 'tail' - an optional string containing text after the element's end tag. And a number of child elements stored in a Python sequence. To create an element instance, use the Element constructor, or the SubElement factory function. You can also use the ElementTree class to wrap an element structure and convert it to and from XML. """ #--------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/psf/license for licensing details. # # ElementTree # Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com # -------------------------------------------------------------------- # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- __all__ = [ # public symbols "Comment", "dump", "Element", "ElementTree", "fromstring", "fromstringlist", "iselement", "iterparse", "parse", "ParseError", "PI", "ProcessingInstruction", "QName", "SubElement", "tostring", "tostringlist", "TreeBuilder", "VERSION", "XML", "XMLID", "XMLParser", "XMLPullParser", "register_namespace", ] VERSION = "1.3.0" import sys import re import warnings import io import collections import contextlib from . import ElementPath class ParseError(SyntaxError): """An error when parsing an XML document. In addition to its exception value, a ParseError contains two extra attributes: 'code' - the specific exception code 'position' - the line and column of the error """ pass # -------------------------------------------------------------------- def iselement(element): """Return True if *element* appears to be an Element.""" return hasattr(element, 'tag') class Element: """An XML element. This class is the reference implementation of the Element interface. An element's length is its number of subelements. That means if you want to check if an element is truly empty, you should check BOTH its length AND its text attribute. The element tag, attribute names, and attribute values can be either bytes or strings. *tag* is the element name. *attrib* is an optional dictionary containing element attributes. *extra* are additional element attributes given as keyword arguments. Example form: text...tail """ tag = None """The element's name.""" attrib = None """Dictionary of the element's attributes.""" text = None """ Text before first subelement. This is either a string or the value None. Note that if there is no text, this attribute may be either None or the empty string, depending on the parser. """ tail = None """ Text after this element's end tag, but before the next sibling element's start tag. This is either a string or the value None. Note that if there was no text, this attribute may be either None or an empty string, depending on the parser. """ def __init__(self, tag, attrib={}, **extra): if not isinstance(attrib, dict): raise TypeError("attrib must be dict, not %s" % ( attrib.__class__.__name__,)) attrib = attrib.copy() attrib.update(extra) self.tag = tag self.attrib = attrib self._children = [] def __repr__(self): return "<%s %r at %#x>" % (self.__class__.__name__, self.tag, id(self)) def makeelement(self, tag, attrib): """Create a new element with the same type. *tag* is a string containing the element name. *attrib* is a dictionary containing the element attributes. Do not call this method, use the SubElement factory function instead. """ return self.__class__(tag, attrib) def copy(self): """Return copy of current element. This creates a shallow copy. Subelements will be shared with the original tree. """ elem = self.makeelement(self.tag, self.attrib) elem.text = self.text elem.tail = self.tail elem[:] = self return elem def __len__(self): return len(self._children) def __bool__(self): warnings.warn( "The behavior of this method will change in future versions. " "Use specific 'len(elem)' or 'elem is not None' test instead.", FutureWarning, stacklevel=2 ) return len(self._children) != 0 # emulate old behaviour, for now def __getitem__(self, index): return self._children[index] def __setitem__(self, index, element): # if isinstance(index, slice): # for elt in element: # assert iselement(elt) # else: # assert iselement(element) self._children[index] = element def __delitem__(self, index): del self._children[index] def append(self, subelement): """Add *subelement* to the end of this element. The new element will appear in document order after the last existing subelement (or directly after the text, if it's the first subelement), but before the end tag for this element. """ self._assert_is_element(subelement) self._children.append(subelement) def extend(self, elements): """Append subelements from a sequence. *elements* is a sequence with zero or more elements. """ for element in elements: self._assert_is_element(element) self._children.extend(elements) def insert(self, index, subelement): """Insert *subelement* at position *index*.""" self._assert_is_element(subelement) self._children.insert(index, subelement) def _assert_is_element(self, e): # Need to refer to the actual Python implementation, not the # shadowing C implementation. if not isinstance(e, _Element_Py): raise TypeError('expected an Element, not %s' % type(e).__name__) def remove(self, subelement): """Remove matching subelement. Unlike the find methods, this method compares elements based on identity, NOT ON tag value or contents. To remove subelements by other means, the easiest way is to use a list comprehension to select what elements to keep, and then use slice assignment to update the parent element. ValueError is raised if a matching element could not be found. """ # assert iselement(element) self._children.remove(subelement) def getchildren(self): """(Deprecated) Return all subelements. Elements are returned in document order. """ warnings.warn( "This method will be removed in future versions. " "Use 'list(elem)' or iteration over elem instead.", DeprecationWarning, stacklevel=2 ) return self._children def find(self, path, namespaces=None): """Find first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ return ElementPath.find(self, path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find text for first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *default* is the value to return if the element was not found, *namespaces* is an optional mapping from namespace prefix to full name. Return text content of first matching element, or default value if none was found. Note that if an element is found having no text content, the empty string is returned. """ return ElementPath.findtext(self, path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Returns list containing all matching elements in document order. """ return ElementPath.findall(self, path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ return ElementPath.iterfind(self, path, namespaces) def clear(self): """Reset element. This function removes all subelements, clears all attributes, and sets the text and tail attributes to None. """ self.attrib.clear() self._children = [] self.text = self.tail = None def get(self, key, default=None): """Get element attribute. Equivalent to attrib.get, but some implementations may handle this a bit more efficiently. *key* is what attribute to look for, and *default* is what to return if the attribute was not found. Returns a string containing the attribute value, or the default if attribute was not found. """ return self.attrib.get(key, default) def set(self, key, value): """Set element attribute. Equivalent to attrib[key] = value, but some implementations may handle this a bit more efficiently. *key* is what attribute to set, and *value* is the attribute value to set it to. """ self.attrib[key] = value def keys(self): """Get list of attribute names. Names are returned in an arbitrary order, just like an ordinary Python dict. Equivalent to attrib.keys() """ return self.attrib.keys() def items(self): """Get element attributes as a sequence. The attributes are returned in arbitrary order. Equivalent to attrib.items(). Return a list of (name, value) tuples. """ return self.attrib.items() def iter(self, tag=None): """Create tree iterator. The iterator loops over the element and all subelements in document order, returning all elements with a matching tag. 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. *tag* is what tags to look for (default is to return all elements) Return an iterator containing all the matching elements. """ if tag == "*": tag = None if tag is None or self.tag == tag: yield self for e in self._children: yield from e.iter(tag) # 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)) def itertext(self): """Create text iterator. The iterator loops over the element and all subelements in document order, returning all inner text. """ tag = self.tag if not isinstance(tag, str) and tag is not None: return t = self.text if t: yield t for e in self: yield from e.itertext() t = e.tail if t: yield t def SubElement(parent, tag, attrib={}, **extra): """Subelement factory which creates an element instance, and appends it to an existing parent. The element tag, attribute names, and attribute values can be either bytes or Unicode strings. *parent* is the parent element, *tag* is the subelements name, *attrib* is an optional directory containing element attributes, *extra* are additional attributes given as keyword arguments. """ attrib = attrib.copy() attrib.update(extra) element = parent.makeelement(tag, attrib) parent.append(element) return element def Comment(text=None): """Comment element factory. This function creates a special element which the standard serializer serializes as an XML comment. *text* is a string containing the comment string. """ element = Element(Comment) element.text = text return element def ProcessingInstruction(target, text=None): """Processing Instruction element factory. This function creates a special element which the standard serializer serializes as an XML comment. *target* is a string containing the processing instruction, *text* is a string containing the processing instruction contents, if any. """ element = Element(ProcessingInstruction) element.text = target if text: element.text = element.text + " " + text return element PI = ProcessingInstruction class QName: """Qualified name wrapper. This class can be used to wrap a QName attribute value in order to get proper namespace handing on output. *text_or_uri* is a string containing the QName value either in the form {uri}local, or if the tag argument is given, the URI part of a QName. *tag* is an optional argument which if given, will make the first argument (text_or_uri) be interpreted as a URI, and this argument (tag) be interpreted as a local name. """ def __init__(self, text_or_uri, tag=None): if tag: text_or_uri = "{%s}%s" % (text_or_uri, tag) self.text = text_or_uri def __str__(self): return self.text def __repr__(self): return '<%s %r>' % (self.__class__.__name__, self.text) def __hash__(self): return hash(self.text) def __le__(self, other): if isinstance(other, QName): return self.text <= other.text return self.text <= other def __lt__(self, other): if isinstance(other, QName): return self.text < other.text return self.text < other def __ge__(self, other): if isinstance(other, QName): return self.text >= other.text return self.text >= other def __gt__(self, other): if isinstance(other, QName): return self.text > other.text return self.text > other def __eq__(self, other): if isinstance(other, QName): return self.text == other.text return self.text == other # -------------------------------------------------------------------- class ElementTree: """An XML element hierarchy. This class also provides support for serialization to and from standard XML. *element* is an optional root element node, *file* is an optional file handle or file name of an XML file whose contents will be used to initialize the tree with. """ def __init__(self, element=None, file=None): # assert element is None or iselement(element) self._root = element # first node if file: self.parse(file) def getroot(self): """Return root element of this tree.""" return self._root def _setroot(self, element): """Replace root element of this tree. This will discard the current contents of the tree and replace it with the given element. Use with care! """ # assert iselement(element) self._root = element def parse(self, source, parser=None): """Load external XML document into element tree. *source* is a file name or file object, *parser* is an optional parser instance that defaults to XMLParser. ParseError is raised if the parser fails to parse the document. Returns the root element of the given source document. """ close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if parser is None: # If no parser was specified, create a default XMLParser parser = XMLParser() if hasattr(parser, '_parse_whole'): # The default XMLParser, when it comes from an accelerator, # can define an internal _parse_whole API for efficiency. # It can be used to parse the whole source without feeding # it with chunks. self._root = parser._parse_whole(source) return self._root while True: data = source.read(65536) if not data: break parser.feed(data) self._root = parser.close() return self._root finally: if close_source: source.close() def iter(self, tag=None): """Create and return tree iterator for the root element. The iterator loops over all elements in this tree, in document order. *tag* is a string with the tag name to iterate over (default is to return all elements). """ # assert self._root is not None return self._root.iter(tag) # compatibility def getiterator(self, tag=None): # 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)) def find(self, path, namespaces=None): """Find first matching element by tag name or path. Same as getroot().find(path), which is Element.find() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.find(path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find first matching element by tag name or path. Same as getroot().findtext(path), which is Element.findtext() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.findtext(path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().findall(path), which is Element.findall(). *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return list containing all matching elements in document order. """ # 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.findall(path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().iterfind(path), which is element.iterfind() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ # 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) def write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True): """Write element tree to a file as XML. Arguments: *file_or_filename* -- file name or a file object opened for writing *encoding* -- the output encoding (default: US-ASCII) *xml_declaration* -- bool indicating if an XML declaration should be added to the output. If None, an XML declaration is added if encoding IS NOT either of: US-ASCII, UTF-8, or Unicode *default_namespace* -- sets the default XML namespace (for "xmlns") *method* -- either "xml" (default), "html, "text", or "c14n" *short_empty_elements* -- controls the formatting of elements that contain no content. If True (default) they are emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags """ if not method: method = "xml" elif method not in _serialize: raise ValueError("unknown method %r" % method) if not encoding: if method == "c14n": encoding = "utf-8" else: encoding = "us-ascii" enc_lower = encoding.lower() with _get_writer(file_or_filename, enc_lower) as write: if method == "xml" and (xml_declaration or (xml_declaration is None and enc_lower not in ("utf-8", "us-ascii", "unicode"))): declared_encoding = encoding if enc_lower == "unicode": # Retrieve the default encoding for the xml declaration import locale declared_encoding = locale.getpreferredencoding() write("\n" % ( declared_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, short_empty_elements=short_empty_elements) def write_c14n(self, file): # lxml.etree compatibility. use output method instead return self.write(file, method="c14n") # -------------------------------------------------------------------- # serialization support @contextlib.contextmanager def _get_writer(file_or_filename, encoding): # returns text write method and release all resources after using try: write = file_or_filename.write except AttributeError: # file_or_filename is a file name if encoding == "unicode": file = open(file_or_filename, "w") else: file = open(file_or_filename, "w", encoding=encoding, errors="xmlcharrefreplace") with file: yield file.write else: # file_or_filename is a file-like object # encoding determines if it is a text or binary writer if encoding == "unicode": # use a text writer as is yield write else: # wrap a binary writer with TextIOWrapper with contextlib.ExitStack() as stack: if isinstance(file_or_filename, io.BufferedIOBase): file = file_or_filename elif isinstance(file_or_filename, io.RawIOBase): file = io.BufferedWriter(file_or_filename) # Keep the original file open when the BufferedWriter is # destroyed stack.callback(file.detach) else: # This is to handle passed objects that aren't in the # IOBase hierarchy, but just have a write method file = io.BufferedIOBase() file.writable = lambda: True file.write = write try: # TextIOWrapper uses this methods to determine # if BOM (for UTF-16, etc) should be added file.seekable = file_or_filename.seekable file.tell = file_or_filename.tell except AttributeError: pass file = io.TextIOWrapper(file, encoding=encoding, errors="xmlcharrefreplace", newline="\n") # Keep the original file open when the TextIOWrapper is # destroyed stack.callback(file.detach) yield file.write def _namespaces(elem, 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 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) # populate qname and namespaces table for elem in elem.iter(): tag = elem.tag if isinstance(tag, QName): if 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, short_empty_elements, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % text) elif tag is ProcessingInstruction: write("" % text) else: tag = qnames[tag] if tag is None: if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) 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) or not short_empty_elements: write(">") if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) write("") 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, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % _escape_cdata(text)) elif tag is ProcessingInstruction: write("" % _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(">") ltag = tag.lower() if text: if ltag == "script" or ltag == "style": write(text) else: write(_escape_cdata(text)) for e in elem: _serialize_html(write, e, qnames, None) if ltag not in HTML_EMPTY: write("") 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, } def register_namespace(prefix, uri): """Register a namespace prefix. The registry is global, and any existing mapping for either the given prefix or the namespace URI will be removed. *prefix* is the namespace prefix, *uri* is a namespace uri. Tags and attributes in this namespace will be serialized with prefix if possible. ValueError is raised if prefix is reserved or is invalid. """ if re.match(r"ns\d+$", prefix): raise ValueError("Prefix format reserved for internal use") for k, v in list(_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", } # For tests and troubleshooting register_namespace._namespace_map = _namespace_map def _raise_serialization_error(text): raise TypeError( "cannot serialize %r (type %s)" % (text, type(text).__name__) ) def _escape_cdata(text): # escape character data try: # it's worth avoiding do-nothing calls for strings that are # shorter than 500 characters, 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) def _escape_attrib(text): # 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("\"", """) # The following business with carriage returns is to satisfy # Section 2.11 of the XML specification, stating that # CR or CR LN should be replaced with just LN # http://www.w3.org/TR/REC-xml/#sec-line-ends if "\r\n" in text: text = text.replace("\r\n", "\n") if "\r" in text: text = text.replace("\r", "\n") #The following four lines are issue 17582 if "\n" in text: text = text.replace("\n", " ") if "\t" in text: text = text.replace("\t", " ") return text except (TypeError, AttributeError): _raise_serialization_error(text) def _escape_attrib_html(text): # escape attribute value try: 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 tostring(element, encoding=None, method=None, *, short_empty_elements=True): """Generate string representation of XML element. All subelements are included. If encoding is "unicode", a string is returned. Otherwise a bytestring is returned. *element* is an Element instance, *encoding* is an optional output encoding defaulting to US-ASCII, *method* is an optional output which can be one of "xml" (default), "html", "text" or "c14n". Returns an (optionally) encoded string containing the XML data. """ stream = io.StringIO() if encoding == 'unicode' else io.BytesIO() ElementTree(element).write(stream, encoding, method=method, short_empty_elements=short_empty_elements) return stream.getvalue() class _ListDataStream(io.BufferedIOBase): """An auxiliary stream accumulating into a list reference.""" def __init__(self, lst): self.lst = lst def writable(self): return True def seekable(self): return True def write(self, b): self.lst.append(b) def tell(self): return len(self.lst) def tostringlist(element, encoding=None, method=None, *, short_empty_elements=True): lst = [] stream = _ListDataStream(lst) ElementTree(element).write(stream, encoding, method=method, short_empty_elements=short_empty_elements) return lst def dump(elem): """Write element tree or element structure to sys.stdout. This function should be used for debugging only. *elem* is either an ElementTree, or a single Element. The exact output format is implementation dependent. In this version, it's written as an ordinary XML file. """ # debugging if not isinstance(elem, ElementTree): elem = ElementTree(elem) elem.write(sys.stdout, encoding="unicode") tail = elem.getroot().tail if not tail or tail[-1] != "\n": sys.stdout.write("\n") # -------------------------------------------------------------------- # parsing def parse(source, parser=None): """Parse XML document into element tree. *source* is a filename or file object containing XML data, *parser* is an optional parser instance defaulting to XMLParser. Return an ElementTree instance. """ tree = ElementTree() tree.parse(source, parser) return tree def iterparse(source, events=None, parser=None): """Incrementally parse XML document into ElementTree. This class also reports what's going on to the user based on the *events* it is initialized with. The supported events are the strings "start", "end", "start-ns" and "end-ns" (the "ns" events are used to get detailed namespace information). If *events* is omitted, only "end" events are reported. *source* is a filename or file object containing XML data, *events* is a list of events to report back, *parser* is an optional parser instance. Returns an iterator providing (event, elem) pairs. """ # Use the internal, undocumented _parser argument for now; When the # parser argument of iterparse is removed, this can be killed. pullparser = XMLPullParser(events=events, _parser=parser) def iterator(): try: while True: yield from pullparser.read_events() # load event buffer data = source.read(16 * 1024) if not data: break pullparser.feed(data) root = pullparser._close_and_return_root() yield from pullparser.read_events() it.root = root finally: if close_source: source.close() class IterParseIterator(collections.Iterator): __next__ = iterator().__next__ it = IterParseIterator() it.root = None del iterator, IterParseIterator close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True return it class XMLPullParser: def __init__(self, events=None, *, _parser=None): # The _parser argument is for internal use only and must not be relied # upon in user code. It will be removed in a future release. # See http://bugs.python.org/issue17741 for more details. self._events_queue = collections.deque() self._parser = _parser or XMLParser(target=TreeBuilder()) # wire up the parser for event reporting if events is None: events = ("end",) self._parser._setevents(self._events_queue, events) def feed(self, data): """Feed encoded data to parser.""" if self._parser is None: raise ValueError("feed() called after end of stream") if data: try: self._parser.feed(data) except SyntaxError as exc: self._events_queue.append(exc) def _close_and_return_root(self): # iterparse needs this to set its root attribute properly :( root = self._parser.close() self._parser = None return root def close(self): """Finish feeding data to parser. Unlike XMLParser, does not return the root element. Use read_events() to consume elements from XMLPullParser. """ self._close_and_return_root() def read_events(self): """Return an iterator over currently available (event, elem) pairs. Events are consumed from the internal event queue as they are retrieved from the iterator. """ events = self._events_queue while events: event = events.popleft() if isinstance(event, Exception): raise event else: yield event def XML(text, parser=None): """Parse XML document from string constant. This function can be used to embed "XML Literals" in Python code. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) return parser.close() def XMLID(text, parser=None): """Parse XML document from string constant for its IDs. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an (Element, dict) tuple, in which the dict maps element id:s to elements. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) tree = parser.close() ids = {} for elem in tree.iter(): id = elem.get("id") if id: ids[id] = elem return tree, ids # Parse XML document from string constant. Alias for XML(). fromstring = XML def fromstringlist(sequence, parser=None): """Parse XML document from sequence of string fragments. *sequence* is a list of other sequence, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) for text in sequence: parser.feed(text) return parser.close() # -------------------------------------------------------------------- class TreeBuilder: """Generic element structure builder. This builder converts a sequence of start, data, and end method calls to a well-formed element structure. You can use this class to build an element structure using a custom XML parser, or a parser for some other XML-like format. *element_factory* is an optional element factory which is called to create new Element instances, as necessary. """ def __init__(self, element_factory=None): self._data = [] # data collector self._elem = [] # element stack self._last = None # last element self._tail = None # true if we're after an end tag if element_factory is None: element_factory = Element self._factory = element_factory def close(self): """Flush builder buffers and return toplevel document Element.""" assert len(self._elem) == 0, "missing end tags" assert self._last is not None, "missing toplevel element" return self._last def _flush(self): if self._data: if self._last is not None: text = "".join(self._data) if self._tail: assert self._last.tail is None, "internal error (tail)" self._last.tail = text else: assert self._last.text is None, "internal error (text)" self._last.text = text self._data = [] def data(self, data): """Add text to current element.""" self._data.append(data) def start(self, tag, attrs): """Open new element and return it. *tag* is the element name, *attrs* is a dict containing element attributes. """ self._flush() self._last = elem = self._factory(tag, attrs) if self._elem: self._elem[-1].append(elem) self._elem.append(elem) self._tail = 0 return elem def end(self, tag): """Close and return current Element. *tag* is the element name. """ self._flush() self._last = self._elem.pop() assert self._last.tag == tag,\ "end tag mismatch (expected %s, got %s)" % ( self._last.tag, tag) self._tail = 1 return self._last # also see ElementTree and TreeBuilder class XMLParser: """Element structure builder for XML source data based on the expat parser. *html* are predefined HTML entities (deprecated and not supported), *target* is an optional target object which defaults to an instance of the standard TreeBuilder class, *encoding* is an optional encoding string which if given, overrides the encoding specified in the XML file: http://www.iana.org/assignments/character-sets """ def __init__(self, html=0, target=None, encoding=None): try: from xml.parsers import expat except ImportError: 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() # 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 # main callbacks parser.DefaultHandlerExpand = self._default if hasattr(target, 'start'): parser.StartElementHandler = self._start if hasattr(target, 'end'): parser.EndElementHandler = self._end if hasattr(target, 'data'): parser.CharacterDataHandler = target.data # miscellaneous callbacks if hasattr(target, 'comment'): parser.CommentHandler = target.comment if hasattr(target, 'pi'): parser.ProcessingInstructionHandler = target.pi # Configure pyexpat: buffering, new-style attribute handling. parser.buffer_text = 1 parser.ordered_attributes = 1 parser.specified_attributes = 1 self._doctype = None self.entity = {} try: self.version = "Expat %d.%d.%d" % expat.version_info except AttributeError: pass # unknown def _setevents(self, events_queue, events_to_report): # Internal API for XMLPullParser # events_to_report: a list of events to report during parsing (same as # the *events* of XMLPullParser's constructor. # events_queue: a list of actual parsing events that will be populated # by the underlying parser. # parser = self._parser append = events_queue.append for event_name in events_to_report: if event_name == "start": parser.ordered_attributes = 1 parser.specified_attributes = 1 def handler(tag, attrib_in, event=event_name, append=append, start=self._start): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler elif event_name == "end": def handler(tag, event=event_name, append=append, end=self._end): append((event, end(tag))) parser.EndElementHandler = handler elif event_name == "start-ns": def handler(prefix, uri, event=event_name, append=append): append((event, (prefix or "", uri or ""))) parser.StartNamespaceDeclHandler = handler elif event_name == "end-ns": def handler(prefix, event=event_name, append=append): append((event, None)) parser.EndNamespaceDeclHandler = handler else: raise ValueError("unknown event %r" % event_name) 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 try: name = self._names[key] except KeyError: name = key if "}" in name: name = "{" + name self._names[key] = name return name def _start(self, tag, attr_list): # Handler for expat's StartElementHandler. Since ordered_attributes # is set, the attributes are reported as a list of alternating # attribute name,value. fixname = self._fixname tag = fixname(tag) attrib = {} if attr_list: for i in range(0, len(attr_list), 2): attrib[fixname(attr_list[i])] = attr_list[i+1] return self.target.start(tag, attrib) def _end(self, tag): return self.target.end(self._fixname(tag)) def _default(self, text): prefix = text[:1] if prefix == "&": # deal with undefined entities try: data_handler = self.target.data except AttributeError: return try: data_handler(self.entity[text[1:-1]]) except KeyError: from xml.parsers import expat 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 = text.strip() if not text: return self._doctype.append(text) n = len(self._doctype) if n > 2: type = self._doctype[1] if type == "PUBLIC" and n == 4: name, type, pubid, system = self._doctype if pubid: pubid = pubid[1:-1] elif type == "SYSTEM" and n == 3: name, type, system = self._doctype pubid = None else: return if hasattr(self.target, "doctype"): self.target.doctype(name, pubid, system[1:-1]) elif self.doctype != self._XMLParser__doctype: # warn about deprecated call self._XMLParser__doctype(name, pubid, system[1:-1]) self.doctype(name, pubid, system[1:-1]) self._doctype = None def doctype(self, name, pubid, system): """(Deprecated) Handle doctype declaration *name* is the Doctype name, *pubid* is the public identifier, and *system* is the system identifier. """ warnings.warn( "This method of XMLParser is deprecated. Define doctype() " "method on the TreeBuilder target.", DeprecationWarning, ) # sentinel, if doctype is redefined in a subclass __doctype = doctype def feed(self, data): """Feed encoded data to parser.""" try: self.parser.Parse(data, 0) except self._error as v: self._raiseerror(v) def close(self): """Finish feeding data to parser and return element structure.""" try: self.parser.Parse("", 1) # end of data except self._error as v: self._raiseerror(v) try: close_handler = self.target.close except AttributeError: pass else: return close_handler() finally: # get rid of circular references del self.parser, self._parser del self.target, self._target # Import the C accelerators try: # Element is going to be shadowed by the C implementation. We need to keep # the Python version of it accessible for some "creative" by external code # (see tests) _Element_Py = Element # Element, SubElement, ParseError, TreeBuilder, XMLParser from _elementtree import * except ImportError: pass lib64/python3.12/xml/etree/ElementTree.py000064400000220441152360725130014007 0ustar00"""Lightweight XML support for Python. XML is an inherently hierarchical data format, and the most natural way to represent it is with a tree. This module has two classes for this purpose: 1. ElementTree represents the whole XML document as a tree and 2. Element represents a single node in this tree. Interactions with the whole document (reading and writing to/from files) are usually done on the ElementTree level. Interactions with a single XML element and its sub-elements are done on the Element level. Element is a flexible container object designed to store hierarchical data structures in memory. It can be described as a cross between a list and a dictionary. Each Element has a number of properties associated with it: 'tag' - a string containing the element's name. 'attributes' - a Python dictionary storing the element's attributes. 'text' - a string containing the element's text content. 'tail' - an optional string containing text after the element's end tag. And a number of child elements stored in a Python sequence. To create an element instance, use the Element constructor, or the SubElement factory function. You can also use the ElementTree class to wrap an element structure and convert it to and from XML. """ #--------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See https://www.python.org/psf/license for licensing details. # # ElementTree # Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com # -------------------------------------------------------------------- # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- __all__ = [ # public symbols "Comment", "dump", "Element", "ElementTree", "fromstring", "fromstringlist", "indent", "iselement", "iterparse", "parse", "ParseError", "PI", "ProcessingInstruction", "QName", "SubElement", "tostring", "tostringlist", "TreeBuilder", "VERSION", "XML", "XMLID", "XMLParser", "XMLPullParser", "register_namespace", "canonicalize", "C14NWriterTarget", ] VERSION = "1.3.0" import sys import re import warnings import io import collections import collections.abc import contextlib import weakref from . import ElementPath class ParseError(SyntaxError): """An error when parsing an XML document. In addition to its exception value, a ParseError contains two extra attributes: 'code' - the specific exception code 'position' - the line and column of the error """ pass # -------------------------------------------------------------------- def iselement(element): """Return True if *element* appears to be an Element.""" return hasattr(element, 'tag') class Element: """An XML element. This class is the reference implementation of the Element interface. An element's length is its number of subelements. That means if you want to check if an element is truly empty, you should check BOTH its length AND its text attribute. The element tag, attribute names, and attribute values can be either bytes or strings. *tag* is the element name. *attrib* is an optional dictionary containing element attributes. *extra* are additional element attributes given as keyword arguments. Example form: text...tail """ tag = None """The element's name.""" attrib = None """Dictionary of the element's attributes.""" text = None """ Text before first subelement. This is either a string or the value None. Note that if there is no text, this attribute may be either None or the empty string, depending on the parser. """ tail = None """ Text after this element's end tag, but before the next sibling element's start tag. This is either a string or the value None. Note that if there was no text, this attribute may be either None or an empty string, depending on the parser. """ def __init__(self, tag, attrib={}, **extra): if not isinstance(attrib, dict): raise TypeError("attrib must be dict, not %s" % ( attrib.__class__.__name__,)) self.tag = tag self.attrib = {**attrib, **extra} self._children = [] def __repr__(self): return "<%s %r at %#x>" % (self.__class__.__name__, self.tag, id(self)) def makeelement(self, tag, attrib): """Create a new element with the same type. *tag* is a string containing the element name. *attrib* is a dictionary containing the element attributes. Do not call this method, use the SubElement factory function instead. """ return self.__class__(tag, attrib) def __copy__(self): elem = self.makeelement(self.tag, self.attrib) elem.text = self.text elem.tail = self.tail elem[:] = self return elem def __len__(self): return len(self._children) def __bool__(self): warnings.warn( "Testing an element's truth value will always return True in " "future versions. " "Use specific 'len(elem)' or 'elem is not None' test instead.", DeprecationWarning, stacklevel=2 ) return len(self._children) != 0 # emulate old behaviour, for now def __getitem__(self, index): return self._children[index] def __setitem__(self, index, element): if isinstance(index, slice): for elt in element: self._assert_is_element(elt) else: self._assert_is_element(element) self._children[index] = element def __delitem__(self, index): del self._children[index] def append(self, subelement): """Add *subelement* to the end of this element. The new element will appear in document order after the last existing subelement (or directly after the text, if it's the first subelement), but before the end tag for this element. """ self._assert_is_element(subelement) self._children.append(subelement) def extend(self, elements): """Append subelements from a sequence. *elements* is a sequence with zero or more elements. """ for element in elements: self._assert_is_element(element) self._children.append(element) def insert(self, index, subelement): """Insert *subelement* at position *index*.""" self._assert_is_element(subelement) self._children.insert(index, subelement) def _assert_is_element(self, e): # Need to refer to the actual Python implementation, not the # shadowing C implementation. if not isinstance(e, _Element_Py): raise TypeError('expected an Element, not %s' % type(e).__name__) def remove(self, subelement): """Remove matching subelement. Unlike the find methods, this method compares elements based on identity, NOT ON tag value or contents. To remove subelements by other means, the easiest way is to use a list comprehension to select what elements to keep, and then use slice assignment to update the parent element. ValueError is raised if a matching element could not be found. """ # assert iselement(element) self._children.remove(subelement) def find(self, path, namespaces=None): """Find first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ return ElementPath.find(self, path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find text for first matching element by tag name or path. *path* is a string having either an element tag or an XPath, *default* is the value to return if the element was not found, *namespaces* is an optional mapping from namespace prefix to full name. Return text content of first matching element, or default value if none was found. Note that if an element is found having no text content, the empty string is returned. """ return ElementPath.findtext(self, path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Returns list containing all matching elements in document order. """ return ElementPath.findall(self, path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ return ElementPath.iterfind(self, path, namespaces) def clear(self): """Reset element. This function removes all subelements, clears all attributes, and sets the text and tail attributes to None. """ self.attrib.clear() self._children = [] self.text = self.tail = None def get(self, key, default=None): """Get element attribute. Equivalent to attrib.get, but some implementations may handle this a bit more efficiently. *key* is what attribute to look for, and *default* is what to return if the attribute was not found. Returns a string containing the attribute value, or the default if attribute was not found. """ return self.attrib.get(key, default) def set(self, key, value): """Set element attribute. Equivalent to attrib[key] = value, but some implementations may handle this a bit more efficiently. *key* is what attribute to set, and *value* is the attribute value to set it to. """ self.attrib[key] = value def keys(self): """Get list of attribute names. Names are returned in an arbitrary order, just like an ordinary Python dict. Equivalent to attrib.keys() """ return self.attrib.keys() def items(self): """Get element attributes as a sequence. The attributes are returned in arbitrary order. Equivalent to attrib.items(). Return a list of (name, value) tuples. """ return self.attrib.items() def iter(self, tag=None): """Create tree iterator. The iterator loops over the element and all subelements in document order, returning all elements with a matching tag. 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. *tag* is what tags to look for (default is to return all elements) Return an iterator containing all the matching elements. """ if tag == "*": tag = None if tag is None or self.tag == tag: yield self for e in self._children: yield from e.iter(tag) def itertext(self): """Create text iterator. The iterator loops over the element and all subelements in document order, returning all inner text. """ tag = self.tag if not isinstance(tag, str) and tag is not None: return t = self.text if t: yield t for e in self: yield from e.itertext() t = e.tail if t: yield t def SubElement(parent, tag, attrib={}, **extra): """Subelement factory which creates an element instance, and appends it to an existing parent. The element tag, attribute names, and attribute values can be either bytes or Unicode strings. *parent* is the parent element, *tag* is the subelements name, *attrib* is an optional directory containing element attributes, *extra* are additional attributes given as keyword arguments. """ attrib = {**attrib, **extra} element = parent.makeelement(tag, attrib) parent.append(element) return element def Comment(text=None): """Comment element factory. This function creates a special element which the standard serializer serializes as an XML comment. *text* is a string containing the comment string. """ element = Element(Comment) element.text = text return element def ProcessingInstruction(target, text=None): """Processing Instruction element factory. This function creates a special element which the standard serializer serializes as an XML comment. *target* is a string containing the processing instruction, *text* is a string containing the processing instruction contents, if any. """ element = Element(ProcessingInstruction) element.text = target if text: element.text = element.text + " " + text return element PI = ProcessingInstruction class QName: """Qualified name wrapper. This class can be used to wrap a QName attribute value in order to get proper namespace handing on output. *text_or_uri* is a string containing the QName value either in the form {uri}local, or if the tag argument is given, the URI part of a QName. *tag* is an optional argument which if given, will make the first argument (text_or_uri) be interpreted as a URI, and this argument (tag) be interpreted as a local name. """ def __init__(self, text_or_uri, tag=None): if tag: text_or_uri = "{%s}%s" % (text_or_uri, tag) self.text = text_or_uri def __str__(self): return self.text def __repr__(self): return '<%s %r>' % (self.__class__.__name__, self.text) def __hash__(self): return hash(self.text) def __le__(self, other): if isinstance(other, QName): return self.text <= other.text return self.text <= other def __lt__(self, other): if isinstance(other, QName): return self.text < other.text return self.text < other def __ge__(self, other): if isinstance(other, QName): return self.text >= other.text return self.text >= other def __gt__(self, other): if isinstance(other, QName): return self.text > other.text return self.text > other def __eq__(self, other): if isinstance(other, QName): return self.text == other.text return self.text == other # -------------------------------------------------------------------- class ElementTree: """An XML element hierarchy. This class also provides support for serialization to and from standard XML. *element* is an optional root element node, *file* is an optional file handle or file name of an XML file whose contents will be used to initialize the tree with. """ def __init__(self, element=None, file=None): # assert element is None or iselement(element) self._root = element # first node if file: self.parse(file) def getroot(self): """Return root element of this tree.""" return self._root def _setroot(self, element): """Replace root element of this tree. This will discard the current contents of the tree and replace it with the given element. Use with care! """ # assert iselement(element) self._root = element def parse(self, source, parser=None): """Load external XML document into element tree. *source* is a file name or file object, *parser* is an optional parser instance that defaults to XMLParser. ParseError is raised if the parser fails to parse the document. Returns the root element of the given source document. """ close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if parser is None: # If no parser was specified, create a default XMLParser parser = XMLParser() if hasattr(parser, '_parse_whole'): # The default XMLParser, when it comes from an accelerator, # can define an internal _parse_whole API for efficiency. # It can be used to parse the whole source without feeding # it with chunks. self._root = parser._parse_whole(source) return self._root while data := source.read(65536): parser.feed(data) self._root = parser.close() return self._root finally: if close_source: source.close() def iter(self, tag=None): """Create and return tree iterator for the root element. The iterator loops over all elements in this tree, in document order. *tag* is a string with the tag name to iterate over (default is to return all elements). """ # assert self._root is not None return self._root.iter(tag) def find(self, path, namespaces=None): """Find first matching element by tag name or path. Same as getroot().find(path), which is Element.find() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.find(path, namespaces) def findtext(self, path, default=None, namespaces=None): """Find first matching element by tag name or path. Same as getroot().findtext(path), which is Element.findtext() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return the first matching element, or None if no element was found. """ # 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.findtext(path, default, namespaces) def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().findall(path), which is Element.findall(). *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return list containing all matching elements in document order. """ # 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.findall(path, namespaces) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. Same as getroot().iterfind(path), which is element.iterfind() *path* is a string having either an element tag or an XPath, *namespaces* is an optional mapping from namespace prefix to full name. Return an iterable yielding all matching elements in document order. """ # 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) def write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True): """Write element tree to a file as XML. Arguments: *file_or_filename* -- file name or a file object opened for writing *encoding* -- the output encoding (default: US-ASCII) *xml_declaration* -- bool indicating if an XML declaration should be added to the output. If None, an XML declaration is added if encoding IS NOT either of: US-ASCII, UTF-8, or Unicode *default_namespace* -- sets the default XML namespace (for "xmlns") *method* -- either "xml" (default), "html, "text", or "c14n" *short_empty_elements* -- controls the formatting of elements that contain no content. If True (default) they are emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags """ if not method: method = "xml" elif method not in _serialize: raise ValueError("unknown method %r" % method) if not encoding: if method == "c14n": encoding = "utf-8" else: encoding = "us-ascii" with _get_writer(file_or_filename, encoding) as (write, declared_encoding): if method == "xml" and (xml_declaration or (xml_declaration is None and encoding.lower() != "unicode" and declared_encoding.lower() not in ("utf-8", "us-ascii"))): write("\n" % ( declared_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, short_empty_elements=short_empty_elements) def write_c14n(self, file): # lxml.etree compatibility. use output method instead return self.write(file, method="c14n") # -------------------------------------------------------------------- # serialization support @contextlib.contextmanager def _get_writer(file_or_filename, encoding): # returns text write method and release all resources after using try: write = file_or_filename.write except AttributeError: # file_or_filename is a file name if encoding.lower() == "unicode": encoding="utf-8" with open(file_or_filename, "w", encoding=encoding, errors="xmlcharrefreplace") as file: yield file.write, encoding else: # file_or_filename is a file-like object # encoding determines if it is a text or binary writer if encoding.lower() == "unicode": # use a text writer as is yield write, getattr(file_or_filename, "encoding", None) or "utf-8" else: # wrap a binary writer with TextIOWrapper with contextlib.ExitStack() as stack: if isinstance(file_or_filename, io.BufferedIOBase): file = file_or_filename elif isinstance(file_or_filename, io.RawIOBase): file = io.BufferedWriter(file_or_filename) # Keep the original file open when the BufferedWriter is # destroyed stack.callback(file.detach) else: # This is to handle passed objects that aren't in the # IOBase hierarchy, but just have a write method file = io.BufferedIOBase() file.writable = lambda: True file.write = write try: # TextIOWrapper uses this methods to determine # if BOM (for UTF-16, etc) should be added file.seekable = file_or_filename.seekable file.tell = file_or_filename.tell except AttributeError: pass file = io.TextIOWrapper(file, encoding=encoding, errors="xmlcharrefreplace", newline="\n") # Keep the original file open when the TextIOWrapper is # destroyed stack.callback(file.detach) yield file.write, encoding def _namespaces(elem, 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 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) # populate qname and namespaces table for elem in elem.iter(): tag = elem.tag if isinstance(tag, QName): if 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, short_empty_elements, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % text) elif tag is ProcessingInstruction: write("" % text) else: tag = qnames[tag] if tag is None: if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) 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 items: 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) or not short_empty_elements: write(">") if text: write(_escape_cdata(text)) for e in elem: _serialize_xml(write, e, qnames, None, short_empty_elements=short_empty_elements) write("") else: write(" />") if elem.tail: write(_escape_cdata(elem.tail)) HTML_EMPTY = {"area", "base", "basefont", "br", "col", "embed", "frame", "hr", "img", "input", "isindex", "link", "meta", "param", "source", "track", "wbr"} def _serialize_html(write, elem, qnames, namespaces, **kwargs): tag = elem.tag text = elem.text if tag is Comment: write("" % _escape_cdata(text)) elif tag is ProcessingInstruction: write("" % _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 items: 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(">") ltag = tag.lower() if text: if ltag == "script" or ltag == "style": write(text) else: write(_escape_cdata(text)) for e in elem: _serialize_html(write, e, qnames, None) if ltag not in HTML_EMPTY: write("") 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, } def register_namespace(prefix, uri): """Register a namespace prefix. The registry is global, and any existing mapping for either the given prefix or the namespace URI will be removed. *prefix* is the namespace prefix, *uri* is a namespace uri. Tags and attributes in this namespace will be serialized with prefix if possible. ValueError is raised if prefix is reserved or is invalid. """ if re.match(r"ns\d+$", prefix): raise ValueError("Prefix format reserved for internal use") for k, v in list(_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", } # For tests and troubleshooting register_namespace._namespace_map = _namespace_map def _raise_serialization_error(text): raise TypeError( "cannot serialize %r (type %s)" % (text, type(text).__name__) ) def _escape_cdata(text): # escape character data try: # it's worth avoiding do-nothing calls for strings that are # shorter than 500 characters, 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) def _escape_attrib(text): # 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("\"", """) # Although section 2.11 of the XML specification states that CR or # CR LN should be replaced with just LN, it applies only to EOLNs # which take part of organizing file into lines. Within attributes, # we are replacing these with entity numbers, so they do not count. # http://www.w3.org/TR/REC-xml/#sec-line-ends # The current solution, contained in following six lines, was # discussed in issue 17582 and 39011. if "\r" in text: text = text.replace("\r", " ") if "\n" in text: text = text.replace("\n", " ") if "\t" in text: text = text.replace("\t", " ") return text except (TypeError, AttributeError): _raise_serialization_error(text) def _escape_attrib_html(text): # escape attribute value try: 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 tostring(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True): """Generate string representation of XML element. All subelements are included. If encoding is "unicode", a string is returned. Otherwise a bytestring is returned. *element* is an Element instance, *encoding* is an optional output encoding defaulting to US-ASCII, *method* is an optional output which can be one of "xml" (default), "html", "text" or "c14n", *default_namespace* sets the default XML namespace (for "xmlns"). Returns an (optionally) encoded string containing the XML data. """ stream = io.StringIO() if encoding == 'unicode' else io.BytesIO() ElementTree(element).write(stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements) return stream.getvalue() class _ListDataStream(io.BufferedIOBase): """An auxiliary stream accumulating into a list reference.""" def __init__(self, lst): self.lst = lst def writable(self): return True def seekable(self): return True def write(self, b): self.lst.append(b) def tell(self): return len(self.lst) def tostringlist(element, encoding=None, method=None, *, xml_declaration=None, default_namespace=None, short_empty_elements=True): lst = [] stream = _ListDataStream(lst) ElementTree(element).write(stream, encoding, xml_declaration=xml_declaration, default_namespace=default_namespace, method=method, short_empty_elements=short_empty_elements) return lst def dump(elem): """Write element tree or element structure to sys.stdout. This function should be used for debugging only. *elem* is either an ElementTree, or a single Element. The exact output format is implementation dependent. In this version, it's written as an ordinary XML file. """ # debugging if not isinstance(elem, ElementTree): elem = ElementTree(elem) elem.write(sys.stdout, encoding="unicode") tail = elem.getroot().tail if not tail or tail[-1] != "\n": sys.stdout.write("\n") def indent(tree, space=" ", level=0): """Indent an XML document by inserting newlines and indentation space after elements. *tree* is the ElementTree or Element to modify. The (root) element itself will not be changed, but the tail text of all elements in its subtree will be adapted. *space* is the whitespace to insert for each indentation level, two space characters by default. *level* is the initial indentation level. Setting this to a higher value than 0 can be used for indenting subtrees that are more deeply nested inside of a document. """ if isinstance(tree, ElementTree): tree = tree.getroot() if level < 0: raise ValueError(f"Initial indentation level must be >= 0, got {level}") if not len(tree): return # Reduce the memory consumption by reusing indentation strings. indentations = ["\n" + level * space] def _indent_children(elem, level): # Start a new indentation level for the first child. child_level = level + 1 try: child_indentation = indentations[child_level] except IndexError: child_indentation = indentations[level] + space indentations.append(child_indentation) if not elem.text or not elem.text.strip(): elem.text = child_indentation for child in elem: if len(child): _indent_children(child, child_level) if not child.tail or not child.tail.strip(): child.tail = child_indentation # Dedent after the last child by overwriting the previous indentation. if not child.tail.strip(): child.tail = indentations[level] _indent_children(tree, 0) # -------------------------------------------------------------------- # parsing def parse(source, parser=None): """Parse XML document into element tree. *source* is a filename or file object containing XML data, *parser* is an optional parser instance defaulting to XMLParser. Return an ElementTree instance. """ tree = ElementTree() tree.parse(source, parser) return tree def iterparse(source, events=None, parser=None): """Incrementally parse XML document into ElementTree. This class also reports what's going on to the user based on the *events* it is initialized with. The supported events are the strings "start", "end", "start-ns" and "end-ns" (the "ns" events are used to get detailed namespace information). If *events* is omitted, only "end" events are reported. *source* is a filename or file object containing XML data, *events* is a list of events to report back, *parser* is an optional parser instance. Returns an iterator providing (event, elem) pairs. """ # Use the internal, undocumented _parser argument for now; When the # parser argument of iterparse is removed, this can be killed. pullparser = XMLPullParser(events=events, _parser=parser) if not hasattr(source, "read"): source = open(source, "rb") close_source = True else: close_source = False def iterator(source): try: while True: yield from pullparser.read_events() # load event buffer data = source.read(16 * 1024) if not data: break pullparser.feed(data) root = pullparser._close_and_return_root() yield from pullparser.read_events() it = wr() if it is not None: it.root = root finally: if close_source: source.close() class IterParseIterator(collections.abc.Iterator): __next__ = iterator(source).__next__ def __del__(self): if close_source: source.close() it = IterParseIterator() it.root = None wr = weakref.ref(it) return it class XMLPullParser: def __init__(self, events=None, *, _parser=None): # The _parser argument is for internal use only and must not be relied # upon in user code. It will be removed in a future release. # See https://bugs.python.org/issue17741 for more details. self._events_queue = collections.deque() self._parser = _parser or XMLParser(target=TreeBuilder()) # wire up the parser for event reporting if events is None: events = ("end",) self._parser._setevents(self._events_queue, events) def feed(self, data): """Feed encoded data to parser.""" if self._parser is None: raise ValueError("feed() called after end of stream") if data: try: self._parser.feed(data) except SyntaxError as exc: self._events_queue.append(exc) def _close_and_return_root(self): # iterparse needs this to set its root attribute properly :( root = self._parser.close() self._parser = None return root def close(self): """Finish feeding data to parser. Unlike XMLParser, does not return the root element. Use read_events() to consume elements from XMLPullParser. """ self._close_and_return_root() def read_events(self): """Return an iterator over currently available (event, elem) pairs. Events are consumed from the internal event queue as they are retrieved from the iterator. """ events = self._events_queue while events: event = events.popleft() if isinstance(event, Exception): raise event else: yield event def flush(self): if self._parser is None: raise ValueError("flush() called after end of stream") self._parser.flush() def XML(text, parser=None): """Parse XML document from string constant. This function can be used to embed "XML Literals" in Python code. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) return parser.close() def XMLID(text, parser=None): """Parse XML document from string constant for its IDs. *text* is a string containing XML data, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an (Element, dict) tuple, in which the dict maps element id:s to elements. """ if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) tree = parser.close() ids = {} for elem in tree.iter(): id = elem.get("id") if id: ids[id] = elem return tree, ids # Parse XML document from string constant. Alias for XML(). fromstring = XML def fromstringlist(sequence, parser=None): """Parse XML document from sequence of string fragments. *sequence* is a list of other sequence, *parser* is an optional parser instance, defaulting to the standard XMLParser. Returns an Element instance. """ if not parser: parser = XMLParser(target=TreeBuilder()) for text in sequence: parser.feed(text) return parser.close() # -------------------------------------------------------------------- class TreeBuilder: """Generic element structure builder. This builder converts a sequence of start, data, and end method calls to a well-formed element structure. You can use this class to build an element structure using a custom XML parser, or a parser for some other XML-like format. *element_factory* is an optional element factory which is called to create new Element instances, as necessary. *comment_factory* is a factory to create comments to be used instead of the standard factory. If *insert_comments* is false (the default), comments will not be inserted into the tree. *pi_factory* is a factory to create processing instructions to be used instead of the standard factory. If *insert_pis* is false (the default), processing instructions will not be inserted into the tree. """ def __init__(self, element_factory=None, *, comment_factory=None, pi_factory=None, insert_comments=False, insert_pis=False): self._data = [] # data collector self._elem = [] # element stack self._last = None # last element self._root = None # root element self._tail = None # true if we're after an end tag if comment_factory is None: comment_factory = Comment self._comment_factory = comment_factory self.insert_comments = insert_comments if pi_factory is None: pi_factory = ProcessingInstruction self._pi_factory = pi_factory self.insert_pis = insert_pis if element_factory is None: element_factory = Element self._factory = element_factory def close(self): """Flush builder buffers and return toplevel document Element.""" assert len(self._elem) == 0, "missing end tags" assert self._root is not None, "missing toplevel element" return self._root def _flush(self): if self._data: if self._last is not None: text = "".join(self._data) if self._tail: assert self._last.tail is None, "internal error (tail)" self._last.tail = text else: assert self._last.text is None, "internal error (text)" self._last.text = text self._data = [] def data(self, data): """Add text to current element.""" self._data.append(data) def start(self, tag, attrs): """Open new element and return it. *tag* is the element name, *attrs* is a dict containing element attributes. """ self._flush() self._last = elem = self._factory(tag, attrs) if self._elem: self._elem[-1].append(elem) elif self._root is None: self._root = elem self._elem.append(elem) self._tail = 0 return elem def end(self, tag): """Close and return current Element. *tag* is the element name. """ self._flush() self._last = self._elem.pop() assert self._last.tag == tag,\ "end tag mismatch (expected %s, got %s)" % ( self._last.tag, tag) self._tail = 1 return self._last def comment(self, text): """Create a comment using the comment_factory. *text* is the text of the comment. """ return self._handle_single( self._comment_factory, self.insert_comments, text) def pi(self, target, text=None): """Create a processing instruction using the pi_factory. *target* is the target name of the processing instruction. *text* is the data of the processing instruction, or ''. """ return self._handle_single( self._pi_factory, self.insert_pis, target, text) def _handle_single(self, factory, insert, *args): elem = factory(*args) if insert: self._flush() self._last = elem if self._elem: self._elem[-1].append(elem) self._tail = 1 return elem # also see ElementTree and TreeBuilder class XMLParser: """Element structure builder for XML source data based on the expat parser. *target* is an optional target object which defaults to an instance of the standard TreeBuilder class, *encoding* is an optional encoding string which if given, overrides the encoding specified in the XML file: http://www.iana.org/assignments/character-sets """ def __init__(self, *, target=None, encoding=None): try: from xml.parsers import expat except ImportError: 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() # 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 # main callbacks parser.DefaultHandlerExpand = self._default if hasattr(target, 'start'): parser.StartElementHandler = self._start if hasattr(target, 'end'): parser.EndElementHandler = self._end if hasattr(target, 'start_ns'): parser.StartNamespaceDeclHandler = self._start_ns if hasattr(target, 'end_ns'): parser.EndNamespaceDeclHandler = self._end_ns if hasattr(target, 'data'): parser.CharacterDataHandler = target.data # miscellaneous callbacks if hasattr(target, 'comment'): parser.CommentHandler = target.comment if hasattr(target, 'pi'): parser.ProcessingInstructionHandler = target.pi # Configure pyexpat: buffering, new-style attribute handling. parser.buffer_text = 1 parser.ordered_attributes = 1 self._doctype = None self.entity = {} try: self.version = "Expat %d.%d.%d" % expat.version_info except AttributeError: pass # unknown def _setevents(self, events_queue, events_to_report): # Internal API for XMLPullParser # events_to_report: a list of events to report during parsing (same as # the *events* of XMLPullParser's constructor. # events_queue: a list of actual parsing events that will be populated # by the underlying parser. # parser = self._parser append = events_queue.append for event_name in events_to_report: if event_name == "start": parser.ordered_attributes = 1 def handler(tag, attrib_in, event=event_name, append=append, start=self._start): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler elif event_name == "end": def handler(tag, event=event_name, append=append, end=self._end): append((event, end(tag))) parser.EndElementHandler = handler elif event_name == "start-ns": # TreeBuilder does not implement .start_ns() if hasattr(self.target, "start_ns"): def handler(prefix, uri, event=event_name, append=append, start_ns=self._start_ns): append((event, start_ns(prefix, uri))) else: def handler(prefix, uri, event=event_name, append=append): append((event, (prefix or '', uri or ''))) parser.StartNamespaceDeclHandler = handler elif event_name == "end-ns": # TreeBuilder does not implement .end_ns() if hasattr(self.target, "end_ns"): def handler(prefix, event=event_name, append=append, end_ns=self._end_ns): append((event, end_ns(prefix))) else: def handler(prefix, event=event_name, append=append): append((event, None)) parser.EndNamespaceDeclHandler = handler elif event_name == 'comment': def handler(text, event=event_name, append=append, self=self): append((event, self.target.comment(text))) parser.CommentHandler = handler elif event_name == 'pi': def handler(pi_target, data, event=event_name, append=append, self=self): append((event, self.target.pi(pi_target, data))) parser.ProcessingInstructionHandler = handler else: raise ValueError("unknown event %r" % event_name) 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 try: name = self._names[key] except KeyError: name = key if "}" in name: name = "{" + name self._names[key] = name return name def _start_ns(self, prefix, uri): return self.target.start_ns(prefix or '', uri or '') def _end_ns(self, prefix): return self.target.end_ns(prefix or '') def _start(self, tag, attr_list): # Handler for expat's StartElementHandler. Since ordered_attributes # is set, the attributes are reported as a list of alternating # attribute name,value. fixname = self._fixname tag = fixname(tag) attrib = {} if attr_list: for i in range(0, len(attr_list), 2): attrib[fixname(attr_list[i])] = attr_list[i+1] return self.target.start(tag, attrib) def _end(self, tag): return self.target.end(self._fixname(tag)) def _default(self, text): prefix = text[:1] if prefix == "&": # deal with undefined entities try: data_handler = self.target.data except AttributeError: return try: data_handler(self.entity[text[1:-1]]) except KeyError: from xml.parsers import expat 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 = text.strip() if not text: return self._doctype.append(text) n = len(self._doctype) if n > 2: type = self._doctype[1] if type == "PUBLIC" and n == 4: name, type, pubid, system = self._doctype if pubid: pubid = pubid[1:-1] elif type == "SYSTEM" and n == 3: name, type, system = self._doctype pubid = None else: return if hasattr(self.target, "doctype"): self.target.doctype(name, pubid, system[1:-1]) elif hasattr(self, "doctype"): warnings.warn( "The doctype() method of XMLParser is ignored. " "Define doctype() method on the TreeBuilder target.", RuntimeWarning) self._doctype = None def feed(self, data): """Feed encoded data to parser.""" try: self.parser.Parse(data, False) except self._error as v: self._raiseerror(v) def close(self): """Finish feeding data to parser and return element structure.""" try: self.parser.Parse(b"", True) # end of data except self._error as v: self._raiseerror(v) try: close_handler = self.target.close except AttributeError: pass else: return close_handler() finally: # get rid of circular references del self.parser, self._parser del self.target, self._target def flush(self): was_enabled = self.parser.GetReparseDeferralEnabled() try: self.parser.SetReparseDeferralEnabled(False) self.parser.Parse(b"", False) except self._error as v: self._raiseerror(v) finally: self.parser.SetReparseDeferralEnabled(was_enabled) # -------------------------------------------------------------------- # C14N 2.0 def canonicalize(xml_data=None, *, out=None, from_file=None, **options): """Convert XML to its C14N 2.0 serialised form. If *out* is provided, it must be a file or file-like object that receives the serialised canonical XML output (text, not bytes) through its ``.write()`` method. To write to a file, open it in text mode with encoding "utf-8". If *out* is not provided, this function returns the output as text string. Either *xml_data* (an XML string) or *from_file* (a file path or file-like object) must be provided as input. The configuration options are the same as for the ``C14NWriterTarget``. """ if xml_data is None and from_file is None: raise ValueError("Either 'xml_data' or 'from_file' must be provided as input") sio = None if out is None: sio = out = io.StringIO() parser = XMLParser(target=C14NWriterTarget(out.write, **options)) if xml_data is not None: parser.feed(xml_data) parser.close() elif from_file is not None: parse(from_file, parser=parser) return sio.getvalue() if sio is not None else None _looks_like_prefix_name = re.compile(r'^\w+:\w+$', re.UNICODE).match class C14NWriterTarget: """ Canonicalization writer target for the XMLParser. Serialises parse events to XML C14N 2.0. The *write* function is used for writing out the resulting data stream as text (not bytes). To write to a file, open it in text mode with encoding "utf-8" and pass its ``.write`` method. Configuration options: - *with_comments*: set to true to include comments - *strip_text*: set to true to strip whitespace before and after text content - *rewrite_prefixes*: set to true to replace namespace prefixes by "n{number}" - *qname_aware_tags*: a set of qname aware tag names in which prefixes should be replaced in text content - *qname_aware_attrs*: a set of qname aware attribute names in which prefixes should be replaced in text content - *exclude_attrs*: a set of attribute names that should not be serialised - *exclude_tags*: a set of tag names that should not be serialised """ def __init__(self, write, *, with_comments=False, strip_text=False, rewrite_prefixes=False, qname_aware_tags=None, qname_aware_attrs=None, exclude_attrs=None, exclude_tags=None): self._write = write self._data = [] self._with_comments = with_comments self._strip_text = strip_text self._exclude_attrs = set(exclude_attrs) if exclude_attrs else None self._exclude_tags = set(exclude_tags) if exclude_tags else None self._rewrite_prefixes = rewrite_prefixes if qname_aware_tags: self._qname_aware_tags = set(qname_aware_tags) else: self._qname_aware_tags = None if qname_aware_attrs: self._find_qname_aware_attrs = set(qname_aware_attrs).intersection else: self._find_qname_aware_attrs = None # Stack with globally and newly declared namespaces as (uri, prefix) pairs. self._declared_ns_stack = [[ ("http://www.w3.org/XML/1998/namespace", "xml"), ]] # Stack with user declared namespace prefixes as (uri, prefix) pairs. self._ns_stack = [] if not rewrite_prefixes: self._ns_stack.append(list(_namespace_map.items())) self._ns_stack.append([]) self._prefix_map = {} self._preserve_space = [False] self._pending_start = None self._root_seen = False self._root_done = False self._ignored_depth = 0 def _iter_namespaces(self, ns_stack, _reversed=reversed): for namespaces in _reversed(ns_stack): if namespaces: # almost no element declares new namespaces yield from namespaces def _resolve_prefix_name(self, prefixed_name): prefix, name = prefixed_name.split(':', 1) for uri, p in self._iter_namespaces(self._ns_stack): if p == prefix: return f'{{{uri}}}{name}' raise ValueError(f'Prefix {prefix} of QName "{prefixed_name}" is not declared in scope') def _qname(self, qname, uri=None): if uri is None: uri, tag = qname[1:].rsplit('}', 1) if qname[:1] == '{' else ('', qname) else: tag = qname prefixes_seen = set() for u, prefix in self._iter_namespaces(self._declared_ns_stack): if u == uri and prefix not in prefixes_seen: return f'{prefix}:{tag}' if prefix else tag, tag, uri prefixes_seen.add(prefix) # Not declared yet => add new declaration. if self._rewrite_prefixes: if uri in self._prefix_map: prefix = self._prefix_map[uri] else: prefix = self._prefix_map[uri] = f'n{len(self._prefix_map)}' self._declared_ns_stack[-1].append((uri, prefix)) return f'{prefix}:{tag}', tag, uri if not uri and '' not in prefixes_seen: # No default namespace declared => no prefix needed. return tag, tag, uri for u, prefix in self._iter_namespaces(self._ns_stack): if u == uri: self._declared_ns_stack[-1].append((uri, prefix)) return f'{prefix}:{tag}' if prefix else tag, tag, uri if not uri: # As soon as a default namespace is defined, # anything that has no namespace (and thus, no prefix) goes there. return tag, tag, uri raise ValueError(f'Namespace "{uri}" is not declared in scope') def data(self, data): if not self._ignored_depth: self._data.append(data) def _flush(self, _join_text=''.join): data = _join_text(self._data) del self._data[:] if self._strip_text and not self._preserve_space[-1]: data = data.strip() if self._pending_start is not None: args, self._pending_start = self._pending_start, None qname_text = data if data and _looks_like_prefix_name(data) else None self._start(*args, qname_text) if qname_text is not None: return if data and self._root_seen: self._write(_escape_cdata_c14n(data)) def start_ns(self, prefix, uri): if self._ignored_depth: return # we may have to resolve qnames in text content if self._data: self._flush() self._ns_stack[-1].append((uri, prefix)) def start(self, tag, attrs): if self._exclude_tags is not None and ( self._ignored_depth or tag in self._exclude_tags): self._ignored_depth += 1 return if self._data: self._flush() new_namespaces = [] self._declared_ns_stack.append(new_namespaces) if self._qname_aware_tags is not None and tag in self._qname_aware_tags: # Need to parse text first to see if it requires a prefix declaration. self._pending_start = (tag, attrs, new_namespaces) return self._start(tag, attrs, new_namespaces) def _start(self, tag, attrs, new_namespaces, qname_text=None): if self._exclude_attrs is not None and attrs: attrs = {k: v for k, v in attrs.items() if k not in self._exclude_attrs} qnames = {tag, *attrs} resolved_names = {} # Resolve prefixes in attribute and tag text. if qname_text is not None: qname = resolved_names[qname_text] = self._resolve_prefix_name(qname_text) qnames.add(qname) if self._find_qname_aware_attrs is not None and attrs: qattrs = self._find_qname_aware_attrs(attrs) if qattrs: for attr_name in qattrs: value = attrs[attr_name] if _looks_like_prefix_name(value): qname = resolved_names[value] = self._resolve_prefix_name(value) qnames.add(qname) else: qattrs = None else: qattrs = None # Assign prefixes in lexicographical order of used URIs. parse_qname = self._qname parsed_qnames = {n: parse_qname(n) for n in sorted( qnames, key=lambda n: n.split('}', 1))} # Write namespace declarations in prefix order ... if new_namespaces: attr_list = [ ('xmlns:' + prefix if prefix else 'xmlns', uri) for uri, prefix in new_namespaces ] attr_list.sort() else: # almost always empty attr_list = [] # ... followed by attributes in URI+name order if attrs: for k, v in sorted(attrs.items()): if qattrs is not None and k in qattrs and v in resolved_names: v = parsed_qnames[resolved_names[v]][0] attr_qname, attr_name, uri = parsed_qnames[k] # No prefix for attributes in default ('') namespace. attr_list.append((attr_qname if uri else attr_name, v)) # Honour xml:space attributes. space_behaviour = attrs.get('{http://www.w3.org/XML/1998/namespace}space') self._preserve_space.append( space_behaviour == 'preserve' if space_behaviour else self._preserve_space[-1]) # Write the tag. write = self._write write('<' + parsed_qnames[tag][0]) if attr_list: write(''.join([f' {k}="{_escape_attrib_c14n(v)}"' for k, v in attr_list])) write('>') # Write the resolved qname text content. if qname_text is not None: write(_escape_cdata_c14n(parsed_qnames[resolved_names[qname_text]][0])) self._root_seen = True self._ns_stack.append([]) def end(self, tag): if self._ignored_depth: self._ignored_depth -= 1 return if self._data: self._flush() self._write(f'') self._preserve_space.pop() self._root_done = len(self._preserve_space) == 1 self._declared_ns_stack.pop() self._ns_stack.pop() def comment(self, text): if not self._with_comments: return if self._ignored_depth: return if self._root_done: self._write('\n') elif self._root_seen and self._data: self._flush() self._write(f'') if not self._root_seen: self._write('\n') def pi(self, target, data): if self._ignored_depth: return if self._root_done: self._write('\n') elif self._root_seen and self._data: self._flush() self._write( f'' if data else f'') if not self._root_seen: self._write('\n') def _escape_cdata_c14n(text): # 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('>', '>') if '\r' in text: text = text.replace('\r', ' ') return text except (TypeError, AttributeError): _raise_serialization_error(text) def _escape_attrib_c14n(text): # escape attribute value try: if '&' in text: text = text.replace('&', '&') if '<' in text: text = text.replace('<', '<') if '"' in text: text = text.replace('"', '"') if '\t' in text: text = text.replace('\t', ' ') if '\n' in text: text = text.replace('\n', ' ') if '\r' in text: text = text.replace('\r', ' ') return text except (TypeError, AttributeError): _raise_serialization_error(text) # -------------------------------------------------------------------- # Import the C accelerators try: # Element is going to be shadowed by the C implementation. We need to keep # the Python version of it accessible for some "creative" by external code # (see tests) _Element_Py = Element # Element, SubElement, ParseError, TreeBuilder, XMLParser, _set_factories from _elementtree import * from _elementtree import _set_factories except ImportError: pass else: _set_factories(Comment, ProcessingInstruction) lib64/python2.7/xml/etree/ElementTree.py000064400000157310152362675610013747 0ustar00# # ElementTree # $Id: ElementTree.py 3440 2008-07-18 14:45:01Z fredrik $ # # light-weight XML support for Python 2.3 and later. # # history (since 1.2.6): # 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 # 2007-09-13 fl added itertext, iterfind; assorted cleanups # 2007-12-15 fl added C14N hooks, copy method (experimental) # # Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com # # -------------------------------------------------------------------- # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/psf/license for licensing details. __all__ = [ # public symbols "Comment", "dump", "Element", "ElementTree", "fromstring", "fromstringlist", "iselement", "iterparse", "parse", "ParseError", "PI", "ProcessingInstruction", "QName", "SubElement", "tostring", "tostringlist", "TreeBuilder", "VERSION", "XML", "XMLParser", "XMLTreeBuilder", ] VERSION = "1.3.0" ## # The Element type is a flexible container object, designed to # store hierarchical data structures in memory. The type can be # described as a cross between a list and a dictionary. #

# Each element has a number of properties associated with it: #

# # 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 sys import re import warnings class _SimpleElementPath(object): # 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 # -------------------------------------------------------------------- ## # 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. #

# 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 Element(object): # text...tail ## # (Attribute) Element tag. tag = None ## # (Attribute) Element attribute dictionary. Where possible, use # {@link #Element.get}, # {@link #Element.set}, # {@link #Element.keys}, and # {@link #Element.items} to access # element attributes. attrib = None ## # (Attribute) Text before first subelement. This is either a # string or the value None. Note that if there was no text, this # attribute may be either None or an empty string, depending on # the parser. text = None ## # (Attribute) Text after this element's end tag, but before the # next sibling element's start tag. This is either a string or # the value None. Note that if there was no text, this attribute # may be either None or an empty string, depending on the parser. tail = None # text after end tag, if any # constructor def __init__(self, tag, attrib={}, **extra): attrib = attrib.copy() attrib.update(extra) self.tag = tag self.attrib = attrib self._children = [] def __repr__(self): return "" % (repr(self.tag), id(self)) ## # Creates a new element object of the same type as this element. # # @param tag Element tag. # @param attrib Element attributes, given as a dictionary. # @return A new element instance. def makeelement(self, tag, attrib): return self.__class__(tag, attrib) ## # (Experimental) Copies the current element. This creates a # shallow copy; subelements will be shared with the original tree. # # @return A new element instance. def copy(self): elem = self.makeelement(self.tag, self.attrib) elem.text = self.text elem.tail = self.tail elem[:] = self return elem ## # Returns the number of subelements. Note that this only counts # full elements; to check if there's any content in an element, you # have to check both the length and the text attribute. # # @return The number of subelements. def __len__(self): return len(self._children) def __nonzero__(self): warnings.warn( "The behavior of this method will change in future versions. " "Use specific 'len(elem)' or 'elem is not None' test instead.", FutureWarning, stacklevel=2 ) return len(self._children) != 0 # emulate old behaviour, for now ## # Returns the given subelement, by index. # # @param index What subelement to return. # @return The given subelement. # @exception IndexError If the given element does not exist. def __getitem__(self, index): return self._children[index] ## # Replaces the given subelement, by index. # # @param index What subelement to replace. # @param element The new element value. # @exception IndexError If the given element does not exist. def __setitem__(self, index, element): # if isinstance(index, slice): # for elt in element: # assert iselement(elt) # else: # assert iselement(element) self._children[index] = element ## # Deletes the given subelement, by index. # # @param index What subelement to delete. # @exception IndexError If the given element does not exist. def __delitem__(self, index): del self._children[index] ## # Adds a subelement to the end of this element. In document order, # the new element will appear after the last existing subelement (or # directly after the text, if it's the first subelement), but before # the end tag for this element. # # @param element The element to add. def append(self, element): # assert iselement(element) self._children.append(element) ## # Appends subelements from a sequence. # # @param elements A sequence object with zero or more elements. # @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. def insert(self, index, element): # assert iselement(element) self._children.insert(index, element) ## # Removes a matching subelement. Unlike the find methods, # this method compares elements based on identity, not on tag # value or contents. To remove subelements by other means, the # easiest way is often to use a list comprehension to select what # elements to keep, and use slice assignment to update the parent # element. # # @param element What element to remove. # @exception ValueError If a matching element could not be found. def remove(self, element): # assert iselement(element) self._children.remove(element) ## # (Deprecated) Returns all subelements. The elements are returned # in document order. # # @return A list of subelements. # @defreturn list of Element instances def getchildren(self): warnings.warn( "This method will be removed in future versions. " "Use 'list(elem)' or iteration over elem instead.", DeprecationWarning, stacklevel=2 ) return self._children ## # Finds the first matching subelement, by tag name or 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, namespaces=None): return ElementPath.find(self, path, namespaces) ## # Finds text for the first matching subelement, by tag name or path. # # @param path What 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 # is found, but has no text content, this method returns an # empty string. # @defreturn string def findtext(self, path, default=None, namespaces=None): return ElementPath.findtext(self, path, default, namespaces) ## # Finds all matching subelements, by tag name or path. # # @param path What element to look for. # @keyparam namespaces Optional namespace prefix map. # @return A list or other sequence containing all matching elements, # in document order. # @defreturn list of Element instances def findall(self, path, namespaces=None): return ElementPath.findall(self, path, namespaces) ## # Finds all matching subelements, by tag name or 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): return ElementPath.iterfind(self, path, namespaces) ## # Resets an element. This function removes all subelements, clears # all attributes, and sets the text and tail attributes # to None. def clear(self): self.attrib.clear() self._children = [] self.text = self.tail = None ## # Gets an element attribute. Equivalent to attrib.get, but # some implementations may handle this a bit more efficiently. # # @param key What attribute to look for. # @param default What to return if the attribute was not found. # @return The attribute value, or the default value, if the # attribute was not found. # @defreturn string or None def get(self, key, default=None): return self.attrib.get(key, default) ## # Sets an element attribute. Equivalent to attrib[key] = value, # but some implementations may handle this a bit more efficiently. # # @param key What attribute to set. # @param value The attribute value. def set(self, key, value): self.attrib[key] = value ## # Gets a list of attribute names. The names are returned in an # arbitrary order (just like for an ordinary Python dictionary). # Equivalent to attrib.keys(). # # @return A list of element attribute names. # @defreturn list of strings def keys(self): return self.attrib.keys() ## # Gets element attributes, as a sequence. The attributes are # returned in an arbitrary order. Equivalent to attrib.items(). # # @return A list of (name, value) tuples for all attributes. # @defreturn list of (string, string) tuples def items(self): return self.attrib.items() ## # Creates a tree iterator. The iterator loops over this element # and all subelements, in document order, and returns all elements # with a matching tag. #

# 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 An iterator containing all the matching elements. # @defreturn iterator def iter(self, tag=None): if tag == "*": tag = None if tag is None or self.tag == tag: yield self for e in self._children: for e in e.iter(tag): yield e # 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)) ## # 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): tag = self.tag if not isinstance(tag, basestring) 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 # appends it to an existing element. #

# The element name, attribute names, and attribute values can be # either 8-bit ASCII strings or Unicode strings. # # @param parent The parent element. # @param tag The subelement name. # @param attrib An optional dictionary, containing element attributes. # @param **extra Additional attributes, given as keyword arguments. # @return An element instance. # @defreturn Element def SubElement(parent, tag, attrib={}, **extra): attrib = attrib.copy() attrib.update(extra) element = parent.makeelement(tag, attrib) parent.append(element) return element ## # Comment element factory. This factory function creates a special # 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. # # @param text A string containing the comment string. # @return An element instance, representing a comment. # @defreturn Element def Comment(text=None): element = Element(Comment) element.text = text return element ## # PI element factory. This factory function creates a special element # 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. # @return An element instance, representing a PI. # @defreturn Element def ProcessingInstruction(target, text=None): element = Element(ProcessingInstruction) element.text = target if text: element.text = element.text + " " + text return element PI = ProcessingInstruction ## # QName wrapper. This can be used to wrap a QName attribute value, in # order to get proper namespace handling on output. # # @param text A string containing the QName value, in the form {uri}local, # or, if the tag argument is given, the URI part of a QName. # @param tag Optional tag. If given, the first argument is interpreted as # a URI, and this argument is interpreted as a local name. # @return An opaque object, representing the QName. class QName(object): def __init__(self, text_or_uri, tag=None): if tag: text_or_uri = "{%s}%s" % (text_or_uri, tag) self.text = text_or_uri def __str__(self): return self.text def __hash__(self): return hash(self.text) def __cmp__(self, other): if isinstance(other, QName): 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 file name. If given, the # tree is initialized with the contents of this XML file. class ElementTree(object): def __init__(self, element=None, file=None): # assert element is None or iselement(element) self._root = element # first node if file: self.parse(file) ## # Gets the root element for this tree. # # @return An element instance. # @defreturn Element def getroot(self): return self._root ## # Replaces the root element for this tree. This discards the # current contents of the tree, and replaces it with the given # element. Use with care. # # @param element An element instance. def _setroot(self, element): # assert iselement(element) self._root = element ## # Loads an external XML document into this element tree. # # @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): close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if not parser: parser = XMLParser(target=TreeBuilder()) while 1: data = source.read(65536) if not data: break parser.feed(data) self._root = parser.close() return self._root finally: if close_source: source.close() ## # Creates a tree iterator for the root element. The iterator loops # over all elements in this tree, in document order. # # @param tag What tags to look for (default is to return all elements) # @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): # 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)) ## # Same as getroot().find(path), starting at the root of the # tree. # # @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, 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.find(path, namespaces) ## # Same as getroot().findtext(path), starting at the root of the tree. # # @param path What 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 # is found, but has no text content, this method returns an # empty string. # @defreturn string def findtext(self, path, default=None, 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.findtext(path, default, namespaces) ## # Same as getroot().findall(path), starting at the root of the tree. # # @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, 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.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 **options Options, given as keyword arguments. # @keyparam encoding Optional output encoding (default is US-ASCII). # @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. # @keyparam default_namespace Sets the default XML namespace (for "xmlns"). # @keyparam method Optional output method ("xml", "html", "text" or # "c14n"; default is "xml"). 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: file = open(file_or_filename, "wb") write = file.write if not encoding: if method == "c14n": encoding = "utf-8" else: encoding = "us-ascii" elif xml_declaration or (xml_declaration is None and encoding not in ("utf-8", "us-ascii")): if method == "xml": write("\n" % encoding) if method == "text": _serialize_text(write, self._root, encoding) else: qnames, namespaces = _namespaces( self._root, encoding, default_namespace ) serialize = _serialize[method] serialize(write, self._root, encoding, 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") # -------------------------------------------------------------------- # 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:].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] = 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): if 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("" % _encode(text, encoding)) elif tag is ProcessingInstruction: write("" % _encode(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: write("<" + tag) items = 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.encode(encoding), _escape_attrib(v, encoding) )) 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, encoding) write(" %s=\"%s\"" % (qnames[k], v)) 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: 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: 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.encode(encoding), _escape_attrib(v, encoding) )) 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, encoding) # FIXME: handle boolean attributes write(" %s=\"%s\"" % (qnames[k], v)) write(">") ltag = tag.lower() if text: if ltag == "script" or ltag == "style": write(_encode(text, encoding)) else: write(_escape_cdata(text, encoding)) for e in elem: _serialize_html(write, e, encoding, qnames, None) if ltag 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)) _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): 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) # -------------------------------------------------------------------- ## # Generates a string representation of an XML element, including all # subelements. # # @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 An 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) 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 XMLParser} parser is used. # @return An ElementTree instance def parse(source, parser=None): tree = ElementTree() tree.parse(source, parser) return tree ## # Parses an XML document into an element tree incrementally, and reports # what's going on to the user. # # @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. def iterparse(source, events=None, parser=None): close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if not parser: parser = XMLParser(target=TreeBuilder()) return _IterParseIterator(source, events, parser, close_source) except: if close_source: source.close() raise class _IterParseIterator(object): def __init__(self, source, events, parser, close_source=False): self._file = source self._close_file = close_source self._events = [] self._index = 0 self._error = None self.root = self._root = None self._parser = parser # wire up the parser for event reporting parser = self._parser._parser append = self._events.append if events is None: events = ["end"] for event in events: if event == "start": try: parser.ordered_attributes = 1 parser.specified_attributes = 1 def handler(tag, attrib_in, event=event, append=append, start=self._parser._start_list): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler except AttributeError: def handler(tag, attrib_in, event=event, append=append, start=self._parser._start): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler elif event == "end": def handler(tag, event=event, append=append, end=self._parser._end): append((event, end(tag))) parser.EndElementHandler = handler elif event == "start-ns": def handler(prefix, uri, event=event, append=append): try: uri = (uri or "").encode("ascii") except UnicodeError: pass 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): try: while 1: try: item = self._events[self._index] self._index += 1 return item except IndexError: pass if self._error: e = self._error self._error = None raise e if self._parser is None: self.root = self._root break # load event buffer del self._events[:] self._index = 0 data = self._file.read(16384) if data: try: self._parser.feed(data) except SyntaxError as exc: self._error = exc else: self._root = self._parser.close() self._parser = None except: if self._close_file: self._file.close() raise if self._close_file: self._file.close() raise StopIteration 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=None): if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) return parser.close() ## # Parses an XML document from a string constant, and also returns # 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=None): if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) tree = parser.close() ids = {} for elem in tree.iter(): id = elem.get("id") if id: ids[id] = elem return tree, ids ## # Parses an XML document from a string constant. Same as {@link #XML}. # # @def fromstring(text) # @param source A string containing XML data. # @return An Element instance. # @defreturn Element fromstring = XML ## # Parses an XML document from a sequence of string fragments. # # @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 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 # of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link # #TreeBuilder.end} method calls to a well-formed element structure. #

# You can use this class to build an element structure using a custom XML # parser, or a parser for some other XML-like format. # # @param element_factory Optional element factory. This factory # is called to create new Element instances, as necessary. class TreeBuilder(object): def __init__(self, element_factory=None): self._data = [] # data collector self._elem = [] # element stack self._last = None # last element self._tail = None # true if we're after an end tag if element_factory is None: element_factory = Element self._factory = element_factory ## # Flushes the builder buffers, and returns the toplevel document # element. # # @return An Element instance. # @defreturn Element def close(self): assert len(self._elem) == 0, "missing end tags" assert self._last is not None, "missing toplevel element" return self._last def _flush(self): if self._data: if self._last is not None: text = "".join(self._data) if self._tail: assert self._last.tail is None, "internal error (tail)" self._last.tail = text else: assert self._last.text is None, "internal error (text)" self._last.text = text self._data = [] ## # Adds text to the current element. # # @param data A string. This should be either an 8-bit string # containing ASCII text, or a Unicode string. def data(self, data): self._data.append(data) ## # Opens a new element. # # @param tag The element name. # @param attrib A dictionary containing element attributes. # @return The opened element. # @defreturn Element def start(self, tag, attrs): self._flush() self._last = elem = self._factory(tag, attrs) if self._elem: self._elem[-1].append(elem) self._elem.append(elem) self._tail = 0 return elem ## # Closes the current element. # # @param tag The element name. # @return The closed element. # @defreturn Element def end(self, tag): self._flush() self._last = self._elem.pop() assert self._last.tag == tag,\ "end tag mismatch (expected %s, got %s)" % ( self._last.tag, tag) self._tail = 1 return self._last _sentinel = ['sentinel'] ## # Element structure builder for XML source data, based on the # expat parser. # # @keyparam target Target object. If omitted, the builder uses an # 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 XMLParser(object): def __init__(self, html=_sentinel, target=None, encoding=None): if html is not _sentinel: warnings.warnpy3k( "The html argument of XMLParser() is deprecated", DeprecationWarning, stacklevel=2) try: from xml.parsers import expat except ImportError: 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() # 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 except AttributeError: pass # use new-style attribute handling, if supported try: self._parser.ordered_attributes = 1 self._parser.specified_attributes = 1 parser.StartElementHandler = self._start_list except AttributeError: pass 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 text.encode("ascii") except UnicodeError: return text def _fixname(self, key): # expand qname, and convert name string to ascii, if possible try: name = self._names[key] except KeyError: name = key if "}" in name: name = "{" + name self._names[key] = name = self._fixtext(name) return name 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)] = 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])] = fixtext(attrib_in[i+1]) return self.target.start(tag, attrib) def _data(self, text): return self.target.data(self._fixtext(text)) def _end(self, tag): return self.target.end(self._fixname(tag)) def _comment(self, data): try: comment = self.target.comment except AttributeError: pass else: return comment(self._fixtext(data)) def _pi(self, target, data): try: pi = self.target.pi except AttributeError: pass else: return pi(self._fixtext(target), self._fixtext(data)) def _default(self, text): prefix = text[:1] if prefix == "&": # deal with undefined entities try: self.target.data(self.entity[text[1:-1]]) except KeyError: from xml.parsers import expat 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 = text.strip() if not text: return self._doctype.append(text) n = len(self._doctype) if n > 2: type = self._doctype[1] if type == "PUBLIC" and n == 4: name, type, pubid, system = self._doctype elif type == "SYSTEM" and n == 3: name, type, system = self._doctype pubid = None else: return if pubid: pubid = pubid[1:-1] if hasattr(self.target, "doctype"): self.target.doctype(name, pubid, system[1:-1]) elif self.doctype != self._XMLParser__doctype: # warn about deprecated call self._XMLParser__doctype(name, pubid, system[1:-1]) self.doctype(name, pubid, system[1:-1]) self._doctype = None ## # (Deprecated) Handles a doctype declaration. # # @param name Doctype name. # @param pubid Public identifier. # @param system System identifier. def doctype(self, name, pubid, system): """This method of XMLParser is deprecated.""" warnings.warn( "This method of XMLParser is deprecated. Define doctype() " "method on the TreeBuilder target.", DeprecationWarning, ) # sentinel, if doctype is redefined in a subclass __doctype = doctype ## # Feeds data to the parser. # # @param data Encoded data. def feed(self, data): try: self._parser.Parse(data, 0) except self._error, v: self._raiseerror(v) ## # Finishes feeding data to the parser. # # @return An element structure. # @defreturn Element def close(self): 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 XMLTreeBuilder = XMLParser # workaround circular import. try: from ElementC14N import _serialize_c14n _serialize["c14n"] = _serialize_c14n except ImportError: pass usr/lib64/python2.7/xml/etree/ElementTree.py000064400000157310152370630000014537 0ustar00# # ElementTree # $Id: ElementTree.py 3440 2008-07-18 14:45:01Z fredrik $ # # light-weight XML support for Python 2.3 and later. # # history (since 1.2.6): # 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 # 2007-09-13 fl added itertext, iterfind; assorted cleanups # 2007-12-15 fl added C14N hooks, copy method (experimental) # # Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. # # fredrik@pythonware.com # http://www.pythonware.com # # -------------------------------------------------------------------- # The ElementTree toolkit is # # Copyright (c) 1999-2008 by Fredrik Lundh # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of # Secret Labs AB or the author not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # OF THIS SOFTWARE. # -------------------------------------------------------------------- # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/psf/license for licensing details. __all__ = [ # public symbols "Comment", "dump", "Element", "ElementTree", "fromstring", "fromstringlist", "iselement", "iterparse", "parse", "ParseError", "PI", "ProcessingInstruction", "QName", "SubElement", "tostring", "tostringlist", "TreeBuilder", "VERSION", "XML", "XMLParser", "XMLTreeBuilder", ] VERSION = "1.3.0" ## # The Element type is a flexible container object, designed to # store hierarchical data structures in memory. The type can be # described as a cross between a list and a dictionary. #

# Each element has a number of properties associated with it: #

# # 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 sys import re import warnings class _SimpleElementPath(object): # 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 # -------------------------------------------------------------------- ## # 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. #

# 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 Element(object): # text...tail ## # (Attribute) Element tag. tag = None ## # (Attribute) Element attribute dictionary. Where possible, use # {@link #Element.get}, # {@link #Element.set}, # {@link #Element.keys}, and # {@link #Element.items} to access # element attributes. attrib = None ## # (Attribute) Text before first subelement. This is either a # string or the value None. Note that if there was no text, this # attribute may be either None or an empty string, depending on # the parser. text = None ## # (Attribute) Text after this element's end tag, but before the # next sibling element's start tag. This is either a string or # the value None. Note that if there was no text, this attribute # may be either None or an empty string, depending on the parser. tail = None # text after end tag, if any # constructor def __init__(self, tag, attrib={}, **extra): attrib = attrib.copy() attrib.update(extra) self.tag = tag self.attrib = attrib self._children = [] def __repr__(self): return "" % (repr(self.tag), id(self)) ## # Creates a new element object of the same type as this element. # # @param tag Element tag. # @param attrib Element attributes, given as a dictionary. # @return A new element instance. def makeelement(self, tag, attrib): return self.__class__(tag, attrib) ## # (Experimental) Copies the current element. This creates a # shallow copy; subelements will be shared with the original tree. # # @return A new element instance. def copy(self): elem = self.makeelement(self.tag, self.attrib) elem.text = self.text elem.tail = self.tail elem[:] = self return elem ## # Returns the number of subelements. Note that this only counts # full elements; to check if there's any content in an element, you # have to check both the length and the text attribute. # # @return The number of subelements. def __len__(self): return len(self._children) def __nonzero__(self): warnings.warn( "The behavior of this method will change in future versions. " "Use specific 'len(elem)' or 'elem is not None' test instead.", FutureWarning, stacklevel=2 ) return len(self._children) != 0 # emulate old behaviour, for now ## # Returns the given subelement, by index. # # @param index What subelement to return. # @return The given subelement. # @exception IndexError If the given element does not exist. def __getitem__(self, index): return self._children[index] ## # Replaces the given subelement, by index. # # @param index What subelement to replace. # @param element The new element value. # @exception IndexError If the given element does not exist. def __setitem__(self, index, element): # if isinstance(index, slice): # for elt in element: # assert iselement(elt) # else: # assert iselement(element) self._children[index] = element ## # Deletes the given subelement, by index. # # @param index What subelement to delete. # @exception IndexError If the given element does not exist. def __delitem__(self, index): del self._children[index] ## # Adds a subelement to the end of this element. In document order, # the new element will appear after the last existing subelement (or # directly after the text, if it's the first subelement), but before # the end tag for this element. # # @param element The element to add. def append(self, element): # assert iselement(element) self._children.append(element) ## # Appends subelements from a sequence. # # @param elements A sequence object with zero or more elements. # @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. def insert(self, index, element): # assert iselement(element) self._children.insert(index, element) ## # Removes a matching subelement. Unlike the find methods, # this method compares elements based on identity, not on tag # value or contents. To remove subelements by other means, the # easiest way is often to use a list comprehension to select what # elements to keep, and use slice assignment to update the parent # element. # # @param element What element to remove. # @exception ValueError If a matching element could not be found. def remove(self, element): # assert iselement(element) self._children.remove(element) ## # (Deprecated) Returns all subelements. The elements are returned # in document order. # # @return A list of subelements. # @defreturn list of Element instances def getchildren(self): warnings.warn( "This method will be removed in future versions. " "Use 'list(elem)' or iteration over elem instead.", DeprecationWarning, stacklevel=2 ) return self._children ## # Finds the first matching subelement, by tag name or 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, namespaces=None): return ElementPath.find(self, path, namespaces) ## # Finds text for the first matching subelement, by tag name or path. # # @param path What 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 # is found, but has no text content, this method returns an # empty string. # @defreturn string def findtext(self, path, default=None, namespaces=None): return ElementPath.findtext(self, path, default, namespaces) ## # Finds all matching subelements, by tag name or path. # # @param path What element to look for. # @keyparam namespaces Optional namespace prefix map. # @return A list or other sequence containing all matching elements, # in document order. # @defreturn list of Element instances def findall(self, path, namespaces=None): return ElementPath.findall(self, path, namespaces) ## # Finds all matching subelements, by tag name or 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): return ElementPath.iterfind(self, path, namespaces) ## # Resets an element. This function removes all subelements, clears # all attributes, and sets the text and tail attributes # to None. def clear(self): self.attrib.clear() self._children = [] self.text = self.tail = None ## # Gets an element attribute. Equivalent to attrib.get, but # some implementations may handle this a bit more efficiently. # # @param key What attribute to look for. # @param default What to return if the attribute was not found. # @return The attribute value, or the default value, if the # attribute was not found. # @defreturn string or None def get(self, key, default=None): return self.attrib.get(key, default) ## # Sets an element attribute. Equivalent to attrib[key] = value, # but some implementations may handle this a bit more efficiently. # # @param key What attribute to set. # @param value The attribute value. def set(self, key, value): self.attrib[key] = value ## # Gets a list of attribute names. The names are returned in an # arbitrary order (just like for an ordinary Python dictionary). # Equivalent to attrib.keys(). # # @return A list of element attribute names. # @defreturn list of strings def keys(self): return self.attrib.keys() ## # Gets element attributes, as a sequence. The attributes are # returned in an arbitrary order. Equivalent to attrib.items(). # # @return A list of (name, value) tuples for all attributes. # @defreturn list of (string, string) tuples def items(self): return self.attrib.items() ## # Creates a tree iterator. The iterator loops over this element # and all subelements, in document order, and returns all elements # with a matching tag. #

# 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 An iterator containing all the matching elements. # @defreturn iterator def iter(self, tag=None): if tag == "*": tag = None if tag is None or self.tag == tag: yield self for e in self._children: for e in e.iter(tag): yield e # 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)) ## # 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): tag = self.tag if not isinstance(tag, basestring) 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 # appends it to an existing element. #

# The element name, attribute names, and attribute values can be # either 8-bit ASCII strings or Unicode strings. # # @param parent The parent element. # @param tag The subelement name. # @param attrib An optional dictionary, containing element attributes. # @param **extra Additional attributes, given as keyword arguments. # @return An element instance. # @defreturn Element def SubElement(parent, tag, attrib={}, **extra): attrib = attrib.copy() attrib.update(extra) element = parent.makeelement(tag, attrib) parent.append(element) return element ## # Comment element factory. This factory function creates a special # 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. # # @param text A string containing the comment string. # @return An element instance, representing a comment. # @defreturn Element def Comment(text=None): element = Element(Comment) element.text = text return element ## # PI element factory. This factory function creates a special element # 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. # @return An element instance, representing a PI. # @defreturn Element def ProcessingInstruction(target, text=None): element = Element(ProcessingInstruction) element.text = target if text: element.text = element.text + " " + text return element PI = ProcessingInstruction ## # QName wrapper. This can be used to wrap a QName attribute value, in # order to get proper namespace handling on output. # # @param text A string containing the QName value, in the form {uri}local, # or, if the tag argument is given, the URI part of a QName. # @param tag Optional tag. If given, the first argument is interpreted as # a URI, and this argument is interpreted as a local name. # @return An opaque object, representing the QName. class QName(object): def __init__(self, text_or_uri, tag=None): if tag: text_or_uri = "{%s}%s" % (text_or_uri, tag) self.text = text_or_uri def __str__(self): return self.text def __hash__(self): return hash(self.text) def __cmp__(self, other): if isinstance(other, QName): 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 file name. If given, the # tree is initialized with the contents of this XML file. class ElementTree(object): def __init__(self, element=None, file=None): # assert element is None or iselement(element) self._root = element # first node if file: self.parse(file) ## # Gets the root element for this tree. # # @return An element instance. # @defreturn Element def getroot(self): return self._root ## # Replaces the root element for this tree. This discards the # current contents of the tree, and replaces it with the given # element. Use with care. # # @param element An element instance. def _setroot(self, element): # assert iselement(element) self._root = element ## # Loads an external XML document into this element tree. # # @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): close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if not parser: parser = XMLParser(target=TreeBuilder()) while 1: data = source.read(65536) if not data: break parser.feed(data) self._root = parser.close() return self._root finally: if close_source: source.close() ## # Creates a tree iterator for the root element. The iterator loops # over all elements in this tree, in document order. # # @param tag What tags to look for (default is to return all elements) # @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): # 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)) ## # Same as getroot().find(path), starting at the root of the # tree. # # @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, 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.find(path, namespaces) ## # Same as getroot().findtext(path), starting at the root of the tree. # # @param path What 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 # is found, but has no text content, this method returns an # empty string. # @defreturn string def findtext(self, path, default=None, 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.findtext(path, default, namespaces) ## # Same as getroot().findall(path), starting at the root of the tree. # # @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, 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.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 **options Options, given as keyword arguments. # @keyparam encoding Optional output encoding (default is US-ASCII). # @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. # @keyparam default_namespace Sets the default XML namespace (for "xmlns"). # @keyparam method Optional output method ("xml", "html", "text" or # "c14n"; default is "xml"). 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: file = open(file_or_filename, "wb") write = file.write if not encoding: if method == "c14n": encoding = "utf-8" else: encoding = "us-ascii" elif xml_declaration or (xml_declaration is None and encoding not in ("utf-8", "us-ascii")): if method == "xml": write("\n" % encoding) if method == "text": _serialize_text(write, self._root, encoding) else: qnames, namespaces = _namespaces( self._root, encoding, default_namespace ) serialize = _serialize[method] serialize(write, self._root, encoding, 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") # -------------------------------------------------------------------- # 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:].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] = 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): if 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("" % _encode(text, encoding)) elif tag is ProcessingInstruction: write("" % _encode(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: write("<" + tag) items = 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.encode(encoding), _escape_attrib(v, encoding) )) 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, encoding) write(" %s=\"%s\"" % (qnames[k], v)) 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: 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: 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.encode(encoding), _escape_attrib(v, encoding) )) 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, encoding) # FIXME: handle boolean attributes write(" %s=\"%s\"" % (qnames[k], v)) write(">") ltag = tag.lower() if text: if ltag == "script" or ltag == "style": write(_encode(text, encoding)) else: write(_escape_cdata(text, encoding)) for e in elem: _serialize_html(write, e, encoding, qnames, None) if ltag 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)) _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): 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) # -------------------------------------------------------------------- ## # Generates a string representation of an XML element, including all # subelements. # # @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 An 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) 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 XMLParser} parser is used. # @return An ElementTree instance def parse(source, parser=None): tree = ElementTree() tree.parse(source, parser) return tree ## # Parses an XML document into an element tree incrementally, and reports # what's going on to the user. # # @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. def iterparse(source, events=None, parser=None): close_source = False if not hasattr(source, "read"): source = open(source, "rb") close_source = True try: if not parser: parser = XMLParser(target=TreeBuilder()) return _IterParseIterator(source, events, parser, close_source) except: if close_source: source.close() raise class _IterParseIterator(object): def __init__(self, source, events, parser, close_source=False): self._file = source self._close_file = close_source self._events = [] self._index = 0 self._error = None self.root = self._root = None self._parser = parser # wire up the parser for event reporting parser = self._parser._parser append = self._events.append if events is None: events = ["end"] for event in events: if event == "start": try: parser.ordered_attributes = 1 parser.specified_attributes = 1 def handler(tag, attrib_in, event=event, append=append, start=self._parser._start_list): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler except AttributeError: def handler(tag, attrib_in, event=event, append=append, start=self._parser._start): append((event, start(tag, attrib_in))) parser.StartElementHandler = handler elif event == "end": def handler(tag, event=event, append=append, end=self._parser._end): append((event, end(tag))) parser.EndElementHandler = handler elif event == "start-ns": def handler(prefix, uri, event=event, append=append): try: uri = (uri or "").encode("ascii") except UnicodeError: pass 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): try: while 1: try: item = self._events[self._index] self._index += 1 return item except IndexError: pass if self._error: e = self._error self._error = None raise e if self._parser is None: self.root = self._root break # load event buffer del self._events[:] self._index = 0 data = self._file.read(16384) if data: try: self._parser.feed(data) except SyntaxError as exc: self._error = exc else: self._root = self._parser.close() self._parser = None except: if self._close_file: self._file.close() raise if self._close_file: self._file.close() raise StopIteration 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=None): if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) return parser.close() ## # Parses an XML document from a string constant, and also returns # 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=None): if not parser: parser = XMLParser(target=TreeBuilder()) parser.feed(text) tree = parser.close() ids = {} for elem in tree.iter(): id = elem.get("id") if id: ids[id] = elem return tree, ids ## # Parses an XML document from a string constant. Same as {@link #XML}. # # @def fromstring(text) # @param source A string containing XML data. # @return An Element instance. # @defreturn Element fromstring = XML ## # Parses an XML document from a sequence of string fragments. # # @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 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 # of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link # #TreeBuilder.end} method calls to a well-formed element structure. #

# You can use this class to build an element structure using a custom XML # parser, or a parser for some other XML-like format. # # @param element_factory Optional element factory. This factory # is called to create new Element instances, as necessary. class TreeBuilder(object): def __init__(self, element_factory=None): self._data = [] # data collector self._elem = [] # element stack self._last = None # last element self._tail = None # true if we're after an end tag if element_factory is None: element_factory = Element self._factory = element_factory ## # Flushes the builder buffers, and returns the toplevel document # element. # # @return An Element instance. # @defreturn Element def close(self): assert len(self._elem) == 0, "missing end tags" assert self._last is not None, "missing toplevel element" return self._last def _flush(self): if self._data: if self._last is not None: text = "".join(self._data) if self._tail: assert self._last.tail is None, "internal error (tail)" self._last.tail = text else: assert self._last.text is None, "internal error (text)" self._last.text = text self._data = [] ## # Adds text to the current element. # # @param data A string. This should be either an 8-bit string # containing ASCII text, or a Unicode string. def data(self, data): self._data.append(data) ## # Opens a new element. # # @param tag The element name. # @param attrib A dictionary containing element attributes. # @return The opened element. # @defreturn Element def start(self, tag, attrs): self._flush() self._last = elem = self._factory(tag, attrs) if self._elem: self._elem[-1].append(elem) self._elem.append(elem) self._tail = 0 return elem ## # Closes the current element. # # @param tag The element name. # @return The closed element. # @defreturn Element def end(self, tag): self._flush() self._last = self._elem.pop() assert self._last.tag == tag,\ "end tag mismatch (expected %s, got %s)" % ( self._last.tag, tag) self._tail = 1 return self._last _sentinel = ['sentinel'] ## # Element structure builder for XML source data, based on the # expat parser. # # @keyparam target Target object. If omitted, the builder uses an # 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 XMLParser(object): def __init__(self, html=_sentinel, target=None, encoding=None): if html is not _sentinel: warnings.warnpy3k( "The html argument of XMLParser() is deprecated", DeprecationWarning, stacklevel=2) try: from xml.parsers import expat except ImportError: 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() # 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 except AttributeError: pass # use new-style attribute handling, if supported try: self._parser.ordered_attributes = 1 self._parser.specified_attributes = 1 parser.StartElementHandler = self._start_list except AttributeError: pass 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 text.encode("ascii") except UnicodeError: return text def _fixname(self, key): # expand qname, and convert name string to ascii, if possible try: name = self._names[key] except KeyError: name = key if "}" in name: name = "{" + name self._names[key] = name = self._fixtext(name) return name 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)] = 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])] = fixtext(attrib_in[i+1]) return self.target.start(tag, attrib) def _data(self, text): return self.target.data(self._fixtext(text)) def _end(self, tag): return self.target.end(self._fixname(tag)) def _comment(self, data): try: comment = self.target.comment except AttributeError: pass else: return comment(self._fixtext(data)) def _pi(self, target, data): try: pi = self.target.pi except AttributeError: pass else: return pi(self._fixtext(target), self._fixtext(data)) def _default(self, text): prefix = text[:1] if prefix == "&": # deal with undefined entities try: self.target.data(self.entity[text[1:-1]]) except KeyError: from xml.parsers import expat 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 = text.strip() if not text: return self._doctype.append(text) n = len(self._doctype) if n > 2: type = self._doctype[1] if type == "PUBLIC" and n == 4: name, type, pubid, system = self._doctype elif type == "SYSTEM" and n == 3: name, type, system = self._doctype pubid = None else: return if pubid: pubid = pubid[1:-1] if hasattr(self.target, "doctype"): self.target.doctype(name, pubid, system[1:-1]) elif self.doctype != self._XMLParser__doctype: # warn about deprecated call self._XMLParser__doctype(name, pubid, system[1:-1]) self.doctype(name, pubid, system[1:-1]) self._doctype = None ## # (Deprecated) Handles a doctype declaration. # # @param name Doctype name. # @param pubid Public identifier. # @param system System identifier. def doctype(self, name, pubid, system): """This method of XMLParser is deprecated.""" warnings.warn( "This method of XMLParser is deprecated. Define doctype() " "method on the TreeBuilder target.", DeprecationWarning, ) # sentinel, if doctype is redefined in a subclass __doctype = doctype ## # Feeds data to the parser. # # @param data Encoded data. def feed(self, data): try: self._parser.Parse(data, 0) except self._error, v: self._raiseerror(v) ## # Finishes feeding data to the parser. # # @return An element structure. # @defreturn Element def close(self): 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 XMLTreeBuilder = XMLParser # workaround circular import. try: from ElementC14N import _serialize_c14n _serialize["c14n"] = _serialize_c14n except ImportError: pass