annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/xml/dom/expatbuilder.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
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)