Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / web / microdom.py
1 # -*- test-case-name: twisted.web.test.test_xml -*-
2 # Copyright (c) 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