jpayne@68
|
1 """Facility to use the Expat parser to load a minidom instance
|
jpayne@68
|
2 from a string or file.
|
jpayne@68
|
3
|
jpayne@68
|
4 This avoids all the overhead of SAX and pulldom to gain performance.
|
jpayne@68
|
5 """
|
jpayne@68
|
6
|
jpayne@68
|
7 # Warning!
|
jpayne@68
|
8 #
|
jpayne@68
|
9 # This module is tightly bound to the implementation details of the
|
jpayne@68
|
10 # minidom DOM and can't be used with other DOM implementations. This
|
jpayne@68
|
11 # is due, in part, to a lack of appropriate methods in the DOM (there is
|
jpayne@68
|
12 # no way to create Entity and Notation nodes via the DOM Level 2
|
jpayne@68
|
13 # interface), and for performance. The latter is the cause of some fairly
|
jpayne@68
|
14 # cryptic code.
|
jpayne@68
|
15 #
|
jpayne@68
|
16 # Performance hacks:
|
jpayne@68
|
17 #
|
jpayne@68
|
18 # - .character_data_handler() has an extra case in which continuing
|
jpayne@68
|
19 # data is appended to an existing Text node; this can be a
|
jpayne@68
|
20 # speedup since pyexpat can break up character data into multiple
|
jpayne@68
|
21 # callbacks even though we set the buffer_text attribute on the
|
jpayne@68
|
22 # parser. This also gives us the advantage that we don't need a
|
jpayne@68
|
23 # separate normalization pass.
|
jpayne@68
|
24 #
|
jpayne@68
|
25 # - Determining that a node exists is done using an identity comparison
|
jpayne@68
|
26 # with None rather than a truth test; this avoids searching for and
|
jpayne@68
|
27 # calling any methods on the node object if it exists. (A rather
|
jpayne@68
|
28 # nice speedup is achieved this way as well!)
|
jpayne@68
|
29
|
jpayne@68
|
30 from xml.dom import xmlbuilder, minidom, Node
|
jpayne@68
|
31 from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE
|
jpayne@68
|
32 from xml.parsers import expat
|
jpayne@68
|
33 from xml.dom.minidom import _append_child, _set_attribute_node
|
jpayne@68
|
34 from xml.dom.NodeFilter import NodeFilter
|
jpayne@68
|
35
|
jpayne@68
|
36 TEXT_NODE = Node.TEXT_NODE
|
jpayne@68
|
37 CDATA_SECTION_NODE = Node.CDATA_SECTION_NODE
|
jpayne@68
|
38 DOCUMENT_NODE = Node.DOCUMENT_NODE
|
jpayne@68
|
39
|
jpayne@68
|
40 FILTER_ACCEPT = xmlbuilder.DOMBuilderFilter.FILTER_ACCEPT
|
jpayne@68
|
41 FILTER_REJECT = xmlbuilder.DOMBuilderFilter.FILTER_REJECT
|
jpayne@68
|
42 FILTER_SKIP = xmlbuilder.DOMBuilderFilter.FILTER_SKIP
|
jpayne@68
|
43 FILTER_INTERRUPT = xmlbuilder.DOMBuilderFilter.FILTER_INTERRUPT
|
jpayne@68
|
44
|
jpayne@68
|
45 theDOMImplementation = minidom.getDOMImplementation()
|
jpayne@68
|
46
|
jpayne@68
|
47 # Expat typename -> TypeInfo
|
jpayne@68
|
48 _typeinfo_map = {
|
jpayne@68
|
49 "CDATA": minidom.TypeInfo(None, "cdata"),
|
jpayne@68
|
50 "ENUM": minidom.TypeInfo(None, "enumeration"),
|
jpayne@68
|
51 "ENTITY": minidom.TypeInfo(None, "entity"),
|
jpayne@68
|
52 "ENTITIES": minidom.TypeInfo(None, "entities"),
|
jpayne@68
|
53 "ID": minidom.TypeInfo(None, "id"),
|
jpayne@68
|
54 "IDREF": minidom.TypeInfo(None, "idref"),
|
jpayne@68
|
55 "IDREFS": minidom.TypeInfo(None, "idrefs"),
|
jpayne@68
|
56 "NMTOKEN": minidom.TypeInfo(None, "nmtoken"),
|
jpayne@68
|
57 "NMTOKENS": minidom.TypeInfo(None, "nmtokens"),
|
jpayne@68
|
58 }
|
jpayne@68
|
59
|
jpayne@68
|
60 class ElementInfo(object):
|
jpayne@68
|
61 __slots__ = '_attr_info', '_model', 'tagName'
|
jpayne@68
|
62
|
jpayne@68
|
63 def __init__(self, tagName, model=None):
|
jpayne@68
|
64 self.tagName = tagName
|
jpayne@68
|
65 self._attr_info = []
|
jpayne@68
|
66 self._model = model
|
jpayne@68
|
67
|
jpayne@68
|
68 def __getstate__(self):
|
jpayne@68
|
69 return self._attr_info, self._model, self.tagName
|
jpayne@68
|
70
|
jpayne@68
|
71 def __setstate__(self, state):
|
jpayne@68
|
72 self._attr_info, self._model, self.tagName = state
|
jpayne@68
|
73
|
jpayne@68
|
74 def getAttributeType(self, aname):
|
jpayne@68
|
75 for info in self._attr_info:
|
jpayne@68
|
76 if info[1] == aname:
|
jpayne@68
|
77 t = info[-2]
|
jpayne@68
|
78 if t[0] == "(":
|
jpayne@68
|
79 return _typeinfo_map["ENUM"]
|
jpayne@68
|
80 else:
|
jpayne@68
|
81 return _typeinfo_map[info[-2]]
|
jpayne@68
|
82 return minidom._no_type
|
jpayne@68
|
83
|
jpayne@68
|
84 def getAttributeTypeNS(self, namespaceURI, localName):
|
jpayne@68
|
85 return minidom._no_type
|
jpayne@68
|
86
|
jpayne@68
|
87 def isElementContent(self):
|
jpayne@68
|
88 if self._model:
|
jpayne@68
|
89 type = self._model[0]
|
jpayne@68
|
90 return type not in (expat.model.XML_CTYPE_ANY,
|
jpayne@68
|
91 expat.model.XML_CTYPE_MIXED)
|
jpayne@68
|
92 else:
|
jpayne@68
|
93 return False
|
jpayne@68
|
94
|
jpayne@68
|
95 def isEmpty(self):
|
jpayne@68
|
96 if self._model:
|
jpayne@68
|
97 return self._model[0] == expat.model.XML_CTYPE_EMPTY
|
jpayne@68
|
98 else:
|
jpayne@68
|
99 return False
|
jpayne@68
|
100
|
jpayne@68
|
101 def isId(self, aname):
|
jpayne@68
|
102 for info in self._attr_info:
|
jpayne@68
|
103 if info[1] == aname:
|
jpayne@68
|
104 return info[-2] == "ID"
|
jpayne@68
|
105 return False
|
jpayne@68
|
106
|
jpayne@68
|
107 def isIdNS(self, euri, ename, auri, aname):
|
jpayne@68
|
108 # not sure this is meaningful
|
jpayne@68
|
109 return self.isId((auri, aname))
|
jpayne@68
|
110
|
jpayne@68
|
111 def _intern(builder, s):
|
jpayne@68
|
112 return builder._intern_setdefault(s, s)
|
jpayne@68
|
113
|
jpayne@68
|
114 def _parse_ns_name(builder, name):
|
jpayne@68
|
115 assert ' ' in name
|
jpayne@68
|
116 parts = name.split(' ')
|
jpayne@68
|
117 intern = builder._intern_setdefault
|
jpayne@68
|
118 if len(parts) == 3:
|
jpayne@68
|
119 uri, localname, prefix = parts
|
jpayne@68
|
120 prefix = intern(prefix, prefix)
|
jpayne@68
|
121 qname = "%s:%s" % (prefix, localname)
|
jpayne@68
|
122 qname = intern(qname, qname)
|
jpayne@68
|
123 localname = intern(localname, localname)
|
jpayne@68
|
124 elif len(parts) == 2:
|
jpayne@68
|
125 uri, localname = parts
|
jpayne@68
|
126 prefix = EMPTY_PREFIX
|
jpayne@68
|
127 qname = localname = intern(localname, localname)
|
jpayne@68
|
128 else:
|
jpayne@68
|
129 raise ValueError("Unsupported syntax: spaces in URIs not supported: %r" % name)
|
jpayne@68
|
130 return intern(uri, uri), localname, prefix, qname
|
jpayne@68
|
131
|
jpayne@68
|
132
|
jpayne@68
|
133 class ExpatBuilder:
|
jpayne@68
|
134 """Document builder that uses Expat to build a ParsedXML.DOM document
|
jpayne@68
|
135 instance."""
|
jpayne@68
|
136
|
jpayne@68
|
137 def __init__(self, options=None):
|
jpayne@68
|
138 if options is None:
|
jpayne@68
|
139 options = xmlbuilder.Options()
|
jpayne@68
|
140 self._options = options
|
jpayne@68
|
141 if self._options.filter is not None:
|
jpayne@68
|
142 self._filter = FilterVisibilityController(self._options.filter)
|
jpayne@68
|
143 else:
|
jpayne@68
|
144 self._filter = None
|
jpayne@68
|
145 # This *really* doesn't do anything in this case, so
|
jpayne@68
|
146 # override it with something fast & minimal.
|
jpayne@68
|
147 self._finish_start_element = id
|
jpayne@68
|
148 self._parser = None
|
jpayne@68
|
149 self.reset()
|
jpayne@68
|
150
|
jpayne@68
|
151 def createParser(self):
|
jpayne@68
|
152 """Create a new parser object."""
|
jpayne@68
|
153 return expat.ParserCreate()
|
jpayne@68
|
154
|
jpayne@68
|
155 def getParser(self):
|
jpayne@68
|
156 """Return the parser object, creating a new one if needed."""
|
jpayne@68
|
157 if not self._parser:
|
jpayne@68
|
158 self._parser = self.createParser()
|
jpayne@68
|
159 self._intern_setdefault = self._parser.intern.setdefault
|
jpayne@68
|
160 self._parser.buffer_text = True
|
jpayne@68
|
161 self._parser.ordered_attributes = True
|
jpayne@68
|
162 self._parser.specified_attributes = True
|
jpayne@68
|
163 self.install(self._parser)
|
jpayne@68
|
164 return self._parser
|
jpayne@68
|
165
|
jpayne@68
|
166 def reset(self):
|
jpayne@68
|
167 """Free all data structures used during DOM construction."""
|
jpayne@68
|
168 self.document = theDOMImplementation.createDocument(
|
jpayne@68
|
169 EMPTY_NAMESPACE, None, None)
|
jpayne@68
|
170 self.curNode = self.document
|
jpayne@68
|
171 self._elem_info = self.document._elem_info
|
jpayne@68
|
172 self._cdata = False
|
jpayne@68
|
173
|
jpayne@68
|
174 def install(self, parser):
|
jpayne@68
|
175 """Install the callbacks needed to build the DOM into the parser."""
|
jpayne@68
|
176 # This creates circular references!
|
jpayne@68
|
177 parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler
|
jpayne@68
|
178 parser.StartElementHandler = self.first_element_handler
|
jpayne@68
|
179 parser.EndElementHandler = self.end_element_handler
|
jpayne@68
|
180 parser.ProcessingInstructionHandler = self.pi_handler
|
jpayne@68
|
181 if self._options.entities:
|
jpayne@68
|
182 parser.EntityDeclHandler = self.entity_decl_handler
|
jpayne@68
|
183 parser.NotationDeclHandler = self.notation_decl_handler
|
jpayne@68
|
184 if self._options.comments:
|
jpayne@68
|
185 parser.CommentHandler = self.comment_handler
|
jpayne@68
|
186 if self._options.cdata_sections:
|
jpayne@68
|
187 parser.StartCdataSectionHandler = self.start_cdata_section_handler
|
jpayne@68
|
188 parser.EndCdataSectionHandler = self.end_cdata_section_handler
|
jpayne@68
|
189 parser.CharacterDataHandler = self.character_data_handler_cdata
|
jpayne@68
|
190 else:
|
jpayne@68
|
191 parser.CharacterDataHandler = self.character_data_handler
|
jpayne@68
|
192 parser.ExternalEntityRefHandler = self.external_entity_ref_handler
|
jpayne@68
|
193 parser.XmlDeclHandler = self.xml_decl_handler
|
jpayne@68
|
194 parser.ElementDeclHandler = self.element_decl_handler
|
jpayne@68
|
195 parser.AttlistDeclHandler = self.attlist_decl_handler
|
jpayne@68
|
196
|
jpayne@68
|
197 def parseFile(self, file):
|
jpayne@68
|
198 """Parse a document from a file object, returning the document
|
jpayne@68
|
199 node."""
|
jpayne@68
|
200 parser = self.getParser()
|
jpayne@68
|
201 first_buffer = True
|
jpayne@68
|
202 try:
|
jpayne@68
|
203 while 1:
|
jpayne@68
|
204 buffer = file.read(16*1024)
|
jpayne@68
|
205 if not buffer:
|
jpayne@68
|
206 break
|
jpayne@68
|
207 parser.Parse(buffer, 0)
|
jpayne@68
|
208 if first_buffer and self.document.documentElement:
|
jpayne@68
|
209 self._setup_subset(buffer)
|
jpayne@68
|
210 first_buffer = False
|
jpayne@68
|
211 parser.Parse("", True)
|
jpayne@68
|
212 except ParseEscape:
|
jpayne@68
|
213 pass
|
jpayne@68
|
214 doc = self.document
|
jpayne@68
|
215 self.reset()
|
jpayne@68
|
216 self._parser = None
|
jpayne@68
|
217 return doc
|
jpayne@68
|
218
|
jpayne@68
|
219 def parseString(self, string):
|
jpayne@68
|
220 """Parse a document from a string, returning the document node."""
|
jpayne@68
|
221 parser = self.getParser()
|
jpayne@68
|
222 try:
|
jpayne@68
|
223 parser.Parse(string, True)
|
jpayne@68
|
224 self._setup_subset(string)
|
jpayne@68
|
225 except ParseEscape:
|
jpayne@68
|
226 pass
|
jpayne@68
|
227 doc = self.document
|
jpayne@68
|
228 self.reset()
|
jpayne@68
|
229 self._parser = None
|
jpayne@68
|
230 return doc
|
jpayne@68
|
231
|
jpayne@68
|
232 def _setup_subset(self, buffer):
|
jpayne@68
|
233 """Load the internal subset if there might be one."""
|
jpayne@68
|
234 if self.document.doctype:
|
jpayne@68
|
235 extractor = InternalSubsetExtractor()
|
jpayne@68
|
236 extractor.parseString(buffer)
|
jpayne@68
|
237 subset = extractor.getSubset()
|
jpayne@68
|
238 self.document.doctype.internalSubset = subset
|
jpayne@68
|
239
|
jpayne@68
|
240 def start_doctype_decl_handler(self, doctypeName, systemId, publicId,
|
jpayne@68
|
241 has_internal_subset):
|
jpayne@68
|
242 doctype = self.document.implementation.createDocumentType(
|
jpayne@68
|
243 doctypeName, publicId, systemId)
|
jpayne@68
|
244 doctype.ownerDocument = self.document
|
jpayne@68
|
245 _append_child(self.document, doctype)
|
jpayne@68
|
246 self.document.doctype = doctype
|
jpayne@68
|
247 if self._filter and self._filter.acceptNode(doctype) == FILTER_REJECT:
|
jpayne@68
|
248 self.document.doctype = None
|
jpayne@68
|
249 del self.document.childNodes[-1]
|
jpayne@68
|
250 doctype = None
|
jpayne@68
|
251 self._parser.EntityDeclHandler = None
|
jpayne@68
|
252 self._parser.NotationDeclHandler = None
|
jpayne@68
|
253 if has_internal_subset:
|
jpayne@68
|
254 if doctype is not None:
|
jpayne@68
|
255 doctype.entities._seq = []
|
jpayne@68
|
256 doctype.notations._seq = []
|
jpayne@68
|
257 self._parser.CommentHandler = None
|
jpayne@68
|
258 self._parser.ProcessingInstructionHandler = None
|
jpayne@68
|
259 self._parser.EndDoctypeDeclHandler = self.end_doctype_decl_handler
|
jpayne@68
|
260
|
jpayne@68
|
261 def end_doctype_decl_handler(self):
|
jpayne@68
|
262 if self._options.comments:
|
jpayne@68
|
263 self._parser.CommentHandler = self.comment_handler
|
jpayne@68
|
264 self._parser.ProcessingInstructionHandler = self.pi_handler
|
jpayne@68
|
265 if not (self._elem_info or self._filter):
|
jpayne@68
|
266 self._finish_end_element = id
|
jpayne@68
|
267
|
jpayne@68
|
268 def pi_handler(self, target, data):
|
jpayne@68
|
269 node = self.document.createProcessingInstruction(target, data)
|
jpayne@68
|
270 _append_child(self.curNode, node)
|
jpayne@68
|
271 if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:
|
jpayne@68
|
272 self.curNode.removeChild(node)
|
jpayne@68
|
273
|
jpayne@68
|
274 def character_data_handler_cdata(self, data):
|
jpayne@68
|
275 childNodes = self.curNode.childNodes
|
jpayne@68
|
276 if self._cdata:
|
jpayne@68
|
277 if ( self._cdata_continue
|
jpayne@68
|
278 and childNodes[-1].nodeType == CDATA_SECTION_NODE):
|
jpayne@68
|
279 childNodes[-1].appendData(data)
|
jpayne@68
|
280 return
|
jpayne@68
|
281 node = self.document.createCDATASection(data)
|
jpayne@68
|
282 self._cdata_continue = True
|
jpayne@68
|
283 elif childNodes and childNodes[-1].nodeType == TEXT_NODE:
|
jpayne@68
|
284 node = childNodes[-1]
|
jpayne@68
|
285 value = node.data + data
|
jpayne@68
|
286 node.data = value
|
jpayne@68
|
287 return
|
jpayne@68
|
288 else:
|
jpayne@68
|
289 node = minidom.Text()
|
jpayne@68
|
290 node.data = data
|
jpayne@68
|
291 node.ownerDocument = self.document
|
jpayne@68
|
292 _append_child(self.curNode, node)
|
jpayne@68
|
293
|
jpayne@68
|
294 def character_data_handler(self, data):
|
jpayne@68
|
295 childNodes = self.curNode.childNodes
|
jpayne@68
|
296 if childNodes and childNodes[-1].nodeType == TEXT_NODE:
|
jpayne@68
|
297 node = childNodes[-1]
|
jpayne@68
|
298 node.data = node.data + data
|
jpayne@68
|
299 return
|
jpayne@68
|
300 node = minidom.Text()
|
jpayne@68
|
301 node.data = node.data + data
|
jpayne@68
|
302 node.ownerDocument = self.document
|
jpayne@68
|
303 _append_child(self.curNode, node)
|
jpayne@68
|
304
|
jpayne@68
|
305 def entity_decl_handler(self, entityName, is_parameter_entity, value,
|
jpayne@68
|
306 base, systemId, publicId, notationName):
|
jpayne@68
|
307 if is_parameter_entity:
|
jpayne@68
|
308 # we don't care about parameter entities for the DOM
|
jpayne@68
|
309 return
|
jpayne@68
|
310 if not self._options.entities:
|
jpayne@68
|
311 return
|
jpayne@68
|
312 node = self.document._create_entity(entityName, publicId,
|
jpayne@68
|
313 systemId, notationName)
|
jpayne@68
|
314 if value is not None:
|
jpayne@68
|
315 # internal entity
|
jpayne@68
|
316 # node *should* be readonly, but we'll cheat
|
jpayne@68
|
317 child = self.document.createTextNode(value)
|
jpayne@68
|
318 node.childNodes.append(child)
|
jpayne@68
|
319 self.document.doctype.entities._seq.append(node)
|
jpayne@68
|
320 if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:
|
jpayne@68
|
321 del self.document.doctype.entities._seq[-1]
|
jpayne@68
|
322
|
jpayne@68
|
323 def notation_decl_handler(self, notationName, base, systemId, publicId):
|
jpayne@68
|
324 node = self.document._create_notation(notationName, publicId, systemId)
|
jpayne@68
|
325 self.document.doctype.notations._seq.append(node)
|
jpayne@68
|
326 if self._filter and self._filter.acceptNode(node) == FILTER_ACCEPT:
|
jpayne@68
|
327 del self.document.doctype.notations._seq[-1]
|
jpayne@68
|
328
|
jpayne@68
|
329 def comment_handler(self, data):
|
jpayne@68
|
330 node = self.document.createComment(data)
|
jpayne@68
|
331 _append_child(self.curNode, node)
|
jpayne@68
|
332 if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:
|
jpayne@68
|
333 self.curNode.removeChild(node)
|
jpayne@68
|
334
|
jpayne@68
|
335 def start_cdata_section_handler(self):
|
jpayne@68
|
336 self._cdata = True
|
jpayne@68
|
337 self._cdata_continue = False
|
jpayne@68
|
338
|
jpayne@68
|
339 def end_cdata_section_handler(self):
|
jpayne@68
|
340 self._cdata = False
|
jpayne@68
|
341 self._cdata_continue = False
|
jpayne@68
|
342
|
jpayne@68
|
343 def external_entity_ref_handler(self, context, base, systemId, publicId):
|
jpayne@68
|
344 return 1
|
jpayne@68
|
345
|
jpayne@68
|
346 def first_element_handler(self, name, attributes):
|
jpayne@68
|
347 if self._filter is None and not self._elem_info:
|
jpayne@68
|
348 self._finish_end_element = id
|
jpayne@68
|
349 self.getParser().StartElementHandler = self.start_element_handler
|
jpayne@68
|
350 self.start_element_handler(name, attributes)
|
jpayne@68
|
351
|
jpayne@68
|
352 def start_element_handler(self, name, attributes):
|
jpayne@68
|
353 node = self.document.createElement(name)
|
jpayne@68
|
354 _append_child(self.curNode, node)
|
jpayne@68
|
355 self.curNode = node
|
jpayne@68
|
356
|
jpayne@68
|
357 if attributes:
|
jpayne@68
|
358 for i in range(0, len(attributes), 2):
|
jpayne@68
|
359 a = minidom.Attr(attributes[i], EMPTY_NAMESPACE,
|
jpayne@68
|
360 None, EMPTY_PREFIX)
|
jpayne@68
|
361 value = attributes[i+1]
|
jpayne@68
|
362 a.value = value
|
jpayne@68
|
363 a.ownerDocument = self.document
|
jpayne@68
|
364 _set_attribute_node(node, a)
|
jpayne@68
|
365
|
jpayne@68
|
366 if node is not self.document.documentElement:
|
jpayne@68
|
367 self._finish_start_element(node)
|
jpayne@68
|
368
|
jpayne@68
|
369 def _finish_start_element(self, node):
|
jpayne@68
|
370 if self._filter:
|
jpayne@68
|
371 # To be general, we'd have to call isSameNode(), but this
|
jpayne@68
|
372 # is sufficient for minidom:
|
jpayne@68
|
373 if node is self.document.documentElement:
|
jpayne@68
|
374 return
|
jpayne@68
|
375 filt = self._filter.startContainer(node)
|
jpayne@68
|
376 if filt == FILTER_REJECT:
|
jpayne@68
|
377 # ignore this node & all descendents
|
jpayne@68
|
378 Rejecter(self)
|
jpayne@68
|
379 elif filt == FILTER_SKIP:
|
jpayne@68
|
380 # ignore this node, but make it's children become
|
jpayne@68
|
381 # children of the parent node
|
jpayne@68
|
382 Skipper(self)
|
jpayne@68
|
383 else:
|
jpayne@68
|
384 return
|
jpayne@68
|
385 self.curNode = node.parentNode
|
jpayne@68
|
386 node.parentNode.removeChild(node)
|
jpayne@68
|
387 node.unlink()
|
jpayne@68
|
388
|
jpayne@68
|
389 # If this ever changes, Namespaces.end_element_handler() needs to
|
jpayne@68
|
390 # be changed to match.
|
jpayne@68
|
391 #
|
jpayne@68
|
392 def end_element_handler(self, name):
|
jpayne@68
|
393 curNode = self.curNode
|
jpayne@68
|
394 self.curNode = curNode.parentNode
|
jpayne@68
|
395 self._finish_end_element(curNode)
|
jpayne@68
|
396
|
jpayne@68
|
397 def _finish_end_element(self, curNode):
|
jpayne@68
|
398 info = self._elem_info.get(curNode.tagName)
|
jpayne@68
|
399 if info:
|
jpayne@68
|
400 self._handle_white_text_nodes(curNode, info)
|
jpayne@68
|
401 if self._filter:
|
jpayne@68
|
402 if curNode is self.document.documentElement:
|
jpayne@68
|
403 return
|
jpayne@68
|
404 if self._filter.acceptNode(curNode) == FILTER_REJECT:
|
jpayne@68
|
405 self.curNode.removeChild(curNode)
|
jpayne@68
|
406 curNode.unlink()
|
jpayne@68
|
407
|
jpayne@68
|
408 def _handle_white_text_nodes(self, node, info):
|
jpayne@68
|
409 if (self._options.whitespace_in_element_content
|
jpayne@68
|
410 or not info.isElementContent()):
|
jpayne@68
|
411 return
|
jpayne@68
|
412
|
jpayne@68
|
413 # We have element type information and should remove ignorable
|
jpayne@68
|
414 # whitespace; identify for text nodes which contain only
|
jpayne@68
|
415 # whitespace.
|
jpayne@68
|
416 L = []
|
jpayne@68
|
417 for child in node.childNodes:
|
jpayne@68
|
418 if child.nodeType == TEXT_NODE and not child.data.strip():
|
jpayne@68
|
419 L.append(child)
|
jpayne@68
|
420
|
jpayne@68
|
421 # Remove ignorable whitespace from the tree.
|
jpayne@68
|
422 for child in L:
|
jpayne@68
|
423 node.removeChild(child)
|
jpayne@68
|
424
|
jpayne@68
|
425 def element_decl_handler(self, name, model):
|
jpayne@68
|
426 info = self._elem_info.get(name)
|
jpayne@68
|
427 if info is None:
|
jpayne@68
|
428 self._elem_info[name] = ElementInfo(name, model)
|
jpayne@68
|
429 else:
|
jpayne@68
|
430 assert info._model is None
|
jpayne@68
|
431 info._model = model
|
jpayne@68
|
432
|
jpayne@68
|
433 def attlist_decl_handler(self, elem, name, type, default, required):
|
jpayne@68
|
434 info = self._elem_info.get(elem)
|
jpayne@68
|
435 if info is None:
|
jpayne@68
|
436 info = ElementInfo(elem)
|
jpayne@68
|
437 self._elem_info[elem] = info
|
jpayne@68
|
438 info._attr_info.append(
|
jpayne@68
|
439 [None, name, None, None, default, 0, type, required])
|
jpayne@68
|
440
|
jpayne@68
|
441 def xml_decl_handler(self, version, encoding, standalone):
|
jpayne@68
|
442 self.document.version = version
|
jpayne@68
|
443 self.document.encoding = encoding
|
jpayne@68
|
444 # This is still a little ugly, thanks to the pyexpat API. ;-(
|
jpayne@68
|
445 if standalone >= 0:
|
jpayne@68
|
446 if standalone:
|
jpayne@68
|
447 self.document.standalone = True
|
jpayne@68
|
448 else:
|
jpayne@68
|
449 self.document.standalone = False
|
jpayne@68
|
450
|
jpayne@68
|
451
|
jpayne@68
|
452 # Don't include FILTER_INTERRUPT, since that's checked separately
|
jpayne@68
|
453 # where allowed.
|
jpayne@68
|
454 _ALLOWED_FILTER_RETURNS = (FILTER_ACCEPT, FILTER_REJECT, FILTER_SKIP)
|
jpayne@68
|
455
|
jpayne@68
|
456 class FilterVisibilityController(object):
|
jpayne@68
|
457 """Wrapper around a DOMBuilderFilter which implements the checks
|
jpayne@68
|
458 to make the whatToShow filter attribute work."""
|
jpayne@68
|
459
|
jpayne@68
|
460 __slots__ = 'filter',
|
jpayne@68
|
461
|
jpayne@68
|
462 def __init__(self, filter):
|
jpayne@68
|
463 self.filter = filter
|
jpayne@68
|
464
|
jpayne@68
|
465 def startContainer(self, node):
|
jpayne@68
|
466 mask = self._nodetype_mask[node.nodeType]
|
jpayne@68
|
467 if self.filter.whatToShow & mask:
|
jpayne@68
|
468 val = self.filter.startContainer(node)
|
jpayne@68
|
469 if val == FILTER_INTERRUPT:
|
jpayne@68
|
470 raise ParseEscape
|
jpayne@68
|
471 if val not in _ALLOWED_FILTER_RETURNS:
|
jpayne@68
|
472 raise ValueError(
|
jpayne@68
|
473 "startContainer() returned illegal value: " + repr(val))
|
jpayne@68
|
474 return val
|
jpayne@68
|
475 else:
|
jpayne@68
|
476 return FILTER_ACCEPT
|
jpayne@68
|
477
|
jpayne@68
|
478 def acceptNode(self, node):
|
jpayne@68
|
479 mask = self._nodetype_mask[node.nodeType]
|
jpayne@68
|
480 if self.filter.whatToShow & mask:
|
jpayne@68
|
481 val = self.filter.acceptNode(node)
|
jpayne@68
|
482 if val == FILTER_INTERRUPT:
|
jpayne@68
|
483 raise ParseEscape
|
jpayne@68
|
484 if val == FILTER_SKIP:
|
jpayne@68
|
485 # move all child nodes to the parent, and remove this node
|
jpayne@68
|
486 parent = node.parentNode
|
jpayne@68
|
487 for child in node.childNodes[:]:
|
jpayne@68
|
488 parent.appendChild(child)
|
jpayne@68
|
489 # node is handled by the caller
|
jpayne@68
|
490 return FILTER_REJECT
|
jpayne@68
|
491 if val not in _ALLOWED_FILTER_RETURNS:
|
jpayne@68
|
492 raise ValueError(
|
jpayne@68
|
493 "acceptNode() returned illegal value: " + repr(val))
|
jpayne@68
|
494 return val
|
jpayne@68
|
495 else:
|
jpayne@68
|
496 return FILTER_ACCEPT
|
jpayne@68
|
497
|
jpayne@68
|
498 _nodetype_mask = {
|
jpayne@68
|
499 Node.ELEMENT_NODE: NodeFilter.SHOW_ELEMENT,
|
jpayne@68
|
500 Node.ATTRIBUTE_NODE: NodeFilter.SHOW_ATTRIBUTE,
|
jpayne@68
|
501 Node.TEXT_NODE: NodeFilter.SHOW_TEXT,
|
jpayne@68
|
502 Node.CDATA_SECTION_NODE: NodeFilter.SHOW_CDATA_SECTION,
|
jpayne@68
|
503 Node.ENTITY_REFERENCE_NODE: NodeFilter.SHOW_ENTITY_REFERENCE,
|
jpayne@68
|
504 Node.ENTITY_NODE: NodeFilter.SHOW_ENTITY,
|
jpayne@68
|
505 Node.PROCESSING_INSTRUCTION_NODE: NodeFilter.SHOW_PROCESSING_INSTRUCTION,
|
jpayne@68
|
506 Node.COMMENT_NODE: NodeFilter.SHOW_COMMENT,
|
jpayne@68
|
507 Node.DOCUMENT_NODE: NodeFilter.SHOW_DOCUMENT,
|
jpayne@68
|
508 Node.DOCUMENT_TYPE_NODE: NodeFilter.SHOW_DOCUMENT_TYPE,
|
jpayne@68
|
509 Node.DOCUMENT_FRAGMENT_NODE: NodeFilter.SHOW_DOCUMENT_FRAGMENT,
|
jpayne@68
|
510 Node.NOTATION_NODE: NodeFilter.SHOW_NOTATION,
|
jpayne@68
|
511 }
|
jpayne@68
|
512
|
jpayne@68
|
513
|
jpayne@68
|
514 class FilterCrutch(object):
|
jpayne@68
|
515 __slots__ = '_builder', '_level', '_old_start', '_old_end'
|
jpayne@68
|
516
|
jpayne@68
|
517 def __init__(self, builder):
|
jpayne@68
|
518 self._level = 0
|
jpayne@68
|
519 self._builder = builder
|
jpayne@68
|
520 parser = builder._parser
|
jpayne@68
|
521 self._old_start = parser.StartElementHandler
|
jpayne@68
|
522 self._old_end = parser.EndElementHandler
|
jpayne@68
|
523 parser.StartElementHandler = self.start_element_handler
|
jpayne@68
|
524 parser.EndElementHandler = self.end_element_handler
|
jpayne@68
|
525
|
jpayne@68
|
526 class Rejecter(FilterCrutch):
|
jpayne@68
|
527 __slots__ = ()
|
jpayne@68
|
528
|
jpayne@68
|
529 def __init__(self, builder):
|
jpayne@68
|
530 FilterCrutch.__init__(self, builder)
|
jpayne@68
|
531 parser = builder._parser
|
jpayne@68
|
532 for name in ("ProcessingInstructionHandler",
|
jpayne@68
|
533 "CommentHandler",
|
jpayne@68
|
534 "CharacterDataHandler",
|
jpayne@68
|
535 "StartCdataSectionHandler",
|
jpayne@68
|
536 "EndCdataSectionHandler",
|
jpayne@68
|
537 "ExternalEntityRefHandler",
|
jpayne@68
|
538 ):
|
jpayne@68
|
539 setattr(parser, name, None)
|
jpayne@68
|
540
|
jpayne@68
|
541 def start_element_handler(self, *args):
|
jpayne@68
|
542 self._level = self._level + 1
|
jpayne@68
|
543
|
jpayne@68
|
544 def end_element_handler(self, *args):
|
jpayne@68
|
545 if self._level == 0:
|
jpayne@68
|
546 # restore the old handlers
|
jpayne@68
|
547 parser = self._builder._parser
|
jpayne@68
|
548 self._builder.install(parser)
|
jpayne@68
|
549 parser.StartElementHandler = self._old_start
|
jpayne@68
|
550 parser.EndElementHandler = self._old_end
|
jpayne@68
|
551 else:
|
jpayne@68
|
552 self._level = self._level - 1
|
jpayne@68
|
553
|
jpayne@68
|
554 class Skipper(FilterCrutch):
|
jpayne@68
|
555 __slots__ = ()
|
jpayne@68
|
556
|
jpayne@68
|
557 def start_element_handler(self, *args):
|
jpayne@68
|
558 node = self._builder.curNode
|
jpayne@68
|
559 self._old_start(*args)
|
jpayne@68
|
560 if self._builder.curNode is not node:
|
jpayne@68
|
561 self._level = self._level + 1
|
jpayne@68
|
562
|
jpayne@68
|
563 def end_element_handler(self, *args):
|
jpayne@68
|
564 if self._level == 0:
|
jpayne@68
|
565 # We're popping back out of the node we're skipping, so we
|
jpayne@68
|
566 # shouldn't need to do anything but reset the handlers.
|
jpayne@68
|
567 self._builder._parser.StartElementHandler = self._old_start
|
jpayne@68
|
568 self._builder._parser.EndElementHandler = self._old_end
|
jpayne@68
|
569 self._builder = None
|
jpayne@68
|
570 else:
|
jpayne@68
|
571 self._level = self._level - 1
|
jpayne@68
|
572 self._old_end(*args)
|
jpayne@68
|
573
|
jpayne@68
|
574
|
jpayne@68
|
575 # framework document used by the fragment builder.
|
jpayne@68
|
576 # Takes a string for the doctype, subset string, and namespace attrs string.
|
jpayne@68
|
577
|
jpayne@68
|
578 _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID = \
|
jpayne@68
|
579 "http://xml.python.org/entities/fragment-builder/internal"
|
jpayne@68
|
580
|
jpayne@68
|
581 _FRAGMENT_BUILDER_TEMPLATE = (
|
jpayne@68
|
582 '''\
|
jpayne@68
|
583 <!DOCTYPE wrapper
|
jpayne@68
|
584 %%s [
|
jpayne@68
|
585 <!ENTITY fragment-builder-internal
|
jpayne@68
|
586 SYSTEM "%s">
|
jpayne@68
|
587 %%s
|
jpayne@68
|
588 ]>
|
jpayne@68
|
589 <wrapper %%s
|
jpayne@68
|
590 >&fragment-builder-internal;</wrapper>'''
|
jpayne@68
|
591 % _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID)
|
jpayne@68
|
592
|
jpayne@68
|
593
|
jpayne@68
|
594 class FragmentBuilder(ExpatBuilder):
|
jpayne@68
|
595 """Builder which constructs document fragments given XML source
|
jpayne@68
|
596 text and a context node.
|
jpayne@68
|
597
|
jpayne@68
|
598 The context node is expected to provide information about the
|
jpayne@68
|
599 namespace declarations which are in scope at the start of the
|
jpayne@68
|
600 fragment.
|
jpayne@68
|
601 """
|
jpayne@68
|
602
|
jpayne@68
|
603 def __init__(self, context, options=None):
|
jpayne@68
|
604 if context.nodeType == DOCUMENT_NODE:
|
jpayne@68
|
605 self.originalDocument = context
|
jpayne@68
|
606 self.context = context
|
jpayne@68
|
607 else:
|
jpayne@68
|
608 self.originalDocument = context.ownerDocument
|
jpayne@68
|
609 self.context = context
|
jpayne@68
|
610 ExpatBuilder.__init__(self, options)
|
jpayne@68
|
611
|
jpayne@68
|
612 def reset(self):
|
jpayne@68
|
613 ExpatBuilder.reset(self)
|
jpayne@68
|
614 self.fragment = None
|
jpayne@68
|
615
|
jpayne@68
|
616 def parseFile(self, file):
|
jpayne@68
|
617 """Parse a document fragment from a file object, returning the
|
jpayne@68
|
618 fragment node."""
|
jpayne@68
|
619 return self.parseString(file.read())
|
jpayne@68
|
620
|
jpayne@68
|
621 def parseString(self, string):
|
jpayne@68
|
622 """Parse a document fragment from a string, returning the
|
jpayne@68
|
623 fragment node."""
|
jpayne@68
|
624 self._source = string
|
jpayne@68
|
625 parser = self.getParser()
|
jpayne@68
|
626 doctype = self.originalDocument.doctype
|
jpayne@68
|
627 ident = ""
|
jpayne@68
|
628 if doctype:
|
jpayne@68
|
629 subset = doctype.internalSubset or self._getDeclarations()
|
jpayne@68
|
630 if doctype.publicId:
|
jpayne@68
|
631 ident = ('PUBLIC "%s" "%s"'
|
jpayne@68
|
632 % (doctype.publicId, doctype.systemId))
|
jpayne@68
|
633 elif doctype.systemId:
|
jpayne@68
|
634 ident = 'SYSTEM "%s"' % doctype.systemId
|
jpayne@68
|
635 else:
|
jpayne@68
|
636 subset = ""
|
jpayne@68
|
637 nsattrs = self._getNSattrs() # get ns decls from node's ancestors
|
jpayne@68
|
638 document = _FRAGMENT_BUILDER_TEMPLATE % (ident, subset, nsattrs)
|
jpayne@68
|
639 try:
|
jpayne@68
|
640 parser.Parse(document, 1)
|
jpayne@68
|
641 except:
|
jpayne@68
|
642 self.reset()
|
jpayne@68
|
643 raise
|
jpayne@68
|
644 fragment = self.fragment
|
jpayne@68
|
645 self.reset()
|
jpayne@68
|
646 ## self._parser = None
|
jpayne@68
|
647 return fragment
|
jpayne@68
|
648
|
jpayne@68
|
649 def _getDeclarations(self):
|
jpayne@68
|
650 """Re-create the internal subset from the DocumentType node.
|
jpayne@68
|
651
|
jpayne@68
|
652 This is only needed if we don't already have the
|
jpayne@68
|
653 internalSubset as a string.
|
jpayne@68
|
654 """
|
jpayne@68
|
655 doctype = self.context.ownerDocument.doctype
|
jpayne@68
|
656 s = ""
|
jpayne@68
|
657 if doctype:
|
jpayne@68
|
658 for i in range(doctype.notations.length):
|
jpayne@68
|
659 notation = doctype.notations.item(i)
|
jpayne@68
|
660 if s:
|
jpayne@68
|
661 s = s + "\n "
|
jpayne@68
|
662 s = "%s<!NOTATION %s" % (s, notation.nodeName)
|
jpayne@68
|
663 if notation.publicId:
|
jpayne@68
|
664 s = '%s PUBLIC "%s"\n "%s">' \
|
jpayne@68
|
665 % (s, notation.publicId, notation.systemId)
|
jpayne@68
|
666 else:
|
jpayne@68
|
667 s = '%s SYSTEM "%s">' % (s, notation.systemId)
|
jpayne@68
|
668 for i in range(doctype.entities.length):
|
jpayne@68
|
669 entity = doctype.entities.item(i)
|
jpayne@68
|
670 if s:
|
jpayne@68
|
671 s = s + "\n "
|
jpayne@68
|
672 s = "%s<!ENTITY %s" % (s, entity.nodeName)
|
jpayne@68
|
673 if entity.publicId:
|
jpayne@68
|
674 s = '%s PUBLIC "%s"\n "%s"' \
|
jpayne@68
|
675 % (s, entity.publicId, entity.systemId)
|
jpayne@68
|
676 elif entity.systemId:
|
jpayne@68
|
677 s = '%s SYSTEM "%s"' % (s, entity.systemId)
|
jpayne@68
|
678 else:
|
jpayne@68
|
679 s = '%s "%s"' % (s, entity.firstChild.data)
|
jpayne@68
|
680 if entity.notationName:
|
jpayne@68
|
681 s = "%s NOTATION %s" % (s, entity.notationName)
|
jpayne@68
|
682 s = s + ">"
|
jpayne@68
|
683 return s
|
jpayne@68
|
684
|
jpayne@68
|
685 def _getNSattrs(self):
|
jpayne@68
|
686 return ""
|
jpayne@68
|
687
|
jpayne@68
|
688 def external_entity_ref_handler(self, context, base, systemId, publicId):
|
jpayne@68
|
689 if systemId == _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID:
|
jpayne@68
|
690 # this entref is the one that we made to put the subtree
|
jpayne@68
|
691 # in; all of our given input is parsed in here.
|
jpayne@68
|
692 old_document = self.document
|
jpayne@68
|
693 old_cur_node = self.curNode
|
jpayne@68
|
694 parser = self._parser.ExternalEntityParserCreate(context)
|
jpayne@68
|
695 # put the real document back, parse into the fragment to return
|
jpayne@68
|
696 self.document = self.originalDocument
|
jpayne@68
|
697 self.fragment = self.document.createDocumentFragment()
|
jpayne@68
|
698 self.curNode = self.fragment
|
jpayne@68
|
699 try:
|
jpayne@68
|
700 parser.Parse(self._source, 1)
|
jpayne@68
|
701 finally:
|
jpayne@68
|
702 self.curNode = old_cur_node
|
jpayne@68
|
703 self.document = old_document
|
jpayne@68
|
704 self._source = None
|
jpayne@68
|
705 return -1
|
jpayne@68
|
706 else:
|
jpayne@68
|
707 return ExpatBuilder.external_entity_ref_handler(
|
jpayne@68
|
708 self, context, base, systemId, publicId)
|
jpayne@68
|
709
|
jpayne@68
|
710
|
jpayne@68
|
711 class Namespaces:
|
jpayne@68
|
712 """Mix-in class for builders; adds support for namespaces."""
|
jpayne@68
|
713
|
jpayne@68
|
714 def _initNamespaces(self):
|
jpayne@68
|
715 # list of (prefix, uri) ns declarations. Namespace attrs are
|
jpayne@68
|
716 # constructed from this and added to the element's attrs.
|
jpayne@68
|
717 self._ns_ordered_prefixes = []
|
jpayne@68
|
718
|
jpayne@68
|
719 def createParser(self):
|
jpayne@68
|
720 """Create a new namespace-handling parser."""
|
jpayne@68
|
721 parser = expat.ParserCreate(namespace_separator=" ")
|
jpayne@68
|
722 parser.namespace_prefixes = True
|
jpayne@68
|
723 return parser
|
jpayne@68
|
724
|
jpayne@68
|
725 def install(self, parser):
|
jpayne@68
|
726 """Insert the namespace-handlers onto the parser."""
|
jpayne@68
|
727 ExpatBuilder.install(self, parser)
|
jpayne@68
|
728 if self._options.namespace_declarations:
|
jpayne@68
|
729 parser.StartNamespaceDeclHandler = (
|
jpayne@68
|
730 self.start_namespace_decl_handler)
|
jpayne@68
|
731
|
jpayne@68
|
732 def start_namespace_decl_handler(self, prefix, uri):
|
jpayne@68
|
733 """Push this namespace declaration on our storage."""
|
jpayne@68
|
734 self._ns_ordered_prefixes.append((prefix, uri))
|
jpayne@68
|
735
|
jpayne@68
|
736 def start_element_handler(self, name, attributes):
|
jpayne@68
|
737 if ' ' in name:
|
jpayne@68
|
738 uri, localname, prefix, qname = _parse_ns_name(self, name)
|
jpayne@68
|
739 else:
|
jpayne@68
|
740 uri = EMPTY_NAMESPACE
|
jpayne@68
|
741 qname = name
|
jpayne@68
|
742 localname = None
|
jpayne@68
|
743 prefix = EMPTY_PREFIX
|
jpayne@68
|
744 node = minidom.Element(qname, uri, prefix, localname)
|
jpayne@68
|
745 node.ownerDocument = self.document
|
jpayne@68
|
746 _append_child(self.curNode, node)
|
jpayne@68
|
747 self.curNode = node
|
jpayne@68
|
748
|
jpayne@68
|
749 if self._ns_ordered_prefixes:
|
jpayne@68
|
750 for prefix, uri in self._ns_ordered_prefixes:
|
jpayne@68
|
751 if prefix:
|
jpayne@68
|
752 a = minidom.Attr(_intern(self, 'xmlns:' + prefix),
|
jpayne@68
|
753 XMLNS_NAMESPACE, prefix, "xmlns")
|
jpayne@68
|
754 else:
|
jpayne@68
|
755 a = minidom.Attr("xmlns", XMLNS_NAMESPACE,
|
jpayne@68
|
756 "xmlns", EMPTY_PREFIX)
|
jpayne@68
|
757 a.value = uri
|
jpayne@68
|
758 a.ownerDocument = self.document
|
jpayne@68
|
759 _set_attribute_node(node, a)
|
jpayne@68
|
760 del self._ns_ordered_prefixes[:]
|
jpayne@68
|
761
|
jpayne@68
|
762 if attributes:
|
jpayne@68
|
763 node._ensure_attributes()
|
jpayne@68
|
764 _attrs = node._attrs
|
jpayne@68
|
765 _attrsNS = node._attrsNS
|
jpayne@68
|
766 for i in range(0, len(attributes), 2):
|
jpayne@68
|
767 aname = attributes[i]
|
jpayne@68
|
768 value = attributes[i+1]
|
jpayne@68
|
769 if ' ' in aname:
|
jpayne@68
|
770 uri, localname, prefix, qname = _parse_ns_name(self, aname)
|
jpayne@68
|
771 a = minidom.Attr(qname, uri, localname, prefix)
|
jpayne@68
|
772 _attrs[qname] = a
|
jpayne@68
|
773 _attrsNS[(uri, localname)] = a
|
jpayne@68
|
774 else:
|
jpayne@68
|
775 a = minidom.Attr(aname, EMPTY_NAMESPACE,
|
jpayne@68
|
776 aname, EMPTY_PREFIX)
|
jpayne@68
|
777 _attrs[aname] = a
|
jpayne@68
|
778 _attrsNS[(EMPTY_NAMESPACE, aname)] = a
|
jpayne@68
|
779 a.ownerDocument = self.document
|
jpayne@68
|
780 a.value = value
|
jpayne@68
|
781 a.ownerElement = node
|
jpayne@68
|
782
|
jpayne@68
|
783 if __debug__:
|
jpayne@68
|
784 # This only adds some asserts to the original
|
jpayne@68
|
785 # end_element_handler(), so we only define this when -O is not
|
jpayne@68
|
786 # used. If changing one, be sure to check the other to see if
|
jpayne@68
|
787 # it needs to be changed as well.
|
jpayne@68
|
788 #
|
jpayne@68
|
789 def end_element_handler(self, name):
|
jpayne@68
|
790 curNode = self.curNode
|
jpayne@68
|
791 if ' ' in name:
|
jpayne@68
|
792 uri, localname, prefix, qname = _parse_ns_name(self, name)
|
jpayne@68
|
793 assert (curNode.namespaceURI == uri
|
jpayne@68
|
794 and curNode.localName == localname
|
jpayne@68
|
795 and curNode.prefix == prefix), \
|
jpayne@68
|
796 "element stack messed up! (namespace)"
|
jpayne@68
|
797 else:
|
jpayne@68
|
798 assert curNode.nodeName == name, \
|
jpayne@68
|
799 "element stack messed up - bad nodeName"
|
jpayne@68
|
800 assert curNode.namespaceURI == EMPTY_NAMESPACE, \
|
jpayne@68
|
801 "element stack messed up - bad namespaceURI"
|
jpayne@68
|
802 self.curNode = curNode.parentNode
|
jpayne@68
|
803 self._finish_end_element(curNode)
|
jpayne@68
|
804
|
jpayne@68
|
805
|
jpayne@68
|
806 class ExpatBuilderNS(Namespaces, ExpatBuilder):
|
jpayne@68
|
807 """Document builder that supports namespaces."""
|
jpayne@68
|
808
|
jpayne@68
|
809 def reset(self):
|
jpayne@68
|
810 ExpatBuilder.reset(self)
|
jpayne@68
|
811 self._initNamespaces()
|
jpayne@68
|
812
|
jpayne@68
|
813
|
jpayne@68
|
814 class FragmentBuilderNS(Namespaces, FragmentBuilder):
|
jpayne@68
|
815 """Fragment builder that supports namespaces."""
|
jpayne@68
|
816
|
jpayne@68
|
817 def reset(self):
|
jpayne@68
|
818 FragmentBuilder.reset(self)
|
jpayne@68
|
819 self._initNamespaces()
|
jpayne@68
|
820
|
jpayne@68
|
821 def _getNSattrs(self):
|
jpayne@68
|
822 """Return string of namespace attributes from this element and
|
jpayne@68
|
823 ancestors."""
|
jpayne@68
|
824 # XXX This needs to be re-written to walk the ancestors of the
|
jpayne@68
|
825 # context to build up the namespace information from
|
jpayne@68
|
826 # declarations, elements, and attributes found in context.
|
jpayne@68
|
827 # Otherwise we have to store a bunch more data on the DOM
|
jpayne@68
|
828 # (though that *might* be more reliable -- not clear).
|
jpayne@68
|
829 attrs = ""
|
jpayne@68
|
830 context = self.context
|
jpayne@68
|
831 L = []
|
jpayne@68
|
832 while context:
|
jpayne@68
|
833 if hasattr(context, '_ns_prefix_uri'):
|
jpayne@68
|
834 for prefix, uri in context._ns_prefix_uri.items():
|
jpayne@68
|
835 # add every new NS decl from context to L and attrs string
|
jpayne@68
|
836 if prefix in L:
|
jpayne@68
|
837 continue
|
jpayne@68
|
838 L.append(prefix)
|
jpayne@68
|
839 if prefix:
|
jpayne@68
|
840 declname = "xmlns:" + prefix
|
jpayne@68
|
841 else:
|
jpayne@68
|
842 declname = "xmlns"
|
jpayne@68
|
843 if attrs:
|
jpayne@68
|
844 attrs = "%s\n %s='%s'" % (attrs, declname, uri)
|
jpayne@68
|
845 else:
|
jpayne@68
|
846 attrs = " %s='%s'" % (declname, uri)
|
jpayne@68
|
847 context = context.parentNode
|
jpayne@68
|
848 return attrs
|
jpayne@68
|
849
|
jpayne@68
|
850
|
jpayne@68
|
851 class ParseEscape(Exception):
|
jpayne@68
|
852 """Exception raised to short-circuit parsing in InternalSubsetExtractor."""
|
jpayne@68
|
853 pass
|
jpayne@68
|
854
|
jpayne@68
|
855 class InternalSubsetExtractor(ExpatBuilder):
|
jpayne@68
|
856 """XML processor which can rip out the internal document type subset."""
|
jpayne@68
|
857
|
jpayne@68
|
858 subset = None
|
jpayne@68
|
859
|
jpayne@68
|
860 def getSubset(self):
|
jpayne@68
|
861 """Return the internal subset as a string."""
|
jpayne@68
|
862 return self.subset
|
jpayne@68
|
863
|
jpayne@68
|
864 def parseFile(self, file):
|
jpayne@68
|
865 try:
|
jpayne@68
|
866 ExpatBuilder.parseFile(self, file)
|
jpayne@68
|
867 except ParseEscape:
|
jpayne@68
|
868 pass
|
jpayne@68
|
869
|
jpayne@68
|
870 def parseString(self, string):
|
jpayne@68
|
871 try:
|
jpayne@68
|
872 ExpatBuilder.parseString(self, string)
|
jpayne@68
|
873 except ParseEscape:
|
jpayne@68
|
874 pass
|
jpayne@68
|
875
|
jpayne@68
|
876 def install(self, parser):
|
jpayne@68
|
877 parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler
|
jpayne@68
|
878 parser.StartElementHandler = self.start_element_handler
|
jpayne@68
|
879
|
jpayne@68
|
880 def start_doctype_decl_handler(self, name, publicId, systemId,
|
jpayne@68
|
881 has_internal_subset):
|
jpayne@68
|
882 if has_internal_subset:
|
jpayne@68
|
883 parser = self.getParser()
|
jpayne@68
|
884 self.subset = []
|
jpayne@68
|
885 parser.DefaultHandler = self.subset.append
|
jpayne@68
|
886 parser.EndDoctypeDeclHandler = self.end_doctype_decl_handler
|
jpayne@68
|
887 else:
|
jpayne@68
|
888 raise ParseEscape()
|
jpayne@68
|
889
|
jpayne@68
|
890 def end_doctype_decl_handler(self):
|
jpayne@68
|
891 s = ''.join(self.subset).replace('\r\n', '\n').replace('\r', '\n')
|
jpayne@68
|
892 self.subset = s
|
jpayne@68
|
893 raise ParseEscape()
|
jpayne@68
|
894
|
jpayne@68
|
895 def start_element_handler(self, name, attrs):
|
jpayne@68
|
896 raise ParseEscape()
|
jpayne@68
|
897
|
jpayne@68
|
898
|
jpayne@68
|
899 def parse(file, namespaces=True):
|
jpayne@68
|
900 """Parse a document, returning the resulting Document node.
|
jpayne@68
|
901
|
jpayne@68
|
902 'file' may be either a file name or an open file object.
|
jpayne@68
|
903 """
|
jpayne@68
|
904 if namespaces:
|
jpayne@68
|
905 builder = ExpatBuilderNS()
|
jpayne@68
|
906 else:
|
jpayne@68
|
907 builder = ExpatBuilder()
|
jpayne@68
|
908
|
jpayne@68
|
909 if isinstance(file, str):
|
jpayne@68
|
910 with open(file, 'rb') as fp:
|
jpayne@68
|
911 result = builder.parseFile(fp)
|
jpayne@68
|
912 else:
|
jpayne@68
|
913 result = builder.parseFile(file)
|
jpayne@68
|
914 return result
|
jpayne@68
|
915
|
jpayne@68
|
916
|
jpayne@68
|
917 def parseString(string, namespaces=True):
|
jpayne@68
|
918 """Parse a document from a string, returning the resulting
|
jpayne@68
|
919 Document node.
|
jpayne@68
|
920 """
|
jpayne@68
|
921 if namespaces:
|
jpayne@68
|
922 builder = ExpatBuilderNS()
|
jpayne@68
|
923 else:
|
jpayne@68
|
924 builder = ExpatBuilder()
|
jpayne@68
|
925 return builder.parseString(string)
|
jpayne@68
|
926
|
jpayne@68
|
927
|
jpayne@68
|
928 def parseFragment(file, context, namespaces=True):
|
jpayne@68
|
929 """Parse a fragment of a document, given the context from which it
|
jpayne@68
|
930 was originally extracted. context should be the parent of the
|
jpayne@68
|
931 node(s) which are in the fragment.
|
jpayne@68
|
932
|
jpayne@68
|
933 'file' may be either a file name or an open file object.
|
jpayne@68
|
934 """
|
jpayne@68
|
935 if namespaces:
|
jpayne@68
|
936 builder = FragmentBuilderNS(context)
|
jpayne@68
|
937 else:
|
jpayne@68
|
938 builder = FragmentBuilder(context)
|
jpayne@68
|
939
|
jpayne@68
|
940 if isinstance(file, str):
|
jpayne@68
|
941 with open(file, 'rb') as fp:
|
jpayne@68
|
942 result = builder.parseFile(fp)
|
jpayne@68
|
943 else:
|
jpayne@68
|
944 result = builder.parseFile(file)
|
jpayne@68
|
945 return result
|
jpayne@68
|
946
|
jpayne@68
|
947
|
jpayne@68
|
948 def parseFragmentString(string, context, namespaces=True):
|
jpayne@68
|
949 """Parse a fragment of a document from a string, given the context
|
jpayne@68
|
950 from which it was originally extracted. context should be the
|
jpayne@68
|
951 parent of the node(s) which are in the fragment.
|
jpayne@68
|
952 """
|
jpayne@68
|
953 if namespaces:
|
jpayne@68
|
954 builder = FragmentBuilderNS(context)
|
jpayne@68
|
955 else:
|
jpayne@68
|
956 builder = FragmentBuilder(context)
|
jpayne@68
|
957 return builder.parseString(string)
|
jpayne@68
|
958
|
jpayne@68
|
959
|
jpayne@68
|
960 def makeBuilder(options):
|
jpayne@68
|
961 """Create a builder based on an Options object."""
|
jpayne@68
|
962 if options.namespaces:
|
jpayne@68
|
963 return ExpatBuilderNS(options)
|
jpayne@68
|
964 else:
|
jpayne@68
|
965 return ExpatBuilder(options)
|