~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/util/elementtree/ElementTree.py

  • Committer: John Arbash Meinel
  • Date: 2009-03-04 16:56:05 UTC
  • mto: (0.17.34 trunk)
  • mto: This revision was merged to the branch mainline in revision 4280.
  • Revision ID: john@arbash-meinel.com-20090304165605-zbap3q69laok4o6p
fully remove the eq table for now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
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 getattr(source, "read", None) is None:
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 getattr(file, "write", None) is None:
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):
663
 
        # write XML to file
664
 
        tag = node.tag
665
 
        if tag is Comment:
666
 
            file.write("<!-- %s -->" % _escape_cdata(node.text, encoding))
667
 
        elif tag is ProcessingInstruction:
668
 
            file.write("<?%s?>" % _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("<" + _encode(tag, encoding))
679
 
            if items or xmlns_items:
680
 
                items.sort() # lexical order
681
 
                for k, v in items:
682
 
                    try:
683
 
                        if isinstance(k, QName) or k[:1] == "{":
684
 
                            k, xmlns = fixtag(k, namespaces)
685
 
                            if xmlns: xmlns_items.append(xmlns)
686
 
                    except TypeError:
687
 
                        _raise_serialization_error(k)
688
 
                    try:
689
 
                        if isinstance(v, QName):
690
 
                            v, xmlns = fixtag(v, namespaces)
691
 
                            if xmlns: xmlns_items.append(xmlns)
692
 
                    except TypeError:
693
 
                        _raise_serialization_error(v)
694
 
                    file.write(" %s=\"%s\"" % (_encode(k, encoding),
695
 
                                               _escape_attrib(v, encoding)))
696
 
                for k, v in xmlns_items:
697
 
                    file.write(" %s=\"%s\"" % (_encode(k, encoding),
698
 
                                               _escape_attrib(v, encoding)))
699
 
            if node.text or len(node):
700
 
                file.write(">")
701
 
                if node.text:
702
 
                    file.write(_escape_cdata(node.text, encoding))
703
 
                for n in node:
704
 
                    self._write(file, n, encoding, namespaces)
705
 
                file.write("</" + _encode(tag, encoding) + ">")
706
 
            else:
707
 
                file.write(" />")
708
 
            for k, v in xmlns_items:
709
 
                del namespaces[v]
710
 
        if node.tail:
711
 
            file.write(_escape_cdata(node.tail, encoding))
712
 
 
713
 
# --------------------------------------------------------------------
714
 
# helpers
715
 
 
716
 
##
717
 
# Checks if an object appears to be a valid element object.
718
 
#
719
 
# @param An element instance.
720
 
# @return A true value if this is an element object.
721
 
# @defreturn flag
722
 
 
723
 
def iselement(element):
724
 
    # FIXME: not sure about this; might be a better idea to look
725
 
    # for tag/attrib/text attributes
726
 
    return isinstance(element, _ElementInterface) or (getattr(element, "tag", None) is not None)
727
 
 
728
 
##
729
 
# Writes an element tree or element structure to sys.stdout.  This
730
 
# function should be used for debugging only.
731
 
# <p>
732
 
# The exact output format is implementation dependent.  In this
733
 
# version, it's written as an ordinary XML file.
734
 
#
735
 
# @param elem An element tree or an individual element.
736
 
 
737
 
def dump(elem):
738
 
    # debugging
739
 
    if not isinstance(elem, ElementTree):
740
 
        elem = ElementTree(elem)
741
 
    elem.write(sys.stdout)
742
 
    tail = elem.getroot().tail
743
 
    if not tail or tail[-1] != "\n":
744
 
        sys.stdout.write("\n")
745
 
 
746
 
def _encode(s, encoding):
747
 
    try:
748
 
        return s.encode(encoding)
749
 
    except AttributeError:
750
 
        return s # 1.5.2: assume the string uses the right encoding
751
 
 
752
 
if sys.version[:3] == "1.5":
753
 
    _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2
754
 
else:
755
 
    _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"'))
756
 
 
757
 
_escape_map = {
758
 
    "&": "&amp;",
759
 
    "<": "&lt;",
760
 
    ">": "&gt;",
761
 
    '"': "&quot;",
762
 
}
763
 
 
764
 
_namespace_map = {
765
 
    # "well-known" namespace prefixes
766
 
    "http://www.w3.org/XML/1998/namespace": "xml",
767
 
    "http://www.w3.org/1999/xhtml": "html",
768
 
    "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
769
 
    "http://schemas.xmlsoap.org/wsdl/": "wsdl",
770
 
}
771
 
 
772
 
def _raise_serialization_error(text):
773
 
    raise TypeError(
774
 
        "cannot serialize %r (type %s)" % (text, type(text).__name__)
775
 
        )
776
 
 
777
 
def _encode_entity(text, pattern=_escape):
778
 
    # map reserved and non-ascii characters to numerical entities
779
 
    def escape_entities(m, map=_escape_map):
780
 
        out = []
781
 
        append = out.append
782
 
        for char in m.group():
783
 
            text = map.get(char)
784
 
            if text is None:
785
 
                text = "&#%d;" % ord(char)
786
 
            append(text)
787
 
        return string.join(out, "")
788
 
    try:
789
 
        return _encode(pattern.sub(escape_entities, text), "ascii")
790
 
    except TypeError:
791
 
        _raise_serialization_error(text)
792
 
 
793
 
#
794
 
# the following functions assume an ascii-compatible encoding
795
 
# (or "utf-16")
796
 
 
797
 
def _escape_cdata(text, encoding=None, replace=string.replace):
798
 
    # escape character data
799
 
    try:
800
 
        if encoding:
801
 
            try:
802
 
                text = _encode(text, encoding)
803
 
            except UnicodeError:
804
 
                return _encode_entity(text)
805
 
        text = replace(text, "&", "&amp;")
806
 
        text = replace(text, "<", "&lt;")
807
 
        text = replace(text, ">", "&gt;")
808
 
        return text
809
 
    except (TypeError, AttributeError):
810
 
        _raise_serialization_error(text)
811
 
 
812
 
def _escape_attrib(text, encoding=None, replace=string.replace):
813
 
    # escape attribute value
814
 
    try:
815
 
        if encoding:
816
 
            try:
817
 
                text = _encode(text, encoding)
818
 
            except UnicodeError:
819
 
                return _encode_entity(text)
820
 
        text = replace(text, "&", "&amp;")
821
 
        text = replace(text, "'", "&apos;") # FIXME: overkill
822
 
        text = replace(text, "\"", "&quot;")
823
 
        text = replace(text, "<", "&lt;")
824
 
        text = replace(text, ">", "&gt;")
825
 
        return text
826
 
    except (TypeError, AttributeError):
827
 
        _raise_serialization_error(text)
828
 
 
829
 
def fixtag(tag, namespaces):
830
 
    # given a decorated tag (of the form {uri}tag), return prefixed
831
 
    # tag and namespace declaration, if any
832
 
    if isinstance(tag, QName):
833
 
        tag = tag.text
834
 
    namespace_uri, tag = string.split(tag[1:], "}", 1)
835
 
    prefix = namespaces.get(namespace_uri)
836
 
    if prefix is None:
837
 
        prefix = _namespace_map.get(namespace_uri)
838
 
        if prefix is None:
839
 
            prefix = "ns%d" % len(namespaces)
840
 
        namespaces[namespace_uri] = prefix
841
 
        if prefix == "xml":
842
 
            xmlns = None
843
 
        else:
844
 
            xmlns = ("xmlns:%s" % prefix, namespace_uri)
845
 
    else:
846
 
        xmlns = None
847
 
    return "%s:%s" % (prefix, tag), xmlns
848
 
 
849
 
##
850
 
# Parses an XML document into an element tree.
851
 
#
852
 
# @param source A filename or file object containing XML data.
853
 
# @param parser An optional parser instance.  If not given, the
854
 
#     standard {@link XMLTreeBuilder} parser is used.
855
 
# @return An ElementTree instance
856
 
 
857
 
def parse(source, parser=None):
858
 
    tree = ElementTree()
859
 
    tree.parse(source, parser)
860
 
    return tree
861
 
 
862
 
##
863
 
# Parses an XML document into an element tree incrementally, and reports
864
 
# what's going on to the user.
865
 
#
866
 
# @param source A filename or file object containing XML data.
867
 
# @param events A list of events to report back.  If omitted, only "end"
868
 
#     events are reported.
869
 
# @return A (event, elem) iterator.
870
 
 
871
 
class iterparse:
872
 
 
873
 
    def __init__(self, source, events=None):
874
 
        if getattr(source, "read", None) is None:
875
 
            source = open(source, "rb")
876
 
        self._file = source
877
 
        self._events = []
878
 
        self._index = 0
879
 
        self.root = self._root = None
880
 
        self._parser = XMLTreeBuilder()
881
 
        # wire up the parser for event reporting
882
 
        parser = self._parser._parser
883
 
        append = self._events.append
884
 
        if events is None:
885
 
            events = ["end"]
886
 
        for event in events:
887
 
            if event == "start":
888
 
                try:
889
 
                    parser.ordered_attributes = 1
890
 
                    parser.specified_attributes = 1
891
 
                    def handler(tag, attrib_in, event=event, append=append,
892
 
                                start=self._parser._start_list):
893
 
                        append((event, start(tag, attrib_in)))
894
 
                    parser.StartElementHandler = handler
895
 
                except AttributeError:
896
 
                    def handler(tag, attrib_in, event=event, append=append,
897
 
                                start=self._parser._start):
898
 
                        append((event, start(tag, attrib_in)))
899
 
                    parser.StartElementHandler = handler
900
 
            elif event == "end":
901
 
                def handler(tag, event=event, append=append,
902
 
                            end=self._parser._end):
903
 
                    append((event, end(tag)))
904
 
                parser.EndElementHandler = handler
905
 
            elif event == "start-ns":
906
 
                def handler(prefix, uri, event=event, append=append):
907
 
                    try:
908
 
                        uri = _encode(uri, "ascii")
909
 
                    except UnicodeError:
910
 
                        pass
911
 
                    append((event, (prefix or "", uri)))
912
 
                parser.StartNamespaceDeclHandler = handler
913
 
            elif event == "end-ns":
914
 
                def handler(prefix, event=event, append=append):
915
 
                    append((event, None))
916
 
                parser.EndNamespaceDeclHandler = handler
917
 
 
918
 
    def next(self):
919
 
        while 1:
920
 
            try:
921
 
                item = self._events[self._index]
922
 
            except IndexError:
923
 
                if self._parser is None:
924
 
                    self.root = self._root
925
 
                    try:
926
 
                        raise StopIteration
927
 
                    except NameError:
928
 
                        raise IndexError
929
 
                # load event buffer
930
 
                del self._events[:]
931
 
                self._index = 0
932
 
                data = self._file.read(16384)
933
 
                if data:
934
 
                    self._parser.feed(data)
935
 
                else:
936
 
                    self._root = self._parser.close()
937
 
                    self._parser = None
938
 
            else:
939
 
                self._index = self._index + 1
940
 
                return item
941
 
 
942
 
    try:
943
 
        iter
944
 
        def __iter__(self):
945
 
            return self
946
 
    except NameError:
947
 
        def __getitem__(self, index):
948
 
            return self.next()
949
 
 
950
 
##
951
 
# Parses an XML document from a string constant.  This function can
952
 
# be used to embed "XML literals" in Python code.
953
 
#
954
 
# @param source A string containing XML data.
955
 
# @return An Element instance.
956
 
# @defreturn Element
957
 
 
958
 
def XML(text):
959
 
    parser = XMLTreeBuilder()
960
 
    parser.feed(text)
961
 
    return parser.close()
962
 
 
963
 
##
964
 
# Parses an XML document from a string constant, and also returns
965
 
# a dictionary which maps from element id:s to elements.
966
 
#
967
 
# @param source A string containing XML data.
968
 
# @return A tuple containing an Element instance and a dictionary.
969
 
# @defreturn (Element, dictionary)
970
 
 
971
 
def XMLID(text):
972
 
    parser = XMLTreeBuilder()
973
 
    parser.feed(text)
974
 
    tree = parser.close()
975
 
    ids = {}
976
 
    for elem in tree.getiterator():
977
 
        id = elem.get("id")
978
 
        if id:
979
 
            ids[id] = elem
980
 
    return tree, ids
981
 
 
982
 
##
983
 
# Parses an XML document from a string constant.  Same as {@link #XML}.
984
 
#
985
 
# @def fromstring(text)
986
 
# @param source A string containing XML data.
987
 
# @return An Element instance.
988
 
# @defreturn Element
989
 
 
990
 
fromstring = XML
991
 
 
992
 
##
993
 
# Generates a string representation of an XML element, including all
994
 
# subelements.
995
 
#
996
 
# @param element An Element instance.
997
 
# @return An encoded string containing the XML data.
998
 
# @defreturn string
999
 
 
1000
 
def tostring(element, encoding=None):
1001
 
    class dummy:
1002
 
        pass
1003
 
    data = []
1004
 
    file = dummy()
1005
 
    file.write = data.append
1006
 
    ElementTree(element).write(file, encoding)
1007
 
    return string.join(data, "")
1008
 
 
1009
 
##
1010
 
# Generic element structure builder.  This builder converts a sequence
1011
 
# of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link
1012
 
# #TreeBuilder.end} method calls to a well-formed element structure.
1013
 
# <p>
1014
 
# You can use this class to build an element structure using a custom XML
1015
 
# parser, or a parser for some other XML-like format.
1016
 
#
1017
 
# @param element_factory Optional element factory.  This factory
1018
 
#    is called to create new Element instances, as necessary.
1019
 
 
1020
 
class TreeBuilder:
1021
 
 
1022
 
    def __init__(self, element_factory=None):
1023
 
        self._data = [] # data collector
1024
 
        self._elem = [] # element stack
1025
 
        self._last = None # last element
1026
 
        self._tail = None # true if we're after an end tag
1027
 
        if element_factory is None:
1028
 
            element_factory = _ElementInterface
1029
 
        self._factory = element_factory
1030
 
 
1031
 
    ##
1032
 
    # Flushes the parser buffers, and returns the toplevel documen
1033
 
    # element.
1034
 
    #
1035
 
    # @return An Element instance.
1036
 
    # @defreturn Element
1037
 
 
1038
 
    def close(self):
1039
 
        assert len(self._elem) == 0, "missing end tags"
1040
 
        assert self._last is not None, "missing toplevel element"
1041
 
        return self._last
1042
 
 
1043
 
    def _flush(self):
1044
 
        if self._data:
1045
 
            if self._last is not None:
1046
 
                text = string.join(self._data, "")
1047
 
                if self._tail:
1048
 
                    assert self._last.tail is None, "internal error (tail)"
1049
 
                    self._last.tail = text
1050
 
                else:
1051
 
                    assert self._last.text is None, "internal error (text)"
1052
 
                    self._last.text = text
1053
 
            self._data = []
1054
 
 
1055
 
    ##
1056
 
    # Adds text to the current element.
1057
 
    #
1058
 
    # @param data A string.  This should be either an 8-bit string
1059
 
    #    containing ASCII text, or a Unicode string.
1060
 
 
1061
 
    def data(self, data):
1062
 
        self._data.append(data)
1063
 
 
1064
 
    ##
1065
 
    # Opens a new element.
1066
 
    #
1067
 
    # @param tag The element name.
1068
 
    # @param attrib A dictionary containing element attributes.
1069
 
    # @return The opened element.
1070
 
    # @defreturn Element
1071
 
 
1072
 
    def start(self, tag, attrs):
1073
 
        self._flush()
1074
 
        self._last = elem = self._factory(tag, attrs)
1075
 
        if self._elem:
1076
 
            self._elem[-1].append(elem)
1077
 
        self._elem.append(elem)
1078
 
        self._tail = 0
1079
 
        return elem
1080
 
 
1081
 
    ##
1082
 
    # Closes the current element.
1083
 
    #
1084
 
    # @param tag The element name.
1085
 
    # @return The closed element.
1086
 
    # @defreturn Element
1087
 
 
1088
 
    def end(self, tag):
1089
 
        self._flush()
1090
 
        self._last = self._elem.pop()
1091
 
        assert self._last.tag == tag,\
1092
 
               "end tag mismatch (expected %s, got %s)" % (
1093
 
                   self._last.tag, tag)
1094
 
        self._tail = 1
1095
 
        return self._last
1096
 
 
1097
 
##
1098
 
# Element structure builder for XML source data, based on the
1099
 
# <b>expat</b> parser.
1100
 
#
1101
 
# @keyparam target Target object.  If omitted, the builder uses an
1102
 
#     instance of the standard {@link #TreeBuilder} class.
1103
 
# @keyparam html Predefine HTML entities.  This flag is not supported
1104
 
#     by the current implementation.
1105
 
# @see #ElementTree
1106
 
# @see #TreeBuilder
1107
 
 
1108
 
class XMLTreeBuilder:
1109
 
 
1110
 
    def __init__(self, html=0, target=None):
1111
 
        try:
1112
 
            from xml.parsers import expat
1113
 
        except ImportError:
1114
 
            raise ImportError(
1115
 
                "No module named expat; use SimpleXMLTreeBuilder instead"
1116
 
                )
1117
 
        self._parser = parser = expat.ParserCreate(None, "}")
1118
 
        if target is None:
1119
 
            target = TreeBuilder()
1120
 
        self._target = target
1121
 
        self._names = {} # name memo cache
1122
 
        # callbacks
1123
 
        parser.DefaultHandlerExpand = self._default
1124
 
        parser.StartElementHandler = self._start
1125
 
        parser.EndElementHandler = self._end
1126
 
        parser.CharacterDataHandler = self._data
1127
 
        # let expat do the buffering, if supported
1128
 
        try:
1129
 
            self._parser.buffer_text = 1
1130
 
        except AttributeError:
1131
 
            pass
1132
 
        # use new-style attribute handling, if supported
1133
 
        try:
1134
 
            self._parser.ordered_attributes = 1
1135
 
            self._parser.specified_attributes = 1
1136
 
            parser.StartElementHandler = self._start_list
1137
 
        except AttributeError:
1138
 
            pass
1139
 
        encoding = None
1140
 
        if not parser.returns_unicode:
1141
 
            encoding = "utf-8"
1142
 
        # target.xml(encoding, None)
1143
 
        self._doctype = None
1144
 
        self.entity = {}
1145
 
 
1146
 
    def _fixtext(self, text):
1147
 
        # convert text string to ascii, if possible
1148
 
        try:
1149
 
            return _encode(text, "ascii")
1150
 
        except UnicodeError:
1151
 
            return text
1152
 
 
1153
 
    def _fixname(self, key):
1154
 
        # expand qname, and convert name string to ascii, if possible
1155
 
        try:
1156
 
            name = self._names[key]
1157
 
        except KeyError:
1158
 
            name = key
1159
 
            if "}" in name:
1160
 
                name = "{" + name
1161
 
            self._names[key] = name = self._fixtext(name)
1162
 
        return name
1163
 
 
1164
 
    def _start(self, tag, attrib_in):
1165
 
        fixname = self._fixname
1166
 
        tag = fixname(tag)
1167
 
        attrib = {}
1168
 
        for key, value in attrib_in.items():
1169
 
            attrib[fixname(key)] = self._fixtext(value)
1170
 
        return self._target.start(tag, attrib)
1171
 
 
1172
 
    def _start_list(self, tag, attrib_in):
1173
 
        fixname = self._fixname
1174
 
        tag = fixname(tag)
1175
 
        attrib = {}
1176
 
        if attrib_in:
1177
 
            for i in range(0, len(attrib_in), 2):
1178
 
                attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
1179
 
        return self._target.start(tag, attrib)
1180
 
 
1181
 
    def _data(self, text):
1182
 
        return self._target.data(self._fixtext(text))
1183
 
 
1184
 
    def _end(self, tag):
1185
 
        return self._target.end(self._fixname(tag))
1186
 
 
1187
 
    def _default(self, text):
1188
 
        prefix = text[:1]
1189
 
        if prefix == "&":
1190
 
            # deal with undefined entities
1191
 
            try:
1192
 
                self._target.data(self.entity[text[1:-1]])
1193
 
            except KeyError:
1194
 
                from xml.parsers import expat
1195
 
                raise expat.error(
1196
 
                    "undefined entity %s: line %d, column %d" %
1197
 
                    (text, self._parser.ErrorLineNumber,
1198
 
                    self._parser.ErrorColumnNumber)
1199
 
                    )
1200
 
        elif prefix == "<" and text[:9] == "<!DOCTYPE":
1201
 
            self._doctype = [] # inside a doctype declaration
1202
 
        elif self._doctype is not None:
1203
 
            # parse doctype contents
1204
 
            if prefix == ">":
1205
 
                self._doctype = None
1206
 
                return
1207
 
            text = string.strip(text)
1208
 
            if not text:
1209
 
                return
1210
 
            self._doctype.append(text)
1211
 
            n = len(self._doctype)
1212
 
            if n > 2:
1213
 
                type = self._doctype[1]
1214
 
                if type == "PUBLIC" and n == 4:
1215
 
                    name, type, pubid, system = self._doctype
1216
 
                elif type == "SYSTEM" and n == 3:
1217
 
                    name, type, system = self._doctype
1218
 
                    pubid = None
1219
 
                else:
1220
 
                    return
1221
 
                if pubid:
1222
 
                    pubid = pubid[1:-1]
1223
 
                self.doctype(name, pubid, system[1:-1])
1224
 
                self._doctype = None
1225
 
 
1226
 
    ##
1227
 
    # Handles a doctype declaration.
1228
 
    #
1229
 
    # @param name Doctype name.
1230
 
    # @param pubid Public identifier.
1231
 
    # @param system System identifier.
1232
 
 
1233
 
    def doctype(self, name, pubid, system):
1234
 
        pass
1235
 
 
1236
 
    ##
1237
 
    # Feeds data to the parser.
1238
 
    #
1239
 
    # @param data Encoded data.
1240
 
 
1241
 
    def feed(self, data):
1242
 
        self._parser.Parse(data, 0)
1243
 
 
1244
 
    ##
1245
 
    # Finishes feeding data to the parser.
1246
 
    #
1247
 
    # @return An element structure.
1248
 
    # @defreturn Element
1249
 
 
1250
 
    def close(self):
1251
 
        self._parser.Parse("", 1) # end of data
1252
 
        tree = self._target.close()
1253
 
        del self._target, self._parser # get rid of circular references
1254
 
        return tree