]> SALOME platform Git repositories - tools/sat.git/blob - src/ElementTree.py
Salome HOME
LOG : Add full option, logs correspond to the application
[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         res = self.attrib.get(key, default)
374         if not res:
375             res = self.attrib.get(key.encode(), default)
376         if isinstance(res, bytes):
377             return res.decode()
378         else:
379             return res
380
381     ##
382     # Sets an element attribute.
383     #
384     # @param key What attribute to set.
385     # @param value The attribute value.
386
387     def set(self, key, value):
388         self.attrib[key] = value
389
390     ##
391     # Gets a list of attribute names.  The names are returned in an
392     # arbitrary order (just like for an ordinary Python dictionary).
393     #
394     # @return A list of element attribute names.
395     # @defreturn list of strings
396
397     def keys(self):
398         res = []
399         for key in self.attrib.keys():
400             if isinstance(key, bytes):
401                 res.append(key.decode())
402             else:
403                 res.append(key)
404         return res
405                 
406     ##
407     # Gets element attributes, as a sequence.  The attributes are
408     # returned in an arbitrary order.
409     #
410     # @return A list of (name, value) tuples for all attributes.
411     # @defreturn list of (string, string) tuples
412
413     def items(self):
414         return self.attrib.items()
415
416     ##
417     # Creates a tree iterator.  The iterator loops over this element
418     # and all subelements, in document order, and returns all elements
419     # with a matching tag.
420     # <p>
421     # If the tree structure is modified during iteration, the result
422     # is undefined.
423     #
424     # @param tag What tags to look for (default is to return all elements).
425     # @return A list or iterator containing all the matching elements.
426     # @defreturn list or iterator
427
428     def getiterator(self, tag=None):
429         nodes = []
430         if tag == "*":
431             tag = None
432         if tag is None or self.tag == tag:
433             nodes.append(self)
434         for node in self._children:
435             nodes.extend(node.getiterator(tag))
436         return nodes
437
438 # compatibility
439 _Element = _ElementInterface
440
441 ##
442 # Element factory.  This function returns an object implementing the
443 # standard Element interface.  The exact class or type of that object
444 # is implementation dependent, but it will always be compatible with
445 # the {@link #_ElementInterface} class in this module.
446 # <p>
447 # The element name, attribute names, and attribute values can be
448 # either 8-bit ASCII strings or Unicode strings.
449 #
450 # @param tag The element name.
451 # @param attrib An optional dictionary, containing element attributes.
452 # @param **extra Additional attributes, given as keyword arguments.
453 # @return An element instance.
454 # @defreturn Element
455
456 def Element(tag, attrib={}, **extra):
457     attrib = attrib.copy()
458     attrib.update(extra)
459     return _ElementInterface(tag, attrib)
460
461 ##
462 # Subelement factory.  This function creates an element instance, and
463 # appends it to an existing element.
464 # <p>
465 # The element name, attribute names, and attribute values can be
466 # either 8-bit ASCII strings or Unicode strings.
467 #
468 # @param parent The parent element.
469 # @param tag The subelement name.
470 # @param attrib An optional dictionary, containing element attributes.
471 # @param **extra Additional attributes, given as keyword arguments.
472 # @return An element instance.
473 # @defreturn Element
474
475 def SubElement(parent, tag, attrib={}, **extra):
476     attrib = attrib.copy()
477     attrib.update(extra)
478     element = parent.makeelement(tag, attrib)
479     parent.append(element)
480     return element
481
482 ##
483 # Comment element factory.  This factory function creates a special
484 # element that will be serialized as an XML comment.
485 # <p>
486 # The comment string can be either an 8-bit ASCII string or a Unicode
487 # string.
488 #
489 # @param text A string containing the comment string.
490 # @return An element instance, representing a comment.
491 # @defreturn Element
492
493 def Comment(text=None):
494     element = Element(Comment)
495     element.text = text
496     return element
497
498 ##
499 # PI element factory.  This factory function creates a special element
500 # that will be serialized as an XML processing instruction.
501 #
502 # @param target A string containing the PI target.
503 # @param text A string containing the PI contents, if any.
504 # @return An element instance, representing a PI.
505 # @defreturn Element
506
507 def ProcessingInstruction(target, text=None):
508     element = Element(ProcessingInstruction)
509     element.text = target
510     if text:
511         element.text = element.text + " " + text
512     return element
513
514 PI = ProcessingInstruction
515
516 ##
517 # QName wrapper.  This can be used to wrap a QName attribute value, in
518 # order to get proper namespace handling on output.
519 #
520 # @param text A string containing the QName value, in the form {uri}local,
521 #     or, if the tag argument is given, the URI part of a QName.
522 # @param tag Optional tag.  If given, the first argument is interpreted as
523 #     an URI, and this argument is interpreted as a local name.
524 # @return An opaque object, representing the QName.
525
526 class QName:
527     def __init__(self, text_or_uri, tag=None):
528         if tag:
529             text_or_uri = "{%s}%s" % (text_or_uri, tag)
530         self.text = text_or_uri
531     def __str__(self):
532         return self.text
533     def __hash__(self):
534         return hash(self.text)
535     def __cmp__(self, other):
536         if isinstance(other, QName):
537             return cmp(self.text, other.text)
538         return cmp(self.text, other)
539
540 ##
541 # ElementTree wrapper class.  This class represents an entire element
542 # hierarchy, and adds some extra support for serialization to and from
543 # standard XML.
544 #
545 # @param element Optional root element.
546 # @keyparam file Optional file handle or name.  If given, the
547 #     tree is initialized with the contents of this XML file.
548
549 class ElementTree:
550
551     def __init__(self, element=None, file=None):
552         assert element is None or iselement(element)
553         self._root = element # first node
554         if file:
555             self.parse(file)
556
557     ##
558     # Gets the root element for this tree.
559     #
560     # @return An element instance.
561     # @defreturn Element
562
563     def getroot(self):
564         return self._root
565
566     ##
567     # Replaces the root element for this tree.  This discards the
568     # current contents of the tree, and replaces it with the given
569     # element.  Use with care.
570     #
571     # @param element An element instance.
572
573     def _setroot(self, element):
574         assert iselement(element)
575         self._root = element
576
577     ##
578     # Loads an external XML document into this element tree.
579     #
580     # @param source A file name or file object.
581     # @param parser An optional parser instance.  If not given, the
582     #     standard {@link XMLTreeBuilder} parser is used.
583     # @return The document root element.
584     # @defreturn Element
585
586     def parse(self, source, parser=None):
587         if not hasattr(source, "read"):
588             source = open(source, "rb")
589         if not parser:
590             parser = XMLTreeBuilder()
591         while 1:
592             data = source.read(32768)
593             if not data:
594                 break
595             parser.feed(data)
596         self._root = parser.close()
597         return self._root
598
599     ##
600     # Creates a tree iterator for the root element.  The iterator loops
601     # over all elements in this tree, in document order.
602     #
603     # @param tag What tags to look for (default is to return all elements)
604     # @return An iterator.
605     # @defreturn iterator
606
607     def getiterator(self, tag=None):
608         assert self._root is not None
609         return self._root.getiterator(tag)
610
611     ##
612     # Finds the first toplevel element with given tag.
613     # Same as getroot().find(path).
614     #
615     # @param path What element to look for.
616     # @return The first matching element, or None if no element was found.
617     # @defreturn Element or None
618
619     def find(self, path):
620         assert self._root is not None
621         if path[:1] == "/":
622             path = "." + path
623         return self._root.find(path)
624
625     ##
626     # Finds the element text for the first toplevel element with given
627     # tag.  Same as getroot().findtext(path).
628     #
629     # @param path What toplevel element to look for.
630     # @param default What to return if the element was not found.
631     # @return The text content of the first matching element, or the
632     #     default value no element was found.  Note that if the element
633     #     has is found, but has no text content, this method returns an
634     #     empty string.
635     # @defreturn string
636
637     def findtext(self, path, default=None):
638         assert self._root is not None
639         if path[:1] == "/":
640             path = "." + path
641         return self._root.findtext(path, default)
642
643     ##
644     # Finds all toplevel elements with the given tag.
645     # Same as getroot().findall(path).
646     #
647     # @param path What element to look for.
648     # @return A list or iterator containing all matching elements,
649     #    in document order.
650     # @defreturn list of Element instances
651
652     def findall(self, path):
653         assert self._root is not None
654         if path[:1] == "/":
655             path = "." + path
656         return self._root.findall(path)
657
658     ##
659     # Writes the element tree to a file, as XML.
660     #
661     # @param file A file name, or a file object opened for writing.
662     # @param encoding Optional output encoding (default is US-ASCII).
663
664     def write(self, file, encoding="us-ascii"):
665         assert self._root is not None
666         if not hasattr(file, "write"):
667             file = open(file, "wb")
668         if not encoding:
669             encoding = "us-ascii"
670         elif encoding != "utf-8" and encoding != "us-ascii":
671             file.write("<?xml version='1.0' encoding='%s'?>\n" % encoding)
672         self._write(file, self._root, encoding, {})
673
674     def _write(self, file, node, encoding, namespaces, margin=0):
675         # write XML to file
676         tag = node.tag
677         if tag is Comment:
678             file.write("<!-- %s -->\n" % _escape_cdata(node.text, encoding))
679         elif tag is ProcessingInstruction:
680             file.write("<?%s?>\n" % _escape_cdata(node.text, encoding))
681         else:
682             items = node.items()
683             xmlns_items = [] # new namespaces in this scope
684             try:
685                 if isinstance(tag, QName) or tag[:1] == "{":
686                     tag, xmlns = fixtag(tag, namespaces)
687                     if xmlns: xmlns_items.append(xmlns)
688             except TypeError:
689                 _raise_serialization_error(tag)
690             file.write(' ' * margin)
691             file.write(_encode("<", encoding) + _encode(tag, encoding))
692             if items or xmlns_items:
693                 items = sorted(items) # lexical order
694                 for k, v in items:
695                     try:
696                         if isinstance(k, QName) or k[:1] == "{":
697                             k, xmlns = fixtag(k, namespaces)
698                             if xmlns: xmlns_items.append(xmlns)
699                     except TypeError:
700                         _raise_serialization_error(k)
701                     try:
702                         if isinstance(v, QName):
703                             v, xmlns = fixtag(v, namespaces)
704                             if xmlns: xmlns_items.append(xmlns)
705                     except TypeError:
706                         _raise_serialization_error(v)
707                     file.write(" %s=\"%s\"" % (k,v))
708                 for k, v in xmlns_items:
709                     file.write(" %s=\"%s\"" % (k,v))
710             if node.text or len(node):
711                 file.write(">")
712                 if node.text:
713                     file.write(_escape_cdata(node.text, encoding))
714                 if len(node) > 0: file.write("\n")
715                 for n in node:
716                     self._write(file, n, encoding, namespaces, margin + 2)
717                 if len(node) > 0: file.write(' ' * margin)
718                 file.write(_encode("</", encoding) + _encode(tag, encoding) + _encode(">\n", encoding))
719             else:
720                 file.write("/>\n")
721             for k, v in xmlns_items:
722                 del namespaces[v]
723         if node.tail:
724             file.write(_escape_cdata(node.tail, encoding))
725
726 # --------------------------------------------------------------------
727 # helpers
728
729 ##
730 # Checks if an object appears to be a valid element object.
731 #
732 # @param An element instance.
733 # @return A true value if this is an element object.
734 # @defreturn flag
735
736 def iselement(element):
737     # FIXME: not sure about this; might be a better idea to look
738     # for tag/attrib/text attributes
739     return isinstance(element, _ElementInterface) or hasattr(element, "tag")
740
741 ##
742 # Writes an element tree or element structure to sys.stdout.  This
743 # function should be used for debugging only.
744 # <p>
745 # The exact output format is implementation dependent.  In this
746 # version, it's written as an ordinary XML file.
747 #
748 # @param elem An element tree or an individual element.
749
750 def dump(elem):
751     # debugging
752     if not isinstance(elem, ElementTree):
753         elem = ElementTree(elem)
754     elem.write(sys.stdout)
755     tail = elem.getroot().tail
756     if not tail or tail[-1] != "\n":
757         sys.stdout.write("\n")
758
759 def _encode(s, encoding):
760     try:
761         return s.encode(encoding)
762     except AttributeError:
763         return s # 1.5.2: assume the string uses the right encoding
764
765 if sys.version[:3] == "1.5":
766     _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2
767 else:
768     _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"'))
769
770 _escape_map = {
771     "&": "&amp;",
772     "<": "&lt;",
773     ">": "&gt;",
774     '"': "&quot;",
775 }
776
777 _namespace_map = {
778     # "well-known" namespace prefixes
779     "http://www.w3.org/XML/1998/namespace": "xml",
780     "http://www.w3.org/1999/xhtml": "html",
781     "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
782     "http://schemas.xmlsoap.org/wsdl/": "wsdl",
783 }
784
785 def _raise_serialization_error(text):
786     raise TypeError(
787         "cannot serialize %r (type %s)" % (text, type(text).__name__)
788         )
789
790 def _encode_entity(text, pattern=_escape):
791     # map reserved and non-ascii characters to numerical entities
792     def escape_entities(m, map=_escape_map):
793         out = []
794         append = out.append
795         for char in m.group():
796             text = map.get(char)
797             if text is None:
798                 text = "&#%d;" % ord(char)
799             append(text)
800         return string.join(out, "")
801     try:
802         return _encode(pattern.sub(escape_entities, text), "ascii")
803     except TypeError:
804         _raise_serialization_error(text)
805
806 #
807 # the following functions assume an ascii-compatible encoding
808 # (or "utf-16")
809
810 def _escape_cdata(text, encoding=None, replace=str.replace):
811     # escape character data
812     try:
813         if platform.python_version()[0] == '2': # python 2.x.y
814             if encoding:
815                 try:
816                     text = _encode(text, encoding)
817                 except UnicodeError:
818                     return _encode_entity(text)
819             
820         text = replace(text, "&", "&amp;")
821         text = replace(text, "<", "&lt;")
822         text = replace(text, ">", "&gt;")
823         text = replace(text, "####newLine####", "<br \>")
824         if encoding:
825             try:
826                 text = _encode(text, encoding)
827             except UnicodeError:
828                 return _encode_entity(text)
829         return text
830     except (TypeError, AttributeError):
831         _raise_serialization_error(text)
832
833 def _escape_attrib(text, encoding=None, replace=str.replace):
834     # escape attribute value
835     try:
836         text = replace(text, "&", "&amp;")
837         text = replace(text, "'", "&apos;") # FIXME: overkill
838         text = replace(text, "\"", "&quot;")
839         text = replace(text, "<", "&lt;")
840         text = replace(text, ">", "&gt;")
841         if encoding:
842             try:
843                 text = _encode(text, encoding)
844             except UnicodeError:
845                 return _encode_entity(text)
846         return text
847     except (TypeError, AttributeError):
848         _raise_serialization_error(text)
849
850 def fixtag(tag, namespaces):
851     # given a decorated tag (of the form {uri}tag), return prefixed
852     # tag and namespace declaration, if any
853     if isinstance(tag, QName):
854         tag = tag.text
855     namespace_uri, tag = string.split(tag[1:], "}", 1)
856     prefix = namespaces.get(namespace_uri)
857     if prefix is None:
858         prefix = _namespace_map.get(namespace_uri)
859         if prefix is None:
860             prefix = "ns%d" % len(namespaces)
861         namespaces[namespace_uri] = prefix
862         if prefix == "xml":
863             xmlns = None
864         else:
865             xmlns = ("xmlns:%s" % prefix, namespace_uri)
866     else:
867         xmlns = None
868     return "%s:%s" % (prefix, tag), xmlns
869
870 ##
871 # Parses an XML document into an element tree.
872 #
873 # @param source A filename or file object containing XML data.
874 # @param parser An optional parser instance.  If not given, the
875 #     standard {@link XMLTreeBuilder} parser is used.
876 # @return An ElementTree instance
877
878 def parse(source, parser=None):
879     tree = ElementTree()
880     tree.parse(source, parser)
881     return tree
882
883 ##
884 # Parses an XML document into an element tree incrementally, and reports
885 # what's going on to the user.
886 #
887 # @param source A filename or file object containing XML data.
888 # @param events A list of events to report back.  If omitted, only "end"
889 #     events are reported.
890 # @return A (event, elem) iterator.
891
892 class iterparse:
893
894     def __init__(self, source, events=None):
895         if not hasattr(source, "read"):
896             source = open(source, "rb")
897         self._file = source
898         self._events = []
899         self._index = 0
900         self.root = self._root = None
901         self._parser = XMLTreeBuilder()
902         # wire up the parser for event reporting
903         parser = self._parser._parser
904         append = self._events.append
905         if events is None:
906             events = ["end"]
907         for event in events:
908             if event == "start":
909                 try:
910                     parser.ordered_attributes = 1
911                     parser.specified_attributes = 1
912                     def handler(tag, attrib_in, event=event, append=append,
913                                 start=self._parser._start_list):
914                         append((event, start(tag, attrib_in)))
915                     parser.StartElementHandler = handler
916                 except AttributeError:
917                     def handler(tag, attrib_in, event=event, append=append,
918                                 start=self._parser._start):
919                         append((event, start(tag, attrib_in)))
920                     parser.StartElementHandler = handler
921             elif event == "end":
922                 def handler(tag, event=event, append=append,
923                             end=self._parser._end):
924                     append((event, end(tag)))
925                 parser.EndElementHandler = handler
926             elif event == "start-ns":
927                 def handler(prefix, uri, event=event, append=append):
928                     try:
929                         uri = _encode(uri, "ascii")
930                     except UnicodeError:
931                         pass
932                     append((event, (prefix or "", uri)))
933                 parser.StartNamespaceDeclHandler = handler
934             elif event == "end-ns":
935                 def handler(prefix, event=event, append=append):
936                     append((event, None))
937                 parser.EndNamespaceDeclHandler = handler
938
939     def next(self):
940         while 1:
941             try:
942                 item = self._events[self._index]
943             except IndexError:
944                 if self._parser is None:
945                     self.root = self._root
946                     try:
947                         raise StopIteration
948                     except NameError:
949                         raise IndexError
950                 # load event buffer
951                 del self._events[:]
952                 self._index = 0
953                 data = self._file.read(16384)
954                 if data:
955                     self._parser.feed(data)
956                 else:
957                     self._root = self._parser.close()
958                     self._parser = None
959             else:
960                 self._index = self._index + 1
961                 return item
962
963     try:
964         iter
965         def __iter__(self):
966             return self
967     except NameError:
968         def __getitem__(self, index):
969             return self.next()
970
971 ##
972 # Parses an XML document from a string constant.  This function can
973 # be used to embed "XML literals" in Python code.
974 #
975 # @param source A string containing XML data.
976 # @return An Element instance.
977 # @defreturn Element
978
979 def XML(text):
980     parser = XMLTreeBuilder()
981     parser.feed(text)
982     return parser.close()
983
984 ##
985 # Parses an XML document from a string constant, and also returns
986 # a dictionary which maps from element id:s to elements.
987 #
988 # @param source A string containing XML data.
989 # @return A tuple containing an Element instance and a dictionary.
990 # @defreturn (Element, dictionary)
991
992 def XMLID(text):
993     parser = XMLTreeBuilder()
994     parser.feed(text)
995     tree = parser.close()
996     ids = {}
997     for elem in tree.getiterator():
998         id = elem.get("id")
999         if id:
1000             ids[id] = elem
1001     return tree, ids
1002
1003 ##
1004 # Parses an XML document from a string constant.  Same as {@link #XML}.
1005 #
1006 # @def fromstring(text)
1007 # @param source A string containing XML data.
1008 # @return An Element instance.
1009 # @defreturn Element
1010
1011 fromstring = XML
1012
1013 ##
1014 # Generates a string representation of an XML element, including all
1015 # subelements.
1016 #
1017 # @param element An Element instance.
1018 # @return An encoded string containing the XML data.
1019 # @defreturn string
1020
1021 def tostring(element, encoding=None):
1022     class dummy:
1023         pass
1024     data = []
1025     file = dummy()
1026     file.write = data.append
1027     ElementTree(element).write(file, encoding)
1028     data2 = []
1029     for item in data:
1030         if isinstance(item, bytes):
1031             item = item.decode()
1032         data2.append(item)
1033     return "".join(data2)
1034
1035 ##
1036 # Generic element structure builder.  This builder converts a sequence
1037 # of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link
1038 # #TreeBuilder.end} method calls to a well-formed element structure.
1039 # <p>
1040 # You can use this class to build an element structure using a custom XML
1041 # parser, or a parser for some other XML-like format.
1042 #
1043 # @param element_factory Optional element factory.  This factory
1044 #    is called to create new Element instances, as necessary.
1045
1046 class TreeBuilder:
1047
1048     def __init__(self, element_factory=None):
1049         self._data = [] # data collector
1050         self._elem = [] # element stack
1051         self._last = None # last element
1052         self._tail = None # true if we're after an end tag
1053         if element_factory is None:
1054             element_factory = _ElementInterface
1055         self._factory = element_factory
1056
1057     ##
1058     # Flushes the parser buffers, and returns the toplevel documen
1059     # element.
1060     #
1061     # @return An Element instance.
1062     # @defreturn Element
1063
1064     def close(self):
1065         assert len(self._elem) == 0, "missing end tags"
1066         assert self._last != None, "missing toplevel element"
1067         return self._last
1068
1069     def _flush(self):
1070         if self._data:
1071             if self._last is not None:
1072                 text = ""
1073                 for item in self._data:
1074                     try:
1075                         text += item
1076                     except:
1077                         text += item.decode()
1078                 if self._tail:
1079                     assert self._last.tail is None, "internal error (tail)"
1080                     self._last.tail = text
1081                 else:
1082                     assert self._last.text is None, "internal error (text)"
1083                     self._last.text = text
1084             self._data = []
1085
1086     ##
1087     # Adds text to the current element.
1088     #
1089     # @param data A string.  This should be either an 8-bit string
1090     #    containing ASCII text, or a Unicode string.
1091
1092     def data(self, data):
1093         self._data.append(data)
1094
1095     ##
1096     # Opens a new element.
1097     #
1098     # @param tag The element name.
1099     # @param attrib A dictionary containing element attributes.
1100     # @return The opened element.
1101     # @defreturn Element
1102
1103     def start(self, tag, attrs):
1104         self._flush()
1105         self._last = elem = self._factory(tag, attrs)
1106         if self._elem:
1107             self._elem[-1].append(elem)
1108         self._elem.append(elem)
1109         self._tail = 0
1110         return elem
1111
1112     ##
1113     # Closes the current element.
1114     #
1115     # @param tag The element name.
1116     # @return The closed element.
1117     # @defreturn Element
1118
1119     def end(self, tag):
1120         self._flush()
1121         self._last = self._elem.pop()
1122         assert self._last.tag == tag,\
1123                "end tag mismatch (expected %s, got %s)" % (
1124                    self._last.tag, tag)
1125         self._tail = 1
1126         return self._last
1127
1128 ##
1129 # Element structure builder for XML source data, based on the
1130 # <b>expat</b> parser.
1131 #
1132 # @keyparam target Target object.  If omitted, the builder uses an
1133 #     instance of the standard {@link #TreeBuilder} class.
1134 # @keyparam html Predefine HTML entities.  This flag is not supported
1135 #     by the current implementation.
1136 # @see #ElementTree
1137 # @see #TreeBuilder
1138
1139 class XMLTreeBuilder:
1140
1141     def __init__(self, html=0, target=None):
1142         try:
1143             from xml.parsers import expat
1144         except ImportError:
1145             raise ImportError(
1146                 "No module named expat; use SimpleXMLTreeBuilder instead"
1147                 )
1148         self._parser = parser = expat.ParserCreate(None, "}")
1149         if target is None:
1150             target = TreeBuilder()
1151         self._target = target
1152         self._names = {} # name memo cache
1153         # callbacks
1154         parser.DefaultHandlerExpand = self._default
1155         parser.StartElementHandler = self._start
1156         parser.EndElementHandler = self._end
1157         parser.CharacterDataHandler = self._data
1158         # let expat do the buffering, if supported
1159         try:
1160             self._parser.buffer_text = 1
1161         except AttributeError:
1162             pass
1163         # use new-style attribute handling, if supported
1164         try:
1165             self._parser.ordered_attributes = 1
1166             self._parser.specified_attributes = 1
1167             parser.StartElementHandler = self._start_list
1168         except AttributeError:
1169             pass
1170         #encoding = None
1171         #if not parser.returns_unicode:
1172         #    encoding = "utf-8"
1173         # target.xml(encoding, None)
1174         self._doctype = None
1175         self.entity = {}
1176
1177     def _fixtext(self, text):
1178         # convert text string to ascii, if possible
1179         try:
1180             return _encode(text, "ascii")
1181         except UnicodeError:
1182             return text
1183
1184     def _fixname(self, key):
1185         # expand qname, and convert name string to ascii, if possible
1186         try:
1187             name = self._names[key]
1188         except KeyError:
1189             name = key
1190             if "}" in name:
1191                 name = "{" + name
1192             self._names[key] = name = self._fixtext(name)
1193         return name
1194
1195     def _start(self, tag, attrib_in):
1196         fixname = self._fixname
1197         tag = fixname(tag)
1198         attrib = {}
1199         for key, value in attrib_in.items():
1200             attrib[fixname(key)] = self._fixtext(value)
1201         return self._target.start(tag, attrib)
1202
1203     def _start_list(self, tag, attrib_in):
1204         fixname = self._fixname
1205         tag = fixname(tag)
1206         attrib = {}
1207         if attrib_in:
1208             for i in range(0, len(attrib_in), 2):
1209                 attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
1210         return self._target.start(tag, attrib)
1211
1212     def _data(self, text):
1213         return self._target.data(self._fixtext(text))
1214
1215     def _end(self, tag):
1216         return self._target.end(self._fixname(tag))
1217
1218     def _default(self, text):
1219         prefix = text[:1]
1220         if prefix == "&":
1221             # deal with undefined entities
1222             try:
1223                 self._target.data(self.entity[text[1:-1]])
1224             except KeyError:
1225                 from xml.parsers import expat
1226                 raise expat.error(
1227                     "undefined entity %s: line %d, column %d" %
1228                     (text, self._parser.ErrorLineNumber,
1229                     self._parser.ErrorColumnNumber)
1230                     )
1231         elif prefix == "<" and text[:9] == "<!DOCTYPE":
1232             self._doctype = [] # inside a doctype declaration
1233         elif self._doctype is not None:
1234             # parse doctype contents
1235             if prefix == ">":
1236                 self._doctype = None
1237                 return
1238             text = string.strip(text)
1239             if not text:
1240                 return
1241             self._doctype.append(text)
1242             n = len(self._doctype)
1243             if n > 2:
1244                 type = self._doctype[1]
1245                 if type == "PUBLIC" and n == 4:
1246                     name, type, pubid, system = self._doctype
1247                 elif type == "SYSTEM" and n == 3:
1248                     name, type, system = self._doctype
1249                     pubid = None
1250                 else:
1251                     return
1252                 if pubid:
1253                     pubid = pubid[1:-1]
1254                 self.doctype(name, pubid, system[1:-1])
1255                 self._doctype = None
1256
1257     ##
1258     # Handles a doctype declaration.
1259     #
1260     # @param name Doctype name.
1261     # @param pubid Public identifier.
1262     # @param system System identifier.
1263
1264     def doctype(self, name, pubid, system):
1265         pass
1266
1267     ##
1268     # Feeds data to the parser.
1269     #
1270     # @param data Encoded data.
1271
1272     def feed(self, data):
1273         self._parser.Parse(data, 0)
1274
1275     ##
1276     # Finishes feeding data to the parser.
1277     #
1278     # @return An element structure.
1279     # @defreturn Element
1280
1281     def close(self):
1282         self._parser.Parse("", 1) # end of data
1283         tree = self._target.close()
1284         del self._target, self._parser # get rid of circular references
1285         return tree