Limit number of concurrent downloads from a single site
[zeroinstall/solver.git] / zeroinstall / injector / qdom.py
blob3c997c07b92a8d1364a4322a38d77363f7f8bd63
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 xml.parsers import expat
12 class Element(object):
13 """An XML element.
14 @ivar uri: the element's namespace
15 @type uri: str
16 @ivar name: the element's localName
17 @type name: str
18 @ivar attrs: the element's attributes (key is in the form [namespace " "] localName)
19 @type attrs: {str: str}
20 @ivar childNodes: children
21 @type childNodes: [L{Element}]
22 @ivar content: the text content
23 @type content: str"""
24 __slots__ = ['uri', 'name', 'attrs', 'childNodes', 'content']
25 def __init__(self, uri, name, attrs):
26 self.uri = uri
27 self.name = name
28 self.attrs = attrs.copy()
29 self.content = None
30 self.childNodes = []
32 def __str__(self):
33 attrs = [n + '=' + self.attrs[n] for n in self.attrs]
34 start = '<{%s}%s %s' % (self.uri, self.name, ' '.join(attrs))
35 if self.childNodes:
36 return start + '>' + '\n'.join(map(str, self.childNodes)) + ('</%s>' % (self.name))
37 elif self.content:
38 return start + '>' + self.content + ('</%s>' % (self.name))
39 else:
40 return start + '/>'
42 def getAttribute(self, name):
43 return self.attrs.get(name, None)
45 def toDOM(self, doc, prefixes):
46 """Create a DOM Element for this qdom.Element.
47 @param doc: document to use to create the element
48 @return: the new element
49 """
50 elem = prefixes.createElementNS(doc, self.uri, self.name)
52 for fullname, value in self.attrs.iteritems():
53 if ' ' in fullname:
54 ns, localName = fullname.split(' ', 1)
55 else:
56 ns, localName = None, fullname
57 prefixes.setAttributeNS(elem, ns, localName, value)
58 for child in self.childNodes:
59 elem.appendChild(child.toDOM(doc, prefixes))
60 if self.content:
61 elem.appendChild(doc.createTextNode(self.content))
62 return elem
64 class QSAXhandler:
65 """SAXHandler that builds a tree of L{Element}s"""
66 def __init__(self):
67 self.stack = []
69 def startElementNS(self, fullname, attrs):
70 split = fullname.split(' ', 1)
71 if len(split) == 2:
72 self.stack.append(Element(split[0], split[1], attrs))
73 else:
74 self.stack.append(Element(None, fullname, attrs))
75 self.contents = ''
77 def characters(self, data):
78 self.contents += data
80 def endElementNS(self, name):
81 contents = self.contents.strip()
82 self.stack[-1].content = contents
83 self.contents = ''
84 new = self.stack.pop()
85 if self.stack:
86 self.stack[-1].childNodes.append(new)
87 else:
88 self.doc = new
90 def parse(source):
91 """Parse an XML stream into a tree of L{Element}s.
92 @param source: data to parse
93 @type source: file
94 @return: the root
95 @rtype: L{Element}"""
96 handler = QSAXhandler()
97 parser = expat.ParserCreate(namespace_separator = ' ')
99 parser.StartElementHandler = handler.startElementNS
100 parser.EndElementHandler = handler.endElementNS
101 parser.CharacterDataHandler = handler.characters
103 parser.ParseFile(source)
104 return handler.doc
106 class Prefixes:
107 """Keep track of namespace prefixes. Used when serialising a document.
108 @since: 0.54
110 def __init__(self, default_ns):
111 self.prefixes = {}
112 self.default_ns = default_ns
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
122 def setAttributeNS(self, elem, uri, localName, value):
123 if uri is None:
124 elem.setAttributeNS(None, localName, value)
125 else:
126 elem.setAttributeNS(uri, self.get(uri) + ':' + localName, value)
128 def createElementNS(self, doc, uri, localName):
129 if uri == self.default_ns:
130 return doc.createElementNS(uri, localName)
131 else:
132 return doc.createElementNS(uri, self.get(uri) + ':' + localName)