]> SALOME platform Git repositories - tools/sat.git/blob - src/ElementTree.py
Salome HOME
Change log directory. It is now set in site.pyconf
[tools/sat.git] / src / ElementTree.py
1 #
2 # ElementTree
3 # $Id: ElementTree.py 2326 2005-03-17 07:45:21Z fredrik $
4 #
5 # light-weight XML support for Python 1.5.2 and later.
6 #
7 # history:
8 # 2001-10-20 fl   created (from various sources)
9 # 2001-11-01 fl   return root from parse method
10 # 2002-02-16 fl   sort attributes in lexical order
11 # 2002-04-06 fl   TreeBuilder refactoring, added PythonDoc markup
12 # 2002-05-01 fl   finished TreeBuilder refactoring
13 # 2002-07-14 fl   added basic namespace support to ElementTree.write
14 # 2002-07-25 fl   added QName attribute support
15 # 2002-10-20 fl   fixed encoding in write
16 # 2002-11-24 fl   changed default encoding to ascii; fixed attribute encoding
17 # 2002-11-27 fl   accept file objects or file names for parse/write
18 # 2002-12-04 fl   moved XMLTreeBuilder back to this module
19 # 2003-01-11 fl   fixed entity encoding glitch for us-ascii
20 # 2003-02-13 fl   added XML literal factory
21 # 2003-02-21 fl   added ProcessingInstruction/PI factory
22 # 2003-05-11 fl   added tostring/fromstring helpers
23 # 2003-05-26 fl   added ElementPath support
24 # 2003-07-05 fl   added makeelement factory method
25 # 2003-07-28 fl   added more well-known namespace prefixes
26 # 2003-08-15 fl   fixed typo in ElementTree.findtext (Thomas Dartsch)
27 # 2003-09-04 fl   fall back on emulator if ElementPath is not installed
28 # 2003-10-31 fl   markup updates
29 # 2003-11-15 fl   fixed nested namespace bug
30 # 2004-03-28 fl   added XMLID helper
31 # 2004-06-02 fl   added default support to findtext
32 # 2004-06-08 fl   fixed encoding of non-ascii element/attribute names
33 # 2004-08-23 fl   take advantage of post-2.1 expat features
34 # 2005-02-01 fl   added iterparse implementation
35 # 2005-03-02 fl   fixed iterparse support for pre-2.2 versions
36 #
37 # Copyright (c) 1999-2005 by Fredrik Lundh.  All rights reserved.
38 #
39 # fredrik@pythonware.com
40 # http://www.pythonware.com
41 #
42 # --------------------------------------------------------------------
43 # The ElementTree toolkit is
44 #
45 # Copyright (c) 1999-2005 by Fredrik Lundh
46 #
47 # By obtaining, using, and/or copying this software and/or its
48 # associated documentation, you agree that you have read, understood,
49 # and will comply with the following terms and conditions:
50 #
51 # Permission to use, copy, modify, and distribute this software and
52 # its associated documentation for any purpose and without fee is
53 # hereby granted, provided that the above copyright notice appears in
54 # all copies, and that both that copyright notice and this permission
55 # notice appear in supporting documentation, and that the name of
56 # Secret Labs AB or the author not be used in advertising or publicity
57 # pertaining to distribution of the software without specific, written
58 # prior permission.
59 #
60 # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
61 # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
62 # ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
63 # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
64 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
65 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
66 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
67 # OF THIS SOFTWARE.
68 # --------------------------------------------------------------------
69
70 __all__ = [
71     # public symbols
72     "Comment",
73     "dump",
74     "Element", "ElementTree",
75     "fromstring",
76     "iselement", "iterparse",
77     "parse",
78     "PI", "ProcessingInstruction",
79     "QName",
80     "SubElement",
81     "tostring",
82     "TreeBuilder",
83     "VERSION", "XML",
84     "XMLTreeBuilder",
85     ]
86
87 ##
88 # The <b>Element</b> type is a flexible container object, designed to
89 # store hierarchical data structures in memory. The type can be
90 # described as a cross between a list and a dictionary.
91 # <p>
92 # Each element has a number of properties associated with it:
93 # <ul>
94 # <li>a <i>tag</i>. This is a string identifying what kind of data
95 # this element represents (the element type, in other words).</li>
96 # <li>a number of <i>attributes</i>, stored in a Python dictionary.</li>
97 # <li>a <i>text</i> string.</li>
98 # <li>an optional <i>tail</i> string.</li>
99 # <li>a number of <i>child elements</i>, stored in a Python sequence</li>
100 # </ul>
101 #
102 # To create an element instance, use the {@link #Element} or {@link
103 # #SubElement} factory functions.
104 # <p>
105 # The {@link #ElementTree} class can be used to wrap an element
106 # structure, and convert it from and to XML.
107 ##
108
109 import string, sys, re, platform
110
111 class _SimpleElementPath:
112     # emulate pre-1.2 find/findtext/findall behaviour
113     def find(self, element, tag):
114         for elem in element:
115             if elem.tag == tag:
116                 return elem
117         return None
118     def findtext(self, element, tag, default=None):
119         for elem in element:
120             if elem.tag == tag:
121                 return elem.text or ""
122         return default
123     def findall(self, element, tag):
124         if tag[:3] == ".//":
125             return element.getiterator(tag[3:])
126         result = []
127         for elem in element:
128             if elem.tag == tag:
129                 result.append(elem)
130         return result
131
132 try:
133     import ElementPath
134 except ImportError:
135     # FIXME: issue warning in this case?
136     ElementPath = _SimpleElementPath()
137
138 # TODO: add support for custom namespace resolvers/default namespaces
139 # TODO: add improved support for incremental parsing
140
141 VERSION = "1.2.6"
142
143 ##
144 # Internal element class.  This class defines the Element interface,
145 # and provides a reference implementation of this interface.
146 # <p>
147 # You should not create instances of this class directly.  Use the
148 # appropriate factory functions instead, such as {@link #Element}
149 # and {@link #SubElement}.
150 #
151 # @see Element
152 # @see SubElement
153 # @see Comment
154 # @see ProcessingInstruction
155
156 class _ElementInterface:
157     # <tag attrib>text<child/>...</tag>tail
158
159     ##
160     # (Attribute) Element tag.
161
162     tag = None
163
164     ##
165     # (Attribute) Element attribute dictionary.  Where possible, use
166     # {@link #_ElementInterface.get},
167     # {@link #_ElementInterface.set},
168     # {@link #_ElementInterface.keys}, and
169     # {@link #_ElementInterface.items} to access
170     # element attributes.
171
172     attrib = None
173
174     ##
175     # (Attribute) Text before first subelement.  This is either a
176     # string or the value None, if there was no text.
177
178     text = None
179
180     ##
181     # (Attribute) Text after this element's end tag, but before the
182     # next sibling element's start tag.  This is either a string or
183     # the value None, if there was no text.
184
185     tail = None # text after end tag, if any
186
187     def __init__(self, tag, attrib):
188         self.tag = tag
189         self.attrib = attrib
190         self._children = []
191
192     def __repr__(self):
193         return "<Element %s at %x>" % (self.tag, id(self))
194
195     ##
196     # Creates a new element object of the same type as this element.
197     #
198     # @param tag Element tag.
199     # @param attrib Element attributes, given as a dictionary.
200     # @return A new element instance.
201
202     def makeelement(self, tag, attrib):
203         return Element(tag, attrib)
204
205     ##
206     # Returns the number of subelements.
207     #
208     # @return The number of subelements.
209
210     def __len__(self):
211         return len(self._children)
212
213     ##
214     # Returns the given subelement.
215     #
216     # @param index What subelement to return.
217     # @return The given subelement.
218     # @exception IndexError If the given element does not exist.
219
220     def __getitem__(self, index):
221         return self._children[index]
222
223     ##
224     # Replaces the given subelement.
225     #
226     # @param index What subelement to replace.
227     # @param element The new element value.
228     # @exception IndexError If the given element does not exist.
229     # @exception AssertionError If element is not a valid object.
230
231     def __setitem__(self, index, element):
232         assert iselement(element)
233         self._children[index] = element
234
235     ##
236     # Deletes the given subelement.
237     #
238     # @param index What subelement to delete.
239     # @exception IndexError If the given element does not exist.
240
241     def __delitem__(self, index):
242         del self._children[index]
243
244     ##
245     # Returns a list containing subelements in the given range.
246     #
247     # @param start The first subelement to return.
248     # @param stop The first subelement that shouldn't be returned.
249     # @return A sequence object containing subelements.
250
251     def __getslice__(self, start, stop):
252         return self._children[start:stop]
253
254     ##
255     # Replaces a number of subelements with elements from a sequence.
256     #
257     # @param start The first subelement to replace.
258     # @param stop The first subelement that shouldn't be replaced.
259     # @param elements A sequence object with zero or more elements.
260     # @exception AssertionError If a sequence member is not a valid object.
261
262     def __setslice__(self, start, stop, elements):
263         for element in elements:
264             assert iselement(element)
265         self._children[start:stop] = list(elements)
266
267     ##
268     # Deletes a number of subelements.
269     #
270     # @param start The first subelement to delete.
271     # @param stop The first subelement to leave in there.
272
273     def __delslice__(self, start, stop):
274         del self._children[start:stop]
275
276     ##
277     # Adds a subelement to the end of this element.
278     #
279     # @param element The element to add.
280     # @exception AssertionError If a sequence member is not a valid object.
281
282     def append(self, element):
283         assert iselement(element)
284         self._children.append(element)
285
286     ##
287     # Inserts a subelement at the given position in this element.
288     #
289     # @param index Where to insert the new subelement.
290     # @exception AssertionError If the element is not a valid object.
291
292     def insert(self, index, element):
293         assert iselement(element)
294         self._children.insert(index, element)
295
296     ##
297     # Removes a matching subelement.  Unlike the <b>find</b> methods,
298     # this method compares elements based on identity, not on tag
299     # value or contents.
300     #
301     # @param element What element to remove.
302     # @exception ValueError If a matching element could not be found.
303     # @exception AssertionError If the element is not a valid object.
304
305     def remove(self, element):
306         assert iselement(element)
307         self._children.remove(element)
308
309     ##
310     # Returns all subelements.  The elements are returned in document
311     # order.
312     #
313     # @return A list of subelements.
314     # @defreturn list of Element instances
315
316     def getchildren(self):
317         return self._children
318
319     ##
320     # Finds the first matching subelement, by tag name or path.
321     #
322     # @param path What element to look for.
323     # @return The first matching element, or None if no element was found.
324     # @defreturn Element or None
325
326     def find(self, path):
327         return ElementPath.find(self, path)
328
329     ##
330     # Finds text for the first matching subelement, by tag name or path.
331     #
332     # @param path What element to look for.
333     # @param default What to return if the element was not found.
334     # @return The text content of the first matching element, or the
335     #     default value no element was found.  Note that if the element
336     #     has is found, but has no text content, this method returns an
337     #     empty string.
338     # @defreturn string
339
340     def findtext(self, path, default=None):
341         return ElementPath.findtext(self, path, default)
342
343     ##
344     # Finds all matching subelements, by tag name or path.
345     #
346     # @param path What element to look for.
347     # @return A list or iterator containing all matching elements,
348     #    in document order.
349     # @defreturn list of Element instances
350
351     def findall(self, path):
352         return ElementPath.findall(self, path)
353
354     ##
355     # Resets an element.  This function removes all subelements, clears
356     # all attributes, and sets the text and tail attributes to None.
357
358     def clear(self):
359         self.attrib.clear()
360         self._children = []
361         self.text = self.tail = None
362
363     ##
364     # Gets an element attribute.
365     #
366     # @param key What attribute to look for.
367     # @param default What to return if the attribute was not found.
368     # @return The attribute value, or the default value, if the
369     #     attribute was not found.
370     # @defreturn string or None
371
372     def get(self, key, default=None):
373         return self.attrib.get(key, default)
374
375     ##
376     # Sets an element attribute.
377     #
378     # @param key What attribute to set.
379     # @param value The attribute value.
380
381     def set(self, key, value):
382         self.attrib[key] = value
383
384     ##
385     # Gets a list of attribute names.  The names are returned in an
386     # arbitrary order (just like for an ordinary Python dictionary).
387     #
388     # @return A list of element attribute names.
389     # @defreturn list of strings
390
391     def keys(self):
392         return self.attrib.keys()
393
394     ##
395     # Gets element attributes, as a sequence.  The attributes are
396     # returned in an arbitrary order.
397     #
398     # @return A list of (name, value) tuples for all attributes.
399     # @defreturn list of (string, string) tuples
400
401     def items(self):
402         return self.attrib.items()
403
404     ##
405     # Creates a tree iterator.  The iterator loops over this element
406     # and all subelements, in document order, and returns all elements
407     # with a matching tag.
408     # <p>
409     # If the tree structure is modified during iteration, the result
410     # is undefined.
411     #
412     # @param tag What tags to look for (default is to return all elements).
413     # @return A list or iterator containing all the matching elements.
414     # @defreturn list or iterator
415
416     def getiterator(self, tag=None):
417         nodes = []
418         if tag == "*":
419             tag = None
420         if tag is None or self.tag == tag:
421             nodes.append(self)
422         for node in self._children:
423             nodes.extend(node.getiterator(tag))
424         return nodes
425
426 # compatibility
427 _Element = _ElementInterface
428
429 ##
430 # Element factory.  This function returns an object implementing the
431 # standard Element interface.  The exact class or type of that object
432 # is implementation dependent, but it will always be compatible with
433 # the {@link #_ElementInterface} class in this module.
434 # <p>
435 # The element name, attribute names, and attribute values can be
436 # either 8-bit ASCII strings or Unicode strings.
437 #
438 # @param tag The element name.
439 # @param attrib An optional dictionary, containing element attributes.
440 # @param **extra Additional attributes, given as keyword arguments.
441 # @return An element instance.
442 # @defreturn Element
443
444 def Element(tag, attrib={}, **extra):
445     attrib = attrib.copy()
446     attrib.update(extra)
447     return _ElementInterface(tag, attrib)
448
449 ##
450 # Subelement factory.  This function creates an element instance, and
451 # appends it to an existing element.
452 # <p>
453 # The element name, attribute names, and attribute values can be
454 # either 8-bit ASCII strings or Unicode strings.
455 #
456 # @param parent The parent element.
457 # @param tag The subelement name.
458 # @param attrib An optional dictionary, containing element attributes.
459 # @param **extra Additional attributes, given as keyword arguments.
460 # @return An element instance.
461 # @defreturn Element
462
463 def SubElement(parent, tag, attrib={}, **extra):
464     attrib = attrib.copy()
465     attrib.update(extra)
466     element = parent.makeelement(tag, attrib)
467     parent.append(element)
468     return element
469
470 ##
471 # Comment element factory.  This factory function creates a special
472 # element that will be serialized as an XML comment.
473 # <p>
474 # The comment string can be either an 8-bit ASCII string or a Unicode
475 # string.
476 #
477 # @param text A string containing the comment string.
478 # @return An element instance, representing a comment.
479 # @defreturn Element
480
481 def Comment(text=None):
482     element = Element(Comment)
483     element.text = text
484     return element
485
486 ##
487 # PI element factory.  This factory function creates a special element
488 # that will be serialized as an XML processing instruction.
489 #
490 # @param target A string containing the PI target.
491 # @param text A string containing the PI contents, if any.
492 # @return An element instance, representing a PI.
493 # @defreturn Element
494
495 def ProcessingInstruction(target, text=None):
496     element = Element(ProcessingInstruction)
497     element.text = target
498     if text:
499         element.text = element.text + " " + text
500     return element
501
502 PI = ProcessingInstruction
503
504 ##
505 # QName wrapper.  This can be used to wrap a QName attribute value, in
506 # order to get proper namespace handling on output.
507 #
508 # @param text A string containing the QName value, in the form {uri}local,
509 #     or, if the tag argument is given, the URI part of a QName.
510 # @param tag Optional tag.  If given, the first argument is interpreted as
511 #     an URI, and this argument is interpreted as a local name.
512 # @return An opaque object, representing the QName.
513
514 class QName:
515     def __init__(self, text_or_uri, tag=None):
516         if tag:
517             text_or_uri = "{%s}%s" % (text_or_uri, tag)
518         self.text = text_or_uri
519     def __str__(self):
520         return self.text
521     def __hash__(self):
522         return hash(self.text)
523     def __cmp__(self, other):
524         if isinstance(other, QName):
525             return cmp(self.text, other.text)
526         return cmp(self.text, other)
527
528 ##
529 # ElementTree wrapper class.  This class represents an entire element
530 # hierarchy, and adds some extra support for serialization to and from
531 # standard XML.
532 #
533 # @param element Optional root element.
534 # @keyparam file Optional file handle or name.  If given, the
535 #     tree is initialized with the contents of this XML file.
536
537 class ElementTree:
538
539     def __init__(self, element=None, file=None):
540         assert element is None or iselement(element)
541         self._root = element # first node
542         if file:
543             self.parse(file)
544
545     ##
546     # Gets the root element for this tree.
547     #
548     # @return An element instance.
549     # @defreturn Element
550
551     def getroot(self):
552         return self._root
553
554     ##
555     # Replaces the root element for this tree.  This discards the
556     # current contents of the tree, and replaces it with the given
557     # element.  Use with care.
558     #
559     # @param element An element instance.
560
561     def _setroot(self, element):
562         assert iselement(element)
563         self._root = element
564
565     ##
566     # Loads an external XML document into this element tree.
567     #
568     # @param source A file name or file object.
569     # @param parser An optional parser instance.  If not given, the
570     #     standard {@link XMLTreeBuilder} parser is used.
571     # @return The document root element.
572     # @defreturn Element
573
574     def parse(self, source, parser=None):
575         if not hasattr(source, "read"):
576             source = open(source, "rb")
577         if not parser:
578             parser = XMLTreeBuilder()
579         while 1:
580             data = source.read(32768)
581             if not data:
582                 break
583             parser.feed(data)
584         self._root = parser.close()
585         return self._root
586
587     ##
588     # Creates a tree iterator for the root element.  The iterator loops
589     # over all elements in this tree, in document order.
590     #
591     # @param tag What tags to look for (default is to return all elements)
592     # @return An iterator.
593     # @defreturn iterator
594
595     def getiterator(self, tag=None):
596         assert self._root is not None
597         return self._root.getiterator(tag)
598
599     ##
600     # Finds the first toplevel element with given tag.
601     # Same as getroot().find(path).
602     #
603     # @param path What element to look for.
604     # @return The first matching element, or None if no element was found.
605     # @defreturn Element or None
606
607     def find(self, path):
608         assert self._root is not None
609         if path[:1] == "/":
610             path = "." + path
611         return self._root.find(path)
612
613     ##
614     # Finds the element text for the first toplevel element with given
615     # tag.  Same as getroot().findtext(path).
616     #
617     # @param path What toplevel element to look for.
618     # @param default What to return if the element was not found.
619     # @return The text content of the first matching element, or the
620     #     default value no element was found.  Note that if the element
621     #     has is found, but has no text content, this method returns an
622     #     empty string.
623     # @defreturn string
624
625     def findtext(self, path, default=None):
626         assert self._root is not None
627         if path[:1] == "/":
628             path = "." + path
629         return self._root.findtext(path, default)
630
631     ##
632     # Finds all toplevel elements with the given tag.
633     # Same as getroot().findall(path).
634     #
635     # @param path What element to look for.
636     # @return A list or iterator containing all matching elements,
637     #    in document order.
638     # @defreturn list of Element instances
639
640     def findall(self, path):
641         assert self._root is not None
642         if path[:1] == "/":
643             path = "." + path
644         return self._root.findall(path)
645
646     ##
647     # Writes the element tree to a file, as XML.
648     #
649     # @param file A file name, or a file object opened for writing.
650     # @param encoding Optional output encoding (default is US-ASCII).
651
652     def write(self, file, encoding="us-ascii"):
653         assert self._root is not None
654         if not hasattr(file, "write"):
655             file = open(file, "wb")
656         if not encoding:
657             encoding = "us-ascii"
658         elif encoding != "utf-8" and encoding != "us-ascii":
659             file.write("<?xml version='1.0' encoding='%s'?>\n" % encoding)
660         self._write(file, self._root, encoding, {})
661
662     def _write(self, file, node, encoding, namespaces, margin=0):
663         # write XML to file
664         tag = node.tag
665         if tag is Comment:
666             file.write("<!-- %s -->\n" % _escape_cdata(node.text, encoding))
667         elif tag is ProcessingInstruction:
668             file.write("<?%s?>\n" % _escape_cdata(node.text, encoding))
669         else:
670             items = node.items()
671             xmlns_items = [] # new namespaces in this scope
672             try:
673                 if isinstance(tag, QName) or tag[:1] == "{":
674                     tag, xmlns = fixtag(tag, namespaces)
675                     if xmlns: xmlns_items.append(xmlns)
676             except TypeError:
677                 _raise_serialization_error(tag)
678             file.write(' ' * margin)
679             file.write(_encode("<", encoding) + _encode(tag, encoding))
680             if items or xmlns_items:
681                 items = sorted(items) # lexical order
682                 for k, v in items:
683                     try:
684                         if isinstance(k, QName) or k[:1] == "{":
685                             k, xmlns = fixtag(k, namespaces)
686                             if xmlns: xmlns_items.append(xmlns)
687                     except TypeError:
688                         _raise_serialization_error(k)
689                     try:
690                         if isinstance(v, QName):
691                             v, xmlns = fixtag(v, namespaces)
692                             if xmlns: xmlns_items.append(xmlns)
693                     except TypeError:
694                         _raise_serialization_error(v)
695                     file.write(" %s=\"%s\"" % (k,v))
696                 for k, v in xmlns_items:
697                     file.write(" %s=\"%s\"" % (k,v))
698             if node.text or len(node):
699                 file.write(">")
700                 if node.text:
701                     file.write(_escape_cdata(node.text, encoding))
702                 if len(node) > 0: file.write("\n")
703                 for n in node:
704                     self._write(file, n, encoding, namespaces, margin + 2)
705                 if len(node) > 0: file.write(' ' * margin)
706                 file.write(_encode("</", encoding) + _encode(tag, encoding) + _encode(">\n", encoding))
707             else:
708                 file.write("/>\n")
709             for k, v in xmlns_items:
710                 del namespaces[v]
711         if node.tail:
712             file.write(_escape_cdata(node.tail, encoding))
713
714 # --------------------------------------------------------------------
715 # helpers
716
717 ##
718 # Checks if an object appears to be a valid element object.
719 #
720 # @param An element instance.
721 # @return A true value if this is an element object.
722 # @defreturn flag
723
724 def iselement(element):
725     # FIXME: not sure about this; might be a better idea to look
726     # for tag/attrib/text attributes
727     return isinstance(element, _ElementInterface) or hasattr(element, "tag")
728
729 ##
730 # Writes an element tree or element structure to sys.stdout.  This
731 # function should be used for debugging only.
732 # <p>
733 # The exact output format is implementation dependent.  In this
734 # version, it's written as an ordinary XML file.
735 #
736 # @param elem An element tree or an individual element.
737
738 def dump(elem):
739     # debugging
740     if not isinstance(elem, ElementTree):
741         elem = ElementTree(elem)
742     elem.write(sys.stdout)
743     tail = elem.getroot().tail
744     if not tail or tail[-1] != "\n":
745         sys.stdout.write("\n")
746
747 def _encode(s, encoding):
748     try:
749         return s.encode(encoding)
750     except AttributeError:
751         return s # 1.5.2: assume the string uses the right encoding
752
753 if sys.version[:3] == "1.5":
754     _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2
755 else:
756     _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"'))
757
758 _escape_map = {
759     "&": "&amp;",
760     "<": "&lt;",
761     ">": "&gt;",
762     '"': "&quot;",
763 }
764
765 _namespace_map = {
766     # "well-known" namespace prefixes
767     "http://www.w3.org/XML/1998/namespace": "xml",
768     "http://www.w3.org/1999/xhtml": "html",
769     "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
770     "http://schemas.xmlsoap.org/wsdl/": "wsdl",
771 }
772
773 def _raise_serialization_error(text):
774     raise TypeError(
775         "cannot serialize %r (type %s)" % (text, type(text).__name__)
776         )
777
778 def _encode_entity(text, pattern=_escape):
779     # map reserved and non-ascii characters to numerical entities
780     def escape_entities(m, map=_escape_map):
781         out = []
782         append = out.append
783         for char in m.group():
784             text = map.get(char)
785             if text is None:
786                 text = "&#%d;" % ord(char)
787             append(text)
788         return string.join(out, "")
789     try:
790         return _encode(pattern.sub(escape_entities, text), "ascii")
791     except TypeError:
792         _raise_serialization_error(text)
793
794 #
795 # the following functions assume an ascii-compatible encoding
796 # (or "utf-16")
797
798 def _escape_cdata(text, encoding=None, replace=str.replace):
799     # escape character data
800     try:
801         if platform.python_version()[0] == '2': # python 2.x.y
802             if encoding:
803                 try:
804                     text = _encode(text, encoding)
805                 except UnicodeError:
806                     return _encode_entity(text)
807             
808         text = replace(text, "&", "&amp;")
809         text = replace(text, "<", "&lt;")
810         text = replace(text, ">", "&gt;")
811         text = replace(text, "####newLine####", "<br \>")
812         if encoding:
813             try:
814                 text = _encode(text, encoding)
815             except UnicodeError:
816                 return _encode_entity(text)
817         return text
818     except (TypeError, AttributeError):
819         _raise_serialization_error(text)
820
821 def _escape_attrib(text, encoding=None, replace=str.replace):
822     # escape attribute value
823     try:
824         text = replace(text, "&", "&amp;")
825         text = replace(text, "'", "&apos;") # FIXME: overkill
826         text = replace(text, "\"", "&quot;")
827         text = replace(text, "<", "&lt;")
828         text = replace(text, ">", "&gt;")
829         if encoding:
830             try:
831                 text = _encode(text, encoding)
832             except UnicodeError:
833                 return _encode_entity(text)
834         return text
835     except (TypeError, AttributeError):
836         _raise_serialization_error(text)
837
838 def fixtag(tag, namespaces):
839     # given a decorated tag (of the form {uri}tag), return prefixed
840     # tag and namespace declaration, if any
841     if isinstance(tag, QName):
842         tag = tag.text
843     namespace_uri, tag = string.split(tag[1:], "}", 1)
844     prefix = namespaces.get(namespace_uri)
845     if prefix is None:
846         prefix = _namespace_map.get(namespace_uri)
847         if prefix is None:
848             prefix = "ns%d" % len(namespaces)
849         namespaces[namespace_uri] = prefix
850         if prefix == "xml":
851             xmlns = None
852         else:
853             xmlns = ("xmlns:%s" % prefix, namespace_uri)
854     else:
855         xmlns = None
856     return "%s:%s" % (prefix, tag), xmlns
857
858 ##
859 # Parses an XML document into an element tree.
860 #
861 # @param source A filename or file object containing XML data.
862 # @param parser An optional parser instance.  If not given, the
863 #     standard {@link XMLTreeBuilder} parser is used.
864 # @return An ElementTree instance
865
866 def parse(source, parser=None):
867     tree = ElementTree()
868     tree.parse(source, parser)
869     return tree
870
871 ##
872 # Parses an XML document into an element tree incrementally, and reports
873 # what's going on to the user.
874 #
875 # @param source A filename or file object containing XML data.
876 # @param events A list of events to report back.  If omitted, only "end"
877 #     events are reported.
878 # @return A (event, elem) iterator.
879
880 class iterparse:
881
882     def __init__(self, source, events=None):
883         if not hasattr(source, "read"):
884             source = open(source, "rb")
885         self._file = source
886         self._events = []
887         self._index = 0
888         self.root = self._root = None
889         self._parser = XMLTreeBuilder()
890         # wire up the parser for event reporting
891         parser = self._parser._parser
892         append = self._events.append
893         if events is None:
894             events = ["end"]
895         for event in events:
896             if event == "start":
897                 try:
898                     parser.ordered_attributes = 1
899                     parser.specified_attributes = 1
900                     def handler(tag, attrib_in, event=event, append=append,
901                                 start=self._parser._start_list):
902                         append((event, start(tag, attrib_in)))
903                     parser.StartElementHandler = handler
904                 except AttributeError:
905                     def handler(tag, attrib_in, event=event, append=append,
906                                 start=self._parser._start):
907                         append((event, start(tag, attrib_in)))
908                     parser.StartElementHandler = handler
909             elif event == "end":
910                 def handler(tag, event=event, append=append,
911                             end=self._parser._end):
912                     append((event, end(tag)))
913                 parser.EndElementHandler = handler
914             elif event == "start-ns":
915                 def handler(prefix, uri, event=event, append=append):
916                     try:
917                         uri = _encode(uri, "ascii")
918                     except UnicodeError:
919                         pass
920                     append((event, (prefix or "", uri)))
921                 parser.StartNamespaceDeclHandler = handler
922             elif event == "end-ns":
923                 def handler(prefix, event=event, append=append):
924                     append((event, None))
925                 parser.EndNamespaceDeclHandler = handler
926
927     def next(self):
928         while 1:
929             try:
930                 item = self._events[self._index]
931             except IndexError:
932                 if self._parser is None:
933                     self.root = self._root
934                     try:
935                         raise StopIteration
936                     except NameError:
937                         raise IndexError
938                 # load event buffer
939                 del self._events[:]
940                 self._index = 0
941                 data = self._file.read(16384)
942                 if data:
943                     self._parser.feed(data)
944                 else:
945                     self._root = self._parser.close()
946                     self._parser = None
947             else:
948                 self._index = self._index + 1
949                 return item
950
951     try:
952         iter
953         def __iter__(self):
954             return self
955     except NameError:
956         def __getitem__(self, index):
957             return self.next()
958
959 ##
960 # Parses an XML document from a string constant.  This function can
961 # be used to embed "XML literals" in Python code.
962 #
963 # @param source A string containing XML data.
964 # @return An Element instance.
965 # @defreturn Element
966
967 def XML(text):
968     parser = XMLTreeBuilder()
969     parser.feed(text)
970     return parser.close()
971
972 ##
973 # Parses an XML document from a string constant, and also returns
974 # a dictionary which maps from element id:s to elements.
975 #
976 # @param source A string containing XML data.
977 # @return A tuple containing an Element instance and a dictionary.
978 # @defreturn (Element, dictionary)
979
980 def XMLID(text):
981     parser = XMLTreeBuilder()
982     parser.feed(text)
983     tree = parser.close()
984     ids = {}
985     for elem in tree.getiterator():
986         id = elem.get("id")
987         if id:
988             ids[id] = elem
989     return tree, ids
990
991 ##
992 # Parses an XML document from a string constant.  Same as {@link #XML}.
993 #
994 # @def fromstring(text)
995 # @param source A string containing XML data.
996 # @return An Element instance.
997 # @defreturn Element
998
999 fromstring = XML
1000
1001 ##
1002 # Generates a string representation of an XML element, including all
1003 # subelements.
1004 #
1005 # @param element An Element instance.
1006 # @return An encoded string containing the XML data.
1007 # @defreturn string
1008
1009 def tostring(element, encoding=None):
1010     class dummy:
1011         pass
1012     data = []
1013     file = dummy()
1014     file.write = data.append
1015     ElementTree(element).write(file, encoding)
1016     data2 = []
1017     for item in data:
1018         if isinstance(item, bytes):
1019             item = item.decode()
1020         data2.append(item)
1021     return "".join(data2)
1022
1023 ##
1024 # Generic element structure builder.  This builder converts a sequence
1025 # of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link
1026 # #TreeBuilder.end} method calls to a well-formed element structure.
1027 # <p>
1028 # You can use this class to build an element structure using a custom XML
1029 # parser, or a parser for some other XML-like format.
1030 #
1031 # @param element_factory Optional element factory.  This factory
1032 #    is called to create new Element instances, as necessary.
1033
1034 class TreeBuilder:
1035
1036     def __init__(self, element_factory=None):
1037         self._data = [] # data collector
1038         self._elem = [] # element stack
1039         self._last = None # last element
1040         self._tail = None # true if we're after an end tag
1041         if element_factory is None:
1042             element_factory = _ElementInterface
1043         self._factory = element_factory
1044
1045     ##
1046     # Flushes the parser buffers, and returns the toplevel documen
1047     # element.
1048     #
1049     # @return An Element instance.
1050     # @defreturn Element
1051
1052     def close(self):
1053         assert len(self._elem) == 0, "missing end tags"
1054         assert self._last != None, "missing toplevel element"
1055         return self._last
1056
1057     def _flush(self):
1058         if self._data:
1059             if self._last is not None:
1060                 text = string.join(self._data, "")
1061                 if self._tail:
1062                     assert self._last.tail is None, "internal error (tail)"
1063                     self._last.tail = text
1064                 else:
1065                     assert self._last.text is None, "internal error (text)"
1066                     self._last.text = text
1067             self._data = []
1068
1069     ##
1070     # Adds text to the current element.
1071     #
1072     # @param data A string.  This should be either an 8-bit string
1073     #    containing ASCII text, or a Unicode string.
1074
1075     def data(self, data):
1076         self._data.append(data)
1077
1078     ##
1079     # Opens a new element.
1080     #
1081     # @param tag The element name.
1082     # @param attrib A dictionary containing element attributes.
1083     # @return The opened element.
1084     # @defreturn Element
1085
1086     def start(self, tag, attrs):
1087         self._flush()
1088         self._last = elem = self._factory(tag, attrs)
1089         if self._elem:
1090             self._elem[-1].append(elem)
1091         self._elem.append(elem)
1092         self._tail = 0
1093         return elem
1094
1095     ##
1096     # Closes the current element.
1097     #
1098     # @param tag The element name.
1099     # @return The closed element.
1100     # @defreturn Element
1101
1102     def end(self, tag):
1103         self._flush()
1104         self._last = self._elem.pop()
1105         assert self._last.tag == tag,\
1106                "end tag mismatch (expected %s, got %s)" % (
1107                    self._last.tag, tag)
1108         self._tail = 1
1109         return self._last
1110
1111 ##
1112 # Element structure builder for XML source data, based on the
1113 # <b>expat</b> parser.
1114 #
1115 # @keyparam target Target object.  If omitted, the builder uses an
1116 #     instance of the standard {@link #TreeBuilder} class.
1117 # @keyparam html Predefine HTML entities.  This flag is not supported
1118 #     by the current implementation.
1119 # @see #ElementTree
1120 # @see #TreeBuilder
1121
1122 class XMLTreeBuilder:
1123
1124     def __init__(self, html=0, target=None):
1125         try:
1126             from xml.parsers import expat
1127         except ImportError:
1128             raise ImportError(
1129                 "No module named expat; use SimpleXMLTreeBuilder instead"
1130                 )
1131         self._parser = parser = expat.ParserCreate(None, "}")
1132         if target is None:
1133             target = TreeBuilder()
1134         self._target = target
1135         self._names = {} # name memo cache
1136         # callbacks
1137         parser.DefaultHandlerExpand = self._default
1138         parser.StartElementHandler = self._start
1139         parser.EndElementHandler = self._end
1140         parser.CharacterDataHandler = self._data
1141         # let expat do the buffering, if supported
1142         try:
1143             self._parser.buffer_text = 1
1144         except AttributeError:
1145             pass
1146         # use new-style attribute handling, if supported
1147         try:
1148             self._parser.ordered_attributes = 1
1149             self._parser.specified_attributes = 1
1150             parser.StartElementHandler = self._start_list
1151         except AttributeError:
1152             pass
1153         encoding = None
1154         if not parser.returns_unicode:
1155             encoding = "utf-8"
1156         # target.xml(encoding, None)
1157         self._doctype = None
1158         self.entity = {}
1159
1160     def _fixtext(self, text):
1161         # convert text string to ascii, if possible
1162         try:
1163             return _encode(text, "ascii")
1164         except UnicodeError:
1165             return text
1166
1167     def _fixname(self, key):
1168         # expand qname, and convert name string to ascii, if possible
1169         try:
1170             name = self._names[key]
1171         except KeyError:
1172             name = key
1173             if "}" in name:
1174                 name = "{" + name
1175             self._names[key] = name = self._fixtext(name)
1176         return name
1177
1178     def _start(self, tag, attrib_in):
1179         fixname = self._fixname
1180         tag = fixname(tag)
1181         attrib = {}
1182         for key, value in attrib_in.items():
1183             attrib[fixname(key)] = self._fixtext(value)
1184         return self._target.start(tag, attrib)
1185
1186     def _start_list(self, tag, attrib_in):
1187         fixname = self._fixname
1188         tag = fixname(tag)
1189         attrib = {}
1190         if attrib_in:
1191             for i in range(0, len(attrib_in), 2):
1192                 attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
1193         return self._target.start(tag, attrib)
1194
1195     def _data(self, text):
1196         return self._target.data(self._fixtext(text))
1197
1198     def _end(self, tag):
1199         return self._target.end(self._fixname(tag))
1200
1201     def _default(self, text):
1202         prefix = text[:1]
1203         if prefix == "&":
1204             # deal with undefined entities
1205             try:
1206                 self._target.data(self.entity[text[1:-1]])
1207             except KeyError:
1208                 from xml.parsers import expat
1209                 raise expat.error(
1210                     "undefined entity %s: line %d, column %d" %
1211                     (text, self._parser.ErrorLineNumber,
1212                     self._parser.ErrorColumnNumber)
1213                     )
1214         elif prefix == "<" and text[:9] == "<!DOCTYPE":
1215             self._doctype = [] # inside a doctype declaration
1216         elif self._doctype is not None:
1217             # parse doctype contents
1218             if prefix == ">":
1219                 self._doctype = None
1220                 return
1221             text = string.strip(text)
1222             if not text:
1223                 return
1224             self._doctype.append(text)
1225             n = len(self._doctype)
1226             if n > 2:
1227                 type = self._doctype[1]
1228                 if type == "PUBLIC" and n == 4:
1229                     name, type, pubid, system = self._doctype
1230                 elif type == "SYSTEM" and n == 3:
1231                     name, type, system = self._doctype
1232                     pubid = None
1233                 else:
1234                     return
1235                 if pubid:
1236                     pubid = pubid[1:-1]
1237                 self.doctype(name, pubid, system[1:-1])
1238                 self._doctype = None
1239
1240     ##
1241     # Handles a doctype declaration.
1242     #
1243     # @param name Doctype name.
1244     # @param pubid Public identifier.
1245     # @param system System identifier.
1246
1247     def doctype(self, name, pubid, system):
1248         pass
1249
1250     ##
1251     # Feeds data to the parser.
1252     #
1253     # @param data Encoded data.
1254
1255     def feed(self, data):
1256         self._parser.Parse(data, 0)
1257
1258     ##
1259     # Finishes feeding data to the parser.
1260     #
1261     # @return An element structure.
1262     # @defreturn Element
1263
1264     def close(self):
1265         self._parser.Parse("", 1) # end of data
1266         tree = self._target.close()
1267         del self._target, self._parser # get rid of circular references
1268         return tree