~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to elementtree/ElementTree.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-09 06:44:53 UTC
  • Revision ID: mbp@sourcefrog.net-20050309064453-60be0ae479d019b8
store committer's timezone in revision and show 
in changelog

Show diffs side-by-side

added added

removed removed

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