Merged 0.51.1 branch
[zeroinstall/zeroinstall-afb.git] / zeroinstall / injector / qdom.py
blob0c15f024fefc4ed72214db6e14f5c224ed304a85
1 """A quick DOM implementation.
3 Python's xml.dom is very slow. The xml.sax module is also slow (as it imports urllib2).
4 This is our light-weight version.
5 """
7 # Copyright (C) 2009, Thomas Leonard
8 # See the README file for details, or visit http://0install.net.
10 from zeroinstall import _
11 from xml.parsers import expat
13 class Element(object):
14 """An XML element.
15 @ivar uri: the element's namespace
16 @type uri: str
17 @ivar name: the element's localName
18 @type name: str
19 @ivar attrs: the element's attributes (key is in the form [namespace " "] localName)
20 @type attrs: {str: str}
21 @ivar childNodes: children
22 @type childNodes: [L{Element}]
23 @ivar content: the text content
24 @type content: str"""
25 __slots__ = ['uri', 'name', 'attrs', 'childNodes', 'content']
26 def __init__(self, uri, name, attrs):
27 self.uri = uri
28 self.name = name
29 self.attrs = attrs.copy()
30 self.content = None
31 self.childNodes = []
33 def __str__(self):
34 attrs = [n + '=' + self.attrs[n] for n in self.attrs]
35 start = '<{%s}%s %s' % (self.uri, self.name, ' '.join(attrs))
36 if self.childNodes:
37 return start + '>' + '\n'.join(map(str, self.childNodes)) + ('</%s>' % (self.name))
38 elif self.content:
39 return start + '>' + self.content + ('</%s>' % (self.name))
40 else:
41 return start + '/>'
43 def getAttribute(self, name):
44 return self.attrs.get(name, None)
46 def toDOM(self, doc, prefixes):
47 """Create a DOM Element for this qdom.Element.
48 @param doc: document to use to create the element
49 @return: the new element
50 """
51 elem = doc.createElementNS(self.uri, self.name)
52 for fullname, value in self.attrs.iteritems():
53 if ' ' in fullname:
54 ns, localName = fullname.split(' ', 1)
55 name = prefixes.get(ns) + ':' + localName
56 else:
57 ns, name = None, fullname
58 elem.setAttributeNS(ns, name, value)
59 for child in self.childNodes:
60 elem.appendChild(child.toDOM(doc, prefixes))
61 if self.content:
62 elem.appendChild(doc.createTextNode(self.content))
63 return elem
65 class QSAXhandler:
66 """SAXHandler that builds a tree of L{Element}s"""
67 def __init__(self):
68 self.stack = []
70 def startElementNS(self, fullname, attrs):
71 split = fullname.split(' ', 1)
72 if len(split) == 2:
73 self.stack.append(Element(split[0], split[1], attrs))
74 else:
75 self.stack.append(Element(None, fullname, attrs))
76 self.contents = ''
78 def characters(self, data):
79 self.contents += data
81 def endElementNS(self, name):
82 contents = self.contents.strip()
83 self.stack[-1].content = contents
84 self.contents = ''
85 new = self.stack.pop()
86 if self.stack:
87 self.stack[-1].childNodes.append(new)
88 else:
89 self.doc = new
91 def parse(source):
92 """Parse an XML stream into a tree of L{Element}s.
93 @param source: data to parse
94 @type source: file
95 @return: the root
96 @rtype: L{Element}"""
97 handler = QSAXhandler()
98 parser = expat.ParserCreate(namespace_separator = ' ')
100 parser.StartElementHandler = handler.startElementNS
101 parser.EndElementHandler = handler.endElementNS
102 parser.CharacterDataHandler = handler.characters
104 parser.ParseFile(source)
105 return handler.doc
107 class Prefixes:
108 """Keep track of namespace prefixes. Used when serialising a document.
109 @since: 0.51
111 def __init__(self):
112 self.prefixes = {}
114 def get(self, ns):
115 prefix = self.prefixes.get(ns, None)
116 if prefix:
117 return prefix
118 prefix = 'ns%d' % len(self.prefixes)
119 self.prefixes[ns] = prefix
120 return prefix