~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/microdom.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.web.test.test_xml -*-
 
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Micro Document Object Model: a partial DOM implementation with SUX.
 
7
 
 
8
This is an implementation of what we consider to be the useful subset of the
 
9
DOM.  The chief advantage of this library is that, not being burdened with
 
10
standards compliance, it can remain very stable between versions.  We can also
 
11
implement utility 'pythonic' ways to access and mutate the XML tree.
 
12
 
 
13
Since this has not subjected to a serious trial by fire, it is not recommended
 
14
to use this outside of Twisted applications.  However, it seems to work just
 
15
fine for the documentation generator, which parses a fairly representative
 
16
sample of XML.
 
17
 
 
18
Microdom mainly focuses on working with HTML and XHTML.
 
19
"""
 
20
 
 
21
# System Imports
 
22
import re
 
23
from cStringIO import StringIO
 
24
 
 
25
# create NodeList class
 
26
from types import ListType as NodeList
 
27
from types import StringTypes, UnicodeType
 
28
 
 
29
# Twisted Imports
 
30
from twisted.web.sux import XMLParser, ParseError
 
31
from twisted.python.util import InsensitiveDict
 
32
 
 
33
 
 
34
def getElementsByTagName(iNode, name):
 
35
    """
 
36
    Return a list of all child elements of C{iNode} with a name matching
 
37
    C{name}.
 
38
 
 
39
    Note that this implementation does not conform to the DOM Level 1 Core
 
40
    specification because it may return C{iNode}.
 
41
 
 
42
    @param iNode: An element at which to begin searching.  If C{iNode} has a
 
43
        name matching C{name}, it will be included in the result.
 
44
 
 
45
    @param name: A C{str} giving the name of the elements to return.
 
46
 
 
47
    @return: A C{list} of direct or indirect child elements of C{iNode} with
 
48
        the name C{name}.  This may include C{iNode}.
 
49
    """
 
50
    matches = []
 
51
    matches_append = matches.append # faster lookup. don't do this at home
 
52
    slice = [iNode]
 
53
    while len(slice)>0:
 
54
        c = slice.pop(0)
 
55
        if c.nodeName == name:
 
56
            matches_append(c)
 
57
        slice[:0] = c.childNodes
 
58
    return matches
 
59
 
 
60
 
 
61
 
 
62
def getElementsByTagNameNoCase(iNode, name):
 
63
    name = name.lower()
 
64
    matches = []
 
65
    matches_append = matches.append
 
66
    slice=[iNode]
 
67
    while len(slice)>0:
 
68
        c = slice.pop(0)
 
69
        if c.nodeName.lower() == name:
 
70
            matches_append(c)
 
71
        slice[:0] = c.childNodes
 
72
    return matches
 
73
 
 
74
# order is important
 
75
HTML_ESCAPE_CHARS = (('&', '&'), # don't add any entities before this one
 
76
                    ('<', '&lt;'),
 
77
                    ('>', '&gt;'),
 
78
                    ('"', '&quot;'))
 
79
REV_HTML_ESCAPE_CHARS = list(HTML_ESCAPE_CHARS)
 
80
REV_HTML_ESCAPE_CHARS.reverse()
 
81
 
 
82
XML_ESCAPE_CHARS = HTML_ESCAPE_CHARS + (("'", '&apos;'),)
 
83
REV_XML_ESCAPE_CHARS = list(XML_ESCAPE_CHARS)
 
84
REV_XML_ESCAPE_CHARS.reverse()
 
85
 
 
86
def unescape(text, chars=REV_HTML_ESCAPE_CHARS):
 
87
    "Perform the exact opposite of 'escape'."
 
88
    for s, h in chars:
 
89
        text = text.replace(h, s)
 
90
    return text
 
91
 
 
92
def escape(text, chars=HTML_ESCAPE_CHARS):
 
93
    "Escape a few XML special chars with XML entities."
 
94
    for s, h in chars:
 
95
        text = text.replace(s, h)
 
96
    return text
 
97
 
 
98
 
 
99
class MismatchedTags(Exception):
 
100
 
 
101
    def __init__(self, filename, expect, got, endLine, endCol, begLine, begCol):
 
102
       (self.filename, self.expect, self.got, self.begLine, self.begCol, self.endLine,
 
103
        self.endCol) = filename, expect, got, begLine, begCol, endLine, endCol
 
104
 
 
105
    def __str__(self):
 
106
        return ("expected </%s>, got </%s> line: %s col: %s, began line: %s col: %s"
 
107
                % (self.expect, self.got, self.endLine, self.endCol, self.begLine,
 
108
                   self.begCol))
 
109
 
 
110
 
 
111
class Node(object):
 
112
    nodeName = "Node"
 
113
 
 
114
    def __init__(self, parentNode=None):
 
115
        self.parentNode = parentNode
 
116
        self.childNodes = []
 
117
 
 
118
    def isEqualToNode(self, other):
 
119
        """
 
120
        Compare this node to C{other}.  If the nodes have the same number of
 
121
        children and corresponding children are equal to each other, return
 
122
        C{True}, otherwise return C{False}.
 
123
 
 
124
        @type other: L{Node}
 
125
        @rtype: C{bool}
 
126
        """
 
127
        if len(self.childNodes) != len(other.childNodes):
 
128
            return False
 
129
        for a, b in zip(self.childNodes, other.childNodes):
 
130
            if not a.isEqualToNode(b):
 
131
                return False
 
132
        return True
 
133
 
 
134
    def writexml(self, stream, indent='', addindent='', newl='', strip=0,
 
135
                 nsprefixes={}, namespace=''):
 
136
        raise NotImplementedError()
 
137
 
 
138
    def toxml(self, indent='', addindent='', newl='', strip=0, nsprefixes={},
 
139
              namespace=''):
 
140
        s = StringIO()
 
141
        self.writexml(s, indent, addindent, newl, strip, nsprefixes, namespace)
 
142
        rv = s.getvalue()
 
143
        return rv
 
144
 
 
145
    def writeprettyxml(self, stream, indent='', addindent=' ', newl='\n', strip=0):
 
146
        return self.writexml(stream, indent, addindent, newl, strip)
 
147
 
 
148
    def toprettyxml(self, indent='', addindent=' ', newl='\n', strip=0):
 
149
        return self.toxml(indent, addindent, newl, strip)
 
150
 
 
151
    def cloneNode(self, deep=0, parent=None):
 
152
        raise NotImplementedError()
 
153
 
 
154
    def hasChildNodes(self):
 
155
        if self.childNodes:
 
156
            return 1
 
157
        else:
 
158
            return 0
 
159
 
 
160
 
 
161
    def appendChild(self, child):
 
162
        """
 
163
        Make the given L{Node} the last child of this node.
 
164
 
 
165
        @param child: The L{Node} which will become a child of this node.
 
166
 
 
167
        @raise TypeError: If C{child} is not a C{Node} instance.
 
168
        """
 
169
        if not isinstance(child, Node):
 
170
            raise TypeError("expected Node instance")
 
171
        self.childNodes.append(child)
 
172
        child.parentNode = self
 
173
 
 
174
 
 
175
    def insertBefore(self, new, ref):
 
176
        """
 
177
        Make the given L{Node} C{new} a child of this node which comes before
 
178
        the L{Node} C{ref}.
 
179
 
 
180
        @param new: A L{Node} which will become a child of this node.
 
181
 
 
182
        @param ref: A L{Node} which is already a child of this node which
 
183
            C{new} will be inserted before.
 
184
 
 
185
        @raise TypeError: If C{new} or C{ref} is not a C{Node} instance.
 
186
 
 
187
        @return: C{new}
 
188
        """
 
189
        if not isinstance(new, Node) or not isinstance(ref, Node):
 
190
            raise TypeError("expected Node instance")
 
191
        i = self.childNodes.index(ref)
 
192
        new.parentNode = self
 
193
        self.childNodes.insert(i, new)
 
194
        return new
 
195
 
 
196
 
 
197
    def removeChild(self, child):
 
198
        """
 
199
        Remove the given L{Node} from this node's children.
 
200
 
 
201
        @param child: A L{Node} which is a child of this node which will no
 
202
            longer be a child of this node after this method is called.
 
203
 
 
204
        @raise TypeError: If C{child} is not a C{Node} instance.
 
205
 
 
206
        @return: C{child}
 
207
        """
 
208
        if not isinstance(child, Node):
 
209
            raise TypeError("expected Node instance")
 
210
        if child in self.childNodes:
 
211
            self.childNodes.remove(child)
 
212
            child.parentNode = None
 
213
        return child
 
214
 
 
215
    def replaceChild(self, newChild, oldChild):
 
216
        """
 
217
        Replace a L{Node} which is already a child of this node with a
 
218
        different node.
 
219
 
 
220
        @param newChild: A L{Node} which will be made a child of this node.
 
221
 
 
222
        @param oldChild: A L{Node} which is a child of this node which will
 
223
            give up its position to C{newChild}.
 
224
 
 
225
        @raise TypeError: If C{newChild} or C{oldChild} is not a C{Node}
 
226
            instance.
 
227
 
 
228
        @raise ValueError: If C{oldChild} is not a child of this C{Node}.
 
229
        """
 
230
        if not isinstance(newChild, Node) or not isinstance(oldChild, Node):
 
231
            raise TypeError("expected Node instance")
 
232
        if oldChild.parentNode is not self:
 
233
            raise ValueError("oldChild is not a child of this node")
 
234
        self.childNodes[self.childNodes.index(oldChild)] = newChild
 
235
        oldChild.parentNode = None
 
236
        newChild.parentNode = self
 
237
 
 
238
 
 
239
    def lastChild(self):
 
240
        return self.childNodes[-1]
 
241
 
 
242
 
 
243
    def firstChild(self):
 
244
        if len(self.childNodes):
 
245
            return self.childNodes[0]
 
246
        return None
 
247
 
 
248
    #def get_ownerDocument(self):
 
249
    #   """This doesn't really get the owner document; microdom nodes
 
250
    #   don't even have one necessarily.  This gets the root node,
 
251
    #   which is usually what you really meant.
 
252
    #   *NOT DOM COMPLIANT.*
 
253
    #   """
 
254
    #   node=self
 
255
    #   while (node.parentNode): node=node.parentNode
 
256
    #   return node
 
257
    #ownerDocument=node.get_ownerDocument()
 
258
    # leaving commented for discussion; see also domhelpers.getParents(node)
 
259
 
 
260
class Document(Node):
 
261
 
 
262
    def __init__(self, documentElement=None):
 
263
        Node.__init__(self)
 
264
        if documentElement:
 
265
            self.appendChild(documentElement)
 
266
 
 
267
    def cloneNode(self, deep=0, parent=None):
 
268
        d = Document()
 
269
        d.doctype = self.doctype
 
270
        if deep:
 
271
            newEl = self.documentElement.cloneNode(1, self)
 
272
        else:
 
273
            newEl = self.documentElement
 
274
        d.appendChild(newEl)
 
275
        return d
 
276
 
 
277
    doctype = None
 
278
 
 
279
    def isEqualToDocument(self, n):
 
280
        return (self.doctype == n.doctype) and Node.isEqualToNode(self, n)
 
281
    isEqualToNode = isEqualToDocument
 
282
 
 
283
    def get_documentElement(self):
 
284
        return self.childNodes[0]
 
285
    documentElement=property(get_documentElement)
 
286
 
 
287
    def appendChild(self, child):
 
288
        """
 
289
        Make the given L{Node} the I{document element} of this L{Document}.
 
290
 
 
291
        @param child: The L{Node} to make into this L{Document}'s document
 
292
            element.
 
293
 
 
294
        @raise ValueError: If this document already has a document element.
 
295
        """
 
296
        if self.childNodes:
 
297
            raise ValueError("Only one element per document.")
 
298
        Node.appendChild(self, child)
 
299
 
 
300
    def writexml(self, stream, indent='', addindent='', newl='', strip=0,
 
301
                 nsprefixes={}, namespace=''):
 
302
        stream.write('<?xml version="1.0"?>' + newl)
 
303
        if self.doctype:
 
304
            stream.write("<!DOCTYPE "+self.doctype+">" + newl)
 
305
        self.documentElement.writexml(stream, indent, addindent, newl, strip,
 
306
                                      nsprefixes, namespace)
 
307
 
 
308
    # of dubious utility (?)
 
309
    def createElement(self, name, **kw):
 
310
        return Element(name, **kw)
 
311
 
 
312
    def createTextNode(self, text):
 
313
        return Text(text)
 
314
 
 
315
    def createComment(self, text):
 
316
        return Comment(text)
 
317
 
 
318
    def getElementsByTagName(self, name):
 
319
        if self.documentElement.caseInsensitive:
 
320
            return getElementsByTagNameNoCase(self, name)
 
321
        return getElementsByTagName(self, name)
 
322
 
 
323
    def getElementById(self, id):
 
324
        childNodes = self.childNodes[:]
 
325
        while childNodes:
 
326
            node = childNodes.pop(0)
 
327
            if node.childNodes:
 
328
                childNodes.extend(node.childNodes)
 
329
            if hasattr(node, 'getAttribute') and node.getAttribute("id") == id:
 
330
                return node
 
331
 
 
332
 
 
333
class EntityReference(Node):
 
334
 
 
335
    def __init__(self, eref, parentNode=None):
 
336
        Node.__init__(self, parentNode)
 
337
        self.eref = eref
 
338
        self.nodeValue = self.data = "&" + eref + ";"
 
339
 
 
340
    def isEqualToEntityReference(self, n):
 
341
        if not isinstance(n, EntityReference):
 
342
            return 0
 
343
        return (self.eref == n.eref) and (self.nodeValue == n.nodeValue)
 
344
    isEqualToNode = isEqualToEntityReference
 
345
 
 
346
    def writexml(self, stream, indent='', addindent='', newl='', strip=0,
 
347
                 nsprefixes={}, namespace=''):
 
348
        stream.write(self.nodeValue)
 
349
 
 
350
    def cloneNode(self, deep=0, parent=None):
 
351
        return EntityReference(self.eref, parent)
 
352
 
 
353
 
 
354
class CharacterData(Node):
 
355
 
 
356
    def __init__(self, data, parentNode=None):
 
357
        Node.__init__(self, parentNode)
 
358
        self.value = self.data = self.nodeValue = data
 
359
 
 
360
    def isEqualToCharacterData(self, n):
 
361
        return self.value == n.value
 
362
    isEqualToNode = isEqualToCharacterData
 
363
 
 
364
 
 
365
class Comment(CharacterData):
 
366
    """A comment node."""
 
367
 
 
368
    def writexml(self, stream, indent='', addindent='', newl='', strip=0,
 
369
                 nsprefixes={}, namespace=''):
 
370
        val=self.data
 
371
        if isinstance(val, UnicodeType):
 
372
            val=val.encode('utf8')
 
373
        stream.write("<!--%s-->" % val)
 
374
 
 
375
    def cloneNode(self, deep=0, parent=None):
 
376
        return Comment(self.nodeValue, parent)
 
377
 
 
378
 
 
379
class Text(CharacterData):
 
380
 
 
381
    def __init__(self, data, parentNode=None, raw=0):
 
382
        CharacterData.__init__(self, data, parentNode)
 
383
        self.raw = raw
 
384
 
 
385
 
 
386
    def isEqualToNode(self, other):
 
387
        """
 
388
        Compare this text to C{text}.  If the underlying values and the C{raw}
 
389
        flag are the same, return C{True}, otherwise return C{False}.
 
390
        """
 
391
        return (
 
392
            CharacterData.isEqualToNode(self, other) and
 
393
            self.raw == other.raw)
 
394
 
 
395
 
 
396
    def cloneNode(self, deep=0, parent=None):
 
397
        return Text(self.nodeValue, parent, self.raw)
 
398
 
 
399
    def writexml(self, stream, indent='', addindent='', newl='', strip=0,
 
400
                 nsprefixes={}, namespace=''):
 
401
        if self.raw:
 
402
            val = self.nodeValue
 
403
            if not isinstance(val, StringTypes):
 
404
                val = str(self.nodeValue)
 
405
        else:
 
406
            v = self.nodeValue
 
407
            if not isinstance(v, StringTypes):
 
408
                v = str(v)
 
409
            if strip:
 
410
                v = ' '.join(v.split())
 
411
            val = escape(v)
 
412
        if isinstance(val, UnicodeType):
 
413
            val = val.encode('utf8')
 
414
        stream.write(val)
 
415
 
 
416
    def __repr__(self):
 
417
        return "Text(%s" % repr(self.nodeValue) + ')'
 
418
 
 
419
 
 
420
class CDATASection(CharacterData):
 
421
    def cloneNode(self, deep=0, parent=None):
 
422
        return CDATASection(self.nodeValue, parent)
 
423
 
 
424
    def writexml(self, stream, indent='', addindent='', newl='', strip=0,
 
425
                 nsprefixes={}, namespace=''):
 
426
        stream.write("<![CDATA[")
 
427
        stream.write(self.nodeValue)
 
428
        stream.write("]]>")
 
429
 
 
430
def _genprefix():
 
431
    i = 0
 
432
    while True:
 
433
        yield  'p' + str(i)
 
434
        i = i + 1
 
435
genprefix = _genprefix().next
 
436
 
 
437
class _Attr(CharacterData):
 
438
    "Support class for getAttributeNode."
 
439
 
 
440
class Element(Node):
 
441
 
 
442
    preserveCase = 0
 
443
    caseInsensitive = 1
 
444
    nsprefixes = None
 
445
 
 
446
    def __init__(self, tagName, attributes=None, parentNode=None,
 
447
                 filename=None, markpos=None,
 
448
                 caseInsensitive=1, preserveCase=0,
 
449
                 namespace=None):
 
450
        Node.__init__(self, parentNode)
 
451
        self.preserveCase = preserveCase or not caseInsensitive
 
452
        self.caseInsensitive = caseInsensitive
 
453
        if not preserveCase:
 
454
            tagName = tagName.lower()
 
455
        if attributes is None:
 
456
            self.attributes = {}
 
457
        else:
 
458
            self.attributes = attributes
 
459
            for k, v in self.attributes.items():
 
460
                self.attributes[k] = unescape(v)
 
461
 
 
462
        if caseInsensitive:
 
463
            self.attributes = InsensitiveDict(self.attributes,
 
464
                                              preserve=preserveCase)
 
465
 
 
466
        self.endTagName = self.nodeName = self.tagName = tagName
 
467
        self._filename = filename
 
468
        self._markpos = markpos
 
469
        self.namespace = namespace
 
470
 
 
471
    def addPrefixes(self, pfxs):
 
472
        if self.nsprefixes is None:
 
473
            self.nsprefixes = pfxs
 
474
        else:
 
475
            self.nsprefixes.update(pfxs)
 
476
 
 
477
    def endTag(self, endTagName):
 
478
        if not self.preserveCase:
 
479
            endTagName = endTagName.lower()
 
480
        self.endTagName = endTagName
 
481
 
 
482
    def isEqualToElement(self, n):
 
483
        if self.caseInsensitive:
 
484
            return ((self.attributes == n.attributes)
 
485
                    and (self.nodeName.lower() == n.nodeName.lower()))
 
486
        return (self.attributes == n.attributes) and (self.nodeName == n.nodeName)
 
487
 
 
488
 
 
489
    def isEqualToNode(self, other):
 
490
        """
 
491
        Compare this element to C{other}.  If the C{nodeName}, C{namespace},
 
492
        C{attributes}, and C{childNodes} are all the same, return C{True},
 
493
        otherwise return C{False}.
 
494
        """
 
495
        return (
 
496
            self.nodeName.lower() == other.nodeName.lower() and
 
497
            self.namespace == other.namespace and
 
498
            self.attributes == other.attributes and
 
499
            Node.isEqualToNode(self, other))
 
500
 
 
501
 
 
502
    def cloneNode(self, deep=0, parent=None):
 
503
        clone = Element(
 
504
            self.tagName, parentNode=parent, namespace=self.namespace,
 
505
            preserveCase=self.preserveCase, caseInsensitive=self.caseInsensitive)
 
506
        clone.attributes.update(self.attributes)
 
507
        if deep:
 
508
            clone.childNodes = [child.cloneNode(1, clone) for child in self.childNodes]
 
509
        else:
 
510
            clone.childNodes = []
 
511
        return clone
 
512
 
 
513
    def getElementsByTagName(self, name):
 
514
        if self.caseInsensitive:
 
515
            return getElementsByTagNameNoCase(self, name)
 
516
        return getElementsByTagName(self, name)
 
517
 
 
518
    def hasAttributes(self):
 
519
        return 1
 
520
 
 
521
    def getAttribute(self, name, default=None):
 
522
        return self.attributes.get(name, default)
 
523
 
 
524
    def getAttributeNS(self, ns, name, default=None):
 
525
        nsk = (ns, name)
 
526
        if self.attributes.has_key(nsk):
 
527
            return self.attributes[nsk]
 
528
        if ns == self.namespace:
 
529
            return self.attributes.get(name, default)
 
530
        return default
 
531
 
 
532
    def getAttributeNode(self, name):
 
533
        return _Attr(self.getAttribute(name), self)
 
534
 
 
535
    def setAttribute(self, name, attr):
 
536
        self.attributes[name] = attr
 
537
 
 
538
    def removeAttribute(self, name):
 
539
        if name in self.attributes:
 
540
            del self.attributes[name]
 
541
 
 
542
    def hasAttribute(self, name):
 
543
        return name in self.attributes
 
544
 
 
545
 
 
546
    def writexml(self, stream, indent='', addindent='', newl='', strip=0,
 
547
                 nsprefixes={}, namespace=''):
 
548
        """
 
549
        Serialize this L{Element} to the given stream.
 
550
 
 
551
        @param stream: A file-like object to which this L{Element} will be
 
552
            written.
 
553
 
 
554
        @param nsprefixes: A C{dict} mapping namespace URIs as C{str} to
 
555
            prefixes as C{str}.  This defines the prefixes which are already in
 
556
            scope in the document at the point at which this L{Element} exists.
 
557
            This is essentially an implementation detail for namespace support.
 
558
            Applications should not try to use it.
 
559
 
 
560
        @param namespace: The namespace URI as a C{str} which is the default at
 
561
            the point in the document at which this L{Element} exists.  This is
 
562
            essentially an implementation detail for namespace support.
 
563
            Applications should not try to use it.
 
564
        """
 
565
        # write beginning
 
566
        ALLOWSINGLETON = ('img', 'br', 'hr', 'base', 'meta', 'link', 'param',
 
567
                          'area', 'input', 'col', 'basefont', 'isindex',
 
568
                          'frame')
 
569
        BLOCKELEMENTS = ('html', 'head', 'body', 'noscript', 'ins', 'del',
 
570
                         'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'script',
 
571
                         'ul', 'ol', 'dl', 'pre', 'hr', 'blockquote',
 
572
                         'address', 'p', 'div', 'fieldset', 'table', 'tr',
 
573
                         'form', 'object', 'fieldset', 'applet', 'map')
 
574
        FORMATNICELY = ('tr', 'ul', 'ol', 'head')
 
575
 
 
576
        # this should never be necessary unless people start
 
577
        # changing .tagName on the fly(?)
 
578
        if not self.preserveCase:
 
579
            self.endTagName = self.tagName
 
580
        w = stream.write
 
581
        if self.nsprefixes:
 
582
            newprefixes = self.nsprefixes.copy()
 
583
            for ns in nsprefixes.keys():
 
584
                if ns in newprefixes:
 
585
                    del newprefixes[ns]
 
586
        else:
 
587
             newprefixes = {}
 
588
 
 
589
        begin = ['<']
 
590
        if self.tagName in BLOCKELEMENTS:
 
591
            begin = [newl, indent] + begin
 
592
        bext = begin.extend
 
593
        writeattr = lambda _atr, _val: bext((' ', _atr, '="', escape(_val), '"'))
 
594
 
 
595
        # Make a local for tracking what end tag will be used.  If namespace
 
596
        # prefixes are involved, this will be changed to account for that
 
597
        # before it's actually used.
 
598
        endTagName = self.endTagName
 
599
 
 
600
        if namespace != self.namespace and self.namespace is not None:
 
601
            # If the current default namespace is not the namespace of this tag
 
602
            # (and this tag has a namespace at all) then we'll write out
 
603
            # something related to namespaces.
 
604
            if self.namespace in nsprefixes:
 
605
                # This tag's namespace already has a prefix bound to it.  Use
 
606
                # that prefix.
 
607
                prefix = nsprefixes[self.namespace]
 
608
                bext(prefix + ':' + self.tagName)
 
609
                # Also make sure we use it for the end tag.
 
610
                endTagName = prefix + ':' + self.endTagName
 
611
            else:
 
612
                # This tag's namespace has no prefix bound to it.  Change the
 
613
                # default namespace to this tag's namespace so we don't need
 
614
                # prefixes.  Alternatively, we could add a new prefix binding.
 
615
                # I'm not sure why the code was written one way rather than the
 
616
                # other. -exarkun
 
617
                bext(self.tagName)
 
618
                writeattr("xmlns", self.namespace)
 
619
                # The default namespace just changed.  Make sure any children
 
620
                # know about this.
 
621
                namespace = self.namespace
 
622
        else:
 
623
            # This tag has no namespace or its namespace is already the default
 
624
            # namespace.  Nothing extra to do here.
 
625
            bext(self.tagName)
 
626
 
 
627
        j = ''.join
 
628
        for attr, val in self.attributes.iteritems():
 
629
            if isinstance(attr, tuple):
 
630
                ns, key = attr
 
631
                if nsprefixes.has_key(ns):
 
632
                    prefix = nsprefixes[ns]
 
633
                else:
 
634
                    prefix = genprefix()
 
635
                    newprefixes[ns] = prefix
 
636
                assert val is not None
 
637
                writeattr(prefix+':'+key,val)
 
638
            else:
 
639
                assert val is not None
 
640
                writeattr(attr, val)
 
641
        if newprefixes:
 
642
            for ns, prefix in newprefixes.iteritems():
 
643
                if prefix:
 
644
                    writeattr('xmlns:'+prefix, ns)
 
645
            newprefixes.update(nsprefixes)
 
646
            downprefixes = newprefixes
 
647
        else:
 
648
            downprefixes = nsprefixes
 
649
        w(j(begin))
 
650
        if self.childNodes:
 
651
            w(">")
 
652
            newindent = indent + addindent
 
653
            for child in self.childNodes:
 
654
                if self.tagName in BLOCKELEMENTS and \
 
655
                   self.tagName in FORMATNICELY:
 
656
                    w(j((newl, newindent)))
 
657
                child.writexml(stream, newindent, addindent, newl, strip,
 
658
                               downprefixes, namespace)
 
659
            if self.tagName in BLOCKELEMENTS:
 
660
                w(j((newl, indent)))
 
661
            w(j(('</', endTagName, '>')))
 
662
        elif self.tagName.lower() not in ALLOWSINGLETON:
 
663
            w(j(('></', endTagName, '>')))
 
664
        else:
 
665
            w(" />")
 
666
 
 
667
 
 
668
    def __repr__(self):
 
669
        rep = "Element(%s" % repr(self.nodeName)
 
670
        if self.attributes:
 
671
            rep += ", attributes=%r" % (self.attributes,)
 
672
        if self._filename:
 
673
            rep += ", filename=%r" % (self._filename,)
 
674
        if self._markpos:
 
675
            rep += ", markpos=%r" % (self._markpos,)
 
676
        return rep + ')'
 
677
 
 
678
    def __str__(self):
 
679
        rep = "<" + self.nodeName
 
680
        if self._filename or self._markpos:
 
681
            rep += " ("
 
682
        if self._filename:
 
683
            rep += repr(self._filename)
 
684
        if self._markpos:
 
685
            rep += " line %s column %s" % self._markpos
 
686
        if self._filename or self._markpos:
 
687
            rep += ")"
 
688
        for item in self.attributes.items():
 
689
            rep += " %s=%r" % item
 
690
        if self.hasChildNodes():
 
691
            rep += " >...</%s>" % self.nodeName
 
692
        else:
 
693
            rep += " />"
 
694
        return rep
 
695
 
 
696
def _unescapeDict(d):
 
697
    dd = {}
 
698
    for k, v in d.items():
 
699
        dd[k] = unescape(v)
 
700
    return dd
 
701
 
 
702
def _reverseDict(d):
 
703
    dd = {}
 
704
    for k, v in d.items():
 
705
        dd[v]=k
 
706
    return dd
 
707
 
 
708
class MicroDOMParser(XMLParser):
 
709
 
 
710
    # <dash> glyph: a quick scan thru the DTD says BODY, AREA, LINK, IMG, HR,
 
711
    # P, DT, DD, LI, INPUT, OPTION, THEAD, TFOOT, TBODY, COLGROUP, COL, TR, TH,
 
712
    # TD, HEAD, BASE, META, HTML all have optional closing tags
 
713
 
 
714
    soonClosers = 'area link br img hr input base meta'.split()
 
715
    laterClosers = {'p': ['p', 'dt'],
 
716
                    'dt': ['dt','dd'],
 
717
                    'dd': ['dt', 'dd'],
 
718
                    'li': ['li'],
 
719
                    'tbody': ['thead', 'tfoot', 'tbody'],
 
720
                    'thead': ['thead', 'tfoot', 'tbody'],
 
721
                    'tfoot': ['thead', 'tfoot', 'tbody'],
 
722
                    'colgroup': ['colgroup'],
 
723
                    'col': ['col'],
 
724
                    'tr': ['tr'],
 
725
                    'td': ['td'],
 
726
                    'th': ['th'],
 
727
                    'head': ['body'],
 
728
                    'title': ['head', 'body'], # this looks wrong...
 
729
                    'option': ['option'],
 
730
                    }
 
731
 
 
732
 
 
733
    def __init__(self, beExtremelyLenient=0, caseInsensitive=1, preserveCase=0,
 
734
                 soonClosers=soonClosers, laterClosers=laterClosers):
 
735
        self.elementstack = []
 
736
        d = {'xmlns': 'xmlns', '': None}
 
737
        dr = _reverseDict(d)
 
738
        self.nsstack = [(d,None,dr)]
 
739
        self.documents = []
 
740
        self._mddoctype = None
 
741
        self.beExtremelyLenient = beExtremelyLenient
 
742
        self.caseInsensitive = caseInsensitive
 
743
        self.preserveCase = preserveCase or not caseInsensitive
 
744
        self.soonClosers = soonClosers
 
745
        self.laterClosers = laterClosers
 
746
        # self.indentlevel = 0
 
747
 
 
748
    def shouldPreserveSpace(self):
 
749
        for edx in xrange(len(self.elementstack)):
 
750
            el = self.elementstack[-edx]
 
751
            if el.tagName == 'pre' or el.getAttribute("xml:space", '') == 'preserve':
 
752
                return 1
 
753
        return 0
 
754
 
 
755
    def _getparent(self):
 
756
        if self.elementstack:
 
757
            return self.elementstack[-1]
 
758
        else:
 
759
            return None
 
760
 
 
761
    COMMENT = re.compile(r"\s*/[/*]\s*")
 
762
 
 
763
    def _fixScriptElement(self, el):
 
764
        # this deals with case where there is comment or CDATA inside
 
765
        # <script> tag and we want to do the right thing with it
 
766
        if not self.beExtremelyLenient or not len(el.childNodes) == 1:
 
767
            return
 
768
        c = el.firstChild()
 
769
        if isinstance(c, Text):
 
770
            # deal with nasty people who do stuff like:
 
771
            #   <script> // <!--
 
772
            #      x = 1;
 
773
            #   // --></script>
 
774
            # tidy does this, for example.
 
775
            prefix = ""
 
776
            oldvalue = c.value
 
777
            match = self.COMMENT.match(oldvalue)
 
778
            if match:
 
779
                prefix = match.group()
 
780
                oldvalue = oldvalue[len(prefix):]
 
781
 
 
782
            # now see if contents are actual node and comment or CDATA
 
783
            try:
 
784
                e = parseString("<a>%s</a>" % oldvalue).childNodes[0]
 
785
            except (ParseError, MismatchedTags):
 
786
                return
 
787
            if len(e.childNodes) != 1:
 
788
                return
 
789
            e = e.firstChild()
 
790
            if isinstance(e, (CDATASection, Comment)):
 
791
                el.childNodes = []
 
792
                if prefix:
 
793
                    el.childNodes.append(Text(prefix))
 
794
                el.childNodes.append(e)
 
795
 
 
796
    def gotDoctype(self, doctype):
 
797
        self._mddoctype = doctype
 
798
 
 
799
    def gotTagStart(self, name, attributes):
 
800
        # print ' '*self.indentlevel, 'start tag',name
 
801
        # self.indentlevel += 1
 
802
        parent = self._getparent()
 
803
        if (self.beExtremelyLenient and isinstance(parent, Element)):
 
804
            parentName = parent.tagName
 
805
            myName = name
 
806
            if self.caseInsensitive:
 
807
                parentName = parentName.lower()
 
808
                myName = myName.lower()
 
809
            if myName in self.laterClosers.get(parentName, []):
 
810
                self.gotTagEnd(parent.tagName)
 
811
                parent = self._getparent()
 
812
        attributes = _unescapeDict(attributes)
 
813
        namespaces = self.nsstack[-1][0]
 
814
        newspaces = {}
 
815
        for k, v in attributes.items():
 
816
            if k.startswith('xmlns'):
 
817
                spacenames = k.split(':',1)
 
818
                if len(spacenames) == 2:
 
819
                    newspaces[spacenames[1]] = v
 
820
                else:
 
821
                    newspaces[''] = v
 
822
                del attributes[k]
 
823
        if newspaces:
 
824
            namespaces = namespaces.copy()
 
825
            namespaces.update(newspaces)
 
826
        for k, v in attributes.items():
 
827
            ksplit = k.split(':', 1)
 
828
            if len(ksplit) == 2:
 
829
                pfx, tv = ksplit
 
830
                if pfx != 'xml' and namespaces.has_key(pfx):
 
831
                    attributes[namespaces[pfx], tv] = v
 
832
                    del attributes[k]
 
833
        el = Element(name, attributes, parent,
 
834
                     self.filename, self.saveMark(),
 
835
                     caseInsensitive=self.caseInsensitive,
 
836
                     preserveCase=self.preserveCase,
 
837
                     namespace=namespaces.get(''))
 
838
        revspaces = _reverseDict(newspaces)
 
839
        el.addPrefixes(revspaces)
 
840
 
 
841
        if newspaces:
 
842
            rscopy = self.nsstack[-1][2].copy()
 
843
            rscopy.update(revspaces)
 
844
            self.nsstack.append((namespaces, el, rscopy))
 
845
        self.elementstack.append(el)
 
846
        if parent:
 
847
            parent.appendChild(el)
 
848
        if (self.beExtremelyLenient and el.tagName in self.soonClosers):
 
849
            self.gotTagEnd(name)
 
850
 
 
851
    def _gotStandalone(self, factory, data):
 
852
        parent = self._getparent()
 
853
        te = factory(data, parent)
 
854
        if parent:
 
855
            parent.appendChild(te)
 
856
        elif self.beExtremelyLenient:
 
857
            self.documents.append(te)
 
858
 
 
859
    def gotText(self, data):
 
860
        if data.strip() or self.shouldPreserveSpace():
 
861
            self._gotStandalone(Text, data)
 
862
 
 
863
    def gotComment(self, data):
 
864
        self._gotStandalone(Comment, data)
 
865
 
 
866
    def gotEntityReference(self, entityRef):
 
867
        self._gotStandalone(EntityReference, entityRef)
 
868
 
 
869
    def gotCData(self, cdata):
 
870
        self._gotStandalone(CDATASection, cdata)
 
871
 
 
872
    def gotTagEnd(self, name):
 
873
        # print ' '*self.indentlevel, 'end tag',name
 
874
        # self.indentlevel -= 1
 
875
        if not self.elementstack:
 
876
            if self.beExtremelyLenient:
 
877
                return
 
878
            raise MismatchedTags(*((self.filename, "NOTHING", name)
 
879
                                   +self.saveMark()+(0,0)))
 
880
        el = self.elementstack.pop()
 
881
        pfxdix = self.nsstack[-1][2]
 
882
        if self.nsstack[-1][1] is el:
 
883
            nstuple = self.nsstack.pop()
 
884
        else:
 
885
            nstuple = None
 
886
        if self.caseInsensitive:
 
887
            tn = el.tagName.lower()
 
888
            cname = name.lower()
 
889
        else:
 
890
            tn = el.tagName
 
891
            cname = name
 
892
 
 
893
        nsplit = name.split(':',1)
 
894
        if len(nsplit) == 2:
 
895
            pfx, newname = nsplit
 
896
            ns = pfxdix.get(pfx,None)
 
897
            if ns is not None:
 
898
                if el.namespace != ns:
 
899
                    if not self.beExtremelyLenient:
 
900
                        raise MismatchedTags(*((self.filename, el.tagName, name)
 
901
                                               +self.saveMark()+el._markpos))
 
902
        if not (tn == cname):
 
903
            if self.beExtremelyLenient:
 
904
                if self.elementstack:
 
905
                    lastEl = self.elementstack[0]
 
906
                    for idx in xrange(len(self.elementstack)):
 
907
                        if self.elementstack[-(idx+1)].tagName == cname:
 
908
                            self.elementstack[-(idx+1)].endTag(name)
 
909
                            break
 
910
                    else:
 
911
                        # this was a garbage close tag; wait for a real one
 
912
                        self.elementstack.append(el)
 
913
                        if nstuple is not None:
 
914
                            self.nsstack.append(nstuple)
 
915
                        return
 
916
                    del self.elementstack[-(idx+1):]
 
917
                    if not self.elementstack:
 
918
                        self.documents.append(lastEl)
 
919
                        return
 
920
            else:
 
921
                raise MismatchedTags(*((self.filename, el.tagName, name)
 
922
                                       +self.saveMark()+el._markpos))
 
923
        el.endTag(name)
 
924
        if not self.elementstack:
 
925
            self.documents.append(el)
 
926
        if self.beExtremelyLenient and el.tagName == "script":
 
927
            self._fixScriptElement(el)
 
928
 
 
929
    def connectionLost(self, reason):
 
930
        XMLParser.connectionLost(self, reason) # This can cause more events!
 
931
        if self.elementstack:
 
932
            if self.beExtremelyLenient:
 
933
                self.documents.append(self.elementstack[0])
 
934
            else:
 
935
                raise MismatchedTags(*((self.filename, self.elementstack[-1],
 
936
                                        "END_OF_FILE")
 
937
                                       +self.saveMark()
 
938
                                       +self.elementstack[-1]._markpos))
 
939
 
 
940
 
 
941
def parse(readable, *args, **kwargs):
 
942
    """Parse HTML or XML readable."""
 
943
    if not hasattr(readable, "read"):
 
944
        readable = open(readable, "rb")
 
945
    mdp = MicroDOMParser(*args, **kwargs)
 
946
    mdp.filename = getattr(readable, "name", "<xmlfile />")
 
947
    mdp.makeConnection(None)
 
948
    if hasattr(readable,"getvalue"):
 
949
        mdp.dataReceived(readable.getvalue())
 
950
    else:
 
951
        r = readable.read(1024)
 
952
        while r:
 
953
            mdp.dataReceived(r)
 
954
            r = readable.read(1024)
 
955
    mdp.connectionLost(None)
 
956
 
 
957
    if not mdp.documents:
 
958
        raise ParseError(mdp.filename, 0, 0, "No top-level Nodes in document")
 
959
 
 
960
    if mdp.beExtremelyLenient:
 
961
        if len(mdp.documents) == 1:
 
962
            d = mdp.documents[0]
 
963
            if not isinstance(d, Element):
 
964
                el = Element("html")
 
965
                el.appendChild(d)
 
966
                d = el
 
967
        else:
 
968
            d = Element("html")
 
969
            for child in mdp.documents:
 
970
                d.appendChild(child)
 
971
    else:
 
972
        d = mdp.documents[0]
 
973
    doc = Document(d)
 
974
    doc.doctype = mdp._mddoctype
 
975
    return doc
 
976
 
 
977
def parseString(st, *args, **kw):
 
978
    if isinstance(st, UnicodeType):
 
979
        # this isn't particularly ideal, but it does work.
 
980
        return parse(StringIO(st.encode('UTF-16')), *args, **kw)
 
981
    return parse(StringIO(st), *args, **kw)
 
982
 
 
983
 
 
984
def parseXML(readable):
 
985
    """Parse an XML readable object."""
 
986
    return parse(readable, caseInsensitive=0, preserveCase=1)
 
987
 
 
988
 
 
989
def parseXMLString(st):
 
990
    """Parse an XML readable object."""
 
991
    return parseString(st, caseInsensitive=0, preserveCase=1)
 
992
 
 
993
 
 
994
# Utility
 
995
 
 
996
class lmx:
 
997
    """Easy creation of XML."""
 
998
 
 
999
    def __init__(self, node='div'):
 
1000
        if isinstance(node, StringTypes):
 
1001
            node = Element(node)
 
1002
        self.node = node
 
1003
 
 
1004
    def __getattr__(self, name):
 
1005
        if name[0] == '_':
 
1006
            raise AttributeError("no private attrs")
 
1007
        return lambda **kw: self.add(name,**kw)
 
1008
 
 
1009
    def __setitem__(self, key, val):
 
1010
        self.node.setAttribute(key, val)
 
1011
 
 
1012
    def __getitem__(self, key):
 
1013
        return self.node.getAttribute(key)
 
1014
 
 
1015
    def text(self, txt, raw=0):
 
1016
        nn = Text(txt, raw=raw)
 
1017
        self.node.appendChild(nn)
 
1018
        return self
 
1019
 
 
1020
    def add(self, tagName, **kw):
 
1021
        newNode = Element(tagName, caseInsensitive=0, preserveCase=0)
 
1022
        self.node.appendChild(newNode)
 
1023
        xf = lmx(newNode)
 
1024
        for k, v in kw.items():
 
1025
            if k[0] == '_':
 
1026
                k = k[1:]
 
1027
            xf[k]=v
 
1028
        return xf