+++ /dev/null
-"""Facility to use the Expat parser to load a minidom instance\r
-from a string or file.\r
-\r
-This avoids all the overhead of SAX and pulldom to gain performance.\r
-"""\r
-\r
-# Warning!\r
-#\r
-# This module is tightly bound to the implementation details of the\r
-# minidom DOM and can't be used with other DOM implementations. This\r
-# is due, in part, to a lack of appropriate methods in the DOM (there is\r
-# no way to create Entity and Notation nodes via the DOM Level 2\r
-# interface), and for performance. The later is the cause of some fairly\r
-# cryptic code.\r
-#\r
-# Performance hacks:\r
-#\r
-# - .character_data_handler() has an extra case in which continuing\r
-# data is appended to an existing Text node; this can be a\r
-# speedup since pyexpat can break up character data into multiple\r
-# callbacks even though we set the buffer_text attribute on the\r
-# parser. This also gives us the advantage that we don't need a\r
-# separate normalization pass.\r
-#\r
-# - Determining that a node exists is done using an identity comparison\r
-# with None rather than a truth test; this avoids searching for and\r
-# calling any methods on the node object if it exists. (A rather\r
-# nice speedup is achieved this way as well!)\r
-\r
-from xml.dom import xmlbuilder, minidom, Node\r
-from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE\r
-from xml.parsers import expat\r
-from xml.dom.minidom import _append_child, _set_attribute_node\r
-from xml.dom.NodeFilter import NodeFilter\r
-\r
-from xml.dom.minicompat import *\r
-\r
-TEXT_NODE = Node.TEXT_NODE\r
-CDATA_SECTION_NODE = Node.CDATA_SECTION_NODE\r
-DOCUMENT_NODE = Node.DOCUMENT_NODE\r
-\r
-FILTER_ACCEPT = xmlbuilder.DOMBuilderFilter.FILTER_ACCEPT\r
-FILTER_REJECT = xmlbuilder.DOMBuilderFilter.FILTER_REJECT\r
-FILTER_SKIP = xmlbuilder.DOMBuilderFilter.FILTER_SKIP\r
-FILTER_INTERRUPT = xmlbuilder.DOMBuilderFilter.FILTER_INTERRUPT\r
-\r
-theDOMImplementation = minidom.getDOMImplementation()\r
-\r
-# Expat typename -> TypeInfo\r
-_typeinfo_map = {\r
- "CDATA": minidom.TypeInfo(None, "cdata"),\r
- "ENUM": minidom.TypeInfo(None, "enumeration"),\r
- "ENTITY": minidom.TypeInfo(None, "entity"),\r
- "ENTITIES": minidom.TypeInfo(None, "entities"),\r
- "ID": minidom.TypeInfo(None, "id"),\r
- "IDREF": minidom.TypeInfo(None, "idref"),\r
- "IDREFS": minidom.TypeInfo(None, "idrefs"),\r
- "NMTOKEN": minidom.TypeInfo(None, "nmtoken"),\r
- "NMTOKENS": minidom.TypeInfo(None, "nmtokens"),\r
- }\r
-\r
-class ElementInfo(object):\r
- __slots__ = '_attr_info', '_model', 'tagName'\r
-\r
- def __init__(self, tagName, model=None):\r
- self.tagName = tagName\r
- self._attr_info = []\r
- self._model = model\r
-\r
- def __getstate__(self):\r
- return self._attr_info, self._model, self.tagName\r
-\r
- def __setstate__(self, state):\r
- self._attr_info, self._model, self.tagName = state\r
-\r
- def getAttributeType(self, aname):\r
- for info in self._attr_info:\r
- if info[1] == aname:\r
- t = info[-2]\r
- if t[0] == "(":\r
- return _typeinfo_map["ENUM"]\r
- else:\r
- return _typeinfo_map[info[-2]]\r
- return minidom._no_type\r
-\r
- def getAttributeTypeNS(self, namespaceURI, localName):\r
- return minidom._no_type\r
-\r
- def isElementContent(self):\r
- if self._model:\r
- type = self._model[0]\r
- return type not in (expat.model.XML_CTYPE_ANY,\r
- expat.model.XML_CTYPE_MIXED)\r
- else:\r
- return False\r
-\r
- def isEmpty(self):\r
- if self._model:\r
- return self._model[0] == expat.model.XML_CTYPE_EMPTY\r
- else:\r
- return False\r
-\r
- def isId(self, aname):\r
- for info in self._attr_info:\r
- if info[1] == aname:\r
- return info[-2] == "ID"\r
- return False\r
-\r
- def isIdNS(self, euri, ename, auri, aname):\r
- # not sure this is meaningful\r
- return self.isId((auri, aname))\r
-\r
-def _intern(builder, s):\r
- return builder._intern_setdefault(s, s)\r
-\r
-def _parse_ns_name(builder, name):\r
- assert ' ' in name\r
- parts = name.split(' ')\r
- intern = builder._intern_setdefault\r
- if len(parts) == 3:\r
- uri, localname, prefix = parts\r
- prefix = intern(prefix, prefix)\r
- qname = "%s:%s" % (prefix, localname)\r
- qname = intern(qname, qname)\r
- localname = intern(localname, localname)\r
- else:\r
- uri, localname = parts\r
- prefix = EMPTY_PREFIX\r
- qname = localname = intern(localname, localname)\r
- return intern(uri, uri), localname, prefix, qname\r
-\r
-\r
-class ExpatBuilder:\r
- """Document builder that uses Expat to build a ParsedXML.DOM document\r
- instance."""\r
-\r
- def __init__(self, options=None):\r
- if options is None:\r
- options = xmlbuilder.Options()\r
- self._options = options\r
- if self._options.filter is not None:\r
- self._filter = FilterVisibilityController(self._options.filter)\r
- else:\r
- self._filter = None\r
- # This *really* doesn't do anything in this case, so\r
- # override it with something fast & minimal.\r
- self._finish_start_element = id\r
- self._parser = None\r
- self.reset()\r
-\r
- def createParser(self):\r
- """Create a new parser object."""\r
- return expat.ParserCreate()\r
-\r
- def getParser(self):\r
- """Return the parser object, creating a new one if needed."""\r
- if not self._parser:\r
- self._parser = self.createParser()\r
- self._intern_setdefault = self._parser.intern.setdefault\r
- self._parser.buffer_text = True\r
- self._parser.ordered_attributes = True\r
- self._parser.specified_attributes = True\r
- self.install(self._parser)\r
- return self._parser\r
-\r
- def reset(self):\r
- """Free all data structures used during DOM construction."""\r
- self.document = theDOMImplementation.createDocument(\r
- EMPTY_NAMESPACE, None, None)\r
- self.curNode = self.document\r
- self._elem_info = self.document._elem_info\r
- self._cdata = False\r
-\r
- def install(self, parser):\r
- """Install the callbacks needed to build the DOM into the parser."""\r
- # This creates circular references!\r
- parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler\r
- parser.StartElementHandler = self.first_element_handler\r
- parser.EndElementHandler = self.end_element_handler\r
- parser.ProcessingInstructionHandler = self.pi_handler\r
- if self._options.entities:\r
- parser.EntityDeclHandler = self.entity_decl_handler\r
- parser.NotationDeclHandler = self.notation_decl_handler\r
- if self._options.comments:\r
- parser.CommentHandler = self.comment_handler\r
- if self._options.cdata_sections:\r
- parser.StartCdataSectionHandler = self.start_cdata_section_handler\r
- parser.EndCdataSectionHandler = self.end_cdata_section_handler\r
- parser.CharacterDataHandler = self.character_data_handler_cdata\r
- else:\r
- parser.CharacterDataHandler = self.character_data_handler\r
- parser.ExternalEntityRefHandler = self.external_entity_ref_handler\r
- parser.XmlDeclHandler = self.xml_decl_handler\r
- parser.ElementDeclHandler = self.element_decl_handler\r
- parser.AttlistDeclHandler = self.attlist_decl_handler\r
-\r
- def parseFile(self, file):\r
- """Parse a document from a file object, returning the document\r
- node."""\r
- parser = self.getParser()\r
- first_buffer = True\r
- try:\r
- while 1:\r
- buffer = file.read(16*1024)\r
- if not buffer:\r
- break\r
- parser.Parse(buffer, 0)\r
- if first_buffer and self.document.documentElement:\r
- self._setup_subset(buffer)\r
- first_buffer = False\r
- parser.Parse("", True)\r
- except ParseEscape:\r
- pass\r
- doc = self.document\r
- self.reset()\r
- self._parser = None\r
- return doc\r
-\r
- def parseString(self, string):\r
- """Parse a document from a string, returning the document node."""\r
- parser = self.getParser()\r
- try:\r
- parser.Parse(string, True)\r
- self._setup_subset(string)\r
- except ParseEscape:\r
- pass\r
- doc = self.document\r
- self.reset()\r
- self._parser = None\r
- return doc\r
-\r
- def _setup_subset(self, buffer):\r
- """Load the internal subset if there might be one."""\r
- if self.document.doctype:\r
- extractor = InternalSubsetExtractor()\r
- extractor.parseString(buffer)\r
- subset = extractor.getSubset()\r
- self.document.doctype.internalSubset = subset\r
-\r
- def start_doctype_decl_handler(self, doctypeName, systemId, publicId,\r
- has_internal_subset):\r
- doctype = self.document.implementation.createDocumentType(\r
- doctypeName, publicId, systemId)\r
- doctype.ownerDocument = self.document\r
- _append_child(self.document, doctype)\r
- self.document.doctype = doctype\r
- if self._filter and self._filter.acceptNode(doctype) == FILTER_REJECT:\r
- self.document.doctype = None\r
- del self.document.childNodes[-1]\r
- doctype = None\r
- self._parser.EntityDeclHandler = None\r
- self._parser.NotationDeclHandler = None\r
- if has_internal_subset:\r
- if doctype is not None:\r
- doctype.entities._seq = []\r
- doctype.notations._seq = []\r
- self._parser.CommentHandler = None\r
- self._parser.ProcessingInstructionHandler = None\r
- self._parser.EndDoctypeDeclHandler = self.end_doctype_decl_handler\r
-\r
- def end_doctype_decl_handler(self):\r
- if self._options.comments:\r
- self._parser.CommentHandler = self.comment_handler\r
- self._parser.ProcessingInstructionHandler = self.pi_handler\r
- if not (self._elem_info or self._filter):\r
- self._finish_end_element = id\r
-\r
- def pi_handler(self, target, data):\r
- node = self.document.createProcessingInstruction(target, data)\r
- _append_child(self.curNode, node)\r
- if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:\r
- self.curNode.removeChild(node)\r
-\r
- def character_data_handler_cdata(self, data):\r
- childNodes = self.curNode.childNodes\r
- if self._cdata:\r
- if ( self._cdata_continue\r
- and childNodes[-1].nodeType == CDATA_SECTION_NODE):\r
- childNodes[-1].appendData(data)\r
- return\r
- node = self.document.createCDATASection(data)\r
- self._cdata_continue = True\r
- elif childNodes and childNodes[-1].nodeType == TEXT_NODE:\r
- node = childNodes[-1]\r
- value = node.data + data\r
- d = node.__dict__\r
- d['data'] = d['nodeValue'] = value\r
- return\r
- else:\r
- node = minidom.Text()\r
- d = node.__dict__\r
- d['data'] = d['nodeValue'] = data\r
- d['ownerDocument'] = self.document\r
- _append_child(self.curNode, node)\r
-\r
- def character_data_handler(self, data):\r
- childNodes = self.curNode.childNodes\r
- if childNodes and childNodes[-1].nodeType == TEXT_NODE:\r
- node = childNodes[-1]\r
- d = node.__dict__\r
- d['data'] = d['nodeValue'] = node.data + data\r
- return\r
- node = minidom.Text()\r
- d = node.__dict__\r
- d['data'] = d['nodeValue'] = node.data + data\r
- d['ownerDocument'] = self.document\r
- _append_child(self.curNode, node)\r
-\r
- def entity_decl_handler(self, entityName, is_parameter_entity, value,\r
- base, systemId, publicId, notationName):\r
- if is_parameter_entity:\r
- # we don't care about parameter entities for the DOM\r
- return\r
- if not self._options.entities:\r
- return\r
- node = self.document._create_entity(entityName, publicId,\r
- systemId, notationName)\r
- if value is not None:\r
- # internal entity\r
- # node *should* be readonly, but we'll cheat\r
- child = self.document.createTextNode(value)\r
- node.childNodes.append(child)\r
- self.document.doctype.entities._seq.append(node)\r
- if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:\r
- del self.document.doctype.entities._seq[-1]\r
-\r
- def notation_decl_handler(self, notationName, base, systemId, publicId):\r
- node = self.document._create_notation(notationName, publicId, systemId)\r
- self.document.doctype.notations._seq.append(node)\r
- if self._filter and self._filter.acceptNode(node) == FILTER_ACCEPT:\r
- del self.document.doctype.notations._seq[-1]\r
-\r
- def comment_handler(self, data):\r
- node = self.document.createComment(data)\r
- _append_child(self.curNode, node)\r
- if self._filter and self._filter.acceptNode(node) == FILTER_REJECT:\r
- self.curNode.removeChild(node)\r
-\r
- def start_cdata_section_handler(self):\r
- self._cdata = True\r
- self._cdata_continue = False\r
-\r
- def end_cdata_section_handler(self):\r
- self._cdata = False\r
- self._cdata_continue = False\r
-\r
- def external_entity_ref_handler(self, context, base, systemId, publicId):\r
- return 1\r
-\r
- def first_element_handler(self, name, attributes):\r
- if self._filter is None and not self._elem_info:\r
- self._finish_end_element = id\r
- self.getParser().StartElementHandler = self.start_element_handler\r
- self.start_element_handler(name, attributes)\r
-\r
- def start_element_handler(self, name, attributes):\r
- node = self.document.createElement(name)\r
- _append_child(self.curNode, node)\r
- self.curNode = node\r
-\r
- if attributes:\r
- for i in range(0, len(attributes), 2):\r
- a = minidom.Attr(attributes[i], EMPTY_NAMESPACE,\r
- None, EMPTY_PREFIX)\r
- value = attributes[i+1]\r
- d = a.childNodes[0].__dict__\r
- d['data'] = d['nodeValue'] = value\r
- d = a.__dict__\r
- d['value'] = d['nodeValue'] = value\r
- d['ownerDocument'] = self.document\r
- _set_attribute_node(node, a)\r
-\r
- if node is not self.document.documentElement:\r
- self._finish_start_element(node)\r
-\r
- def _finish_start_element(self, node):\r
- if self._filter:\r
- # To be general, we'd have to call isSameNode(), but this\r
- # is sufficient for minidom:\r
- if node is self.document.documentElement:\r
- return\r
- filt = self._filter.startContainer(node)\r
- if filt == FILTER_REJECT:\r
- # ignore this node & all descendents\r
- Rejecter(self)\r
- elif filt == FILTER_SKIP:\r
- # ignore this node, but make it's children become\r
- # children of the parent node\r
- Skipper(self)\r
- else:\r
- return\r
- self.curNode = node.parentNode\r
- node.parentNode.removeChild(node)\r
- node.unlink()\r
-\r
- # If this ever changes, Namespaces.end_element_handler() needs to\r
- # be changed to match.\r
- #\r
- def end_element_handler(self, name):\r
- curNode = self.curNode\r
- self.curNode = curNode.parentNode\r
- self._finish_end_element(curNode)\r
-\r
- def _finish_end_element(self, curNode):\r
- info = self._elem_info.get(curNode.tagName)\r
- if info:\r
- self._handle_white_text_nodes(curNode, info)\r
- if self._filter:\r
- if curNode is self.document.documentElement:\r
- return\r
- if self._filter.acceptNode(curNode) == FILTER_REJECT:\r
- self.curNode.removeChild(curNode)\r
- curNode.unlink()\r
-\r
- def _handle_white_text_nodes(self, node, info):\r
- if (self._options.whitespace_in_element_content\r
- or not info.isElementContent()):\r
- return\r
-\r
- # We have element type information and should remove ignorable\r
- # whitespace; identify for text nodes which contain only\r
- # whitespace.\r
- L = []\r
- for child in node.childNodes:\r
- if child.nodeType == TEXT_NODE and not child.data.strip():\r
- L.append(child)\r
-\r
- # Remove ignorable whitespace from the tree.\r
- for child in L:\r
- node.removeChild(child)\r
-\r
- def element_decl_handler(self, name, model):\r
- info = self._elem_info.get(name)\r
- if info is None:\r
- self._elem_info[name] = ElementInfo(name, model)\r
- else:\r
- assert info._model is None\r
- info._model = model\r
-\r
- def attlist_decl_handler(self, elem, name, type, default, required):\r
- info = self._elem_info.get(elem)\r
- if info is None:\r
- info = ElementInfo(elem)\r
- self._elem_info[elem] = info\r
- info._attr_info.append(\r
- [None, name, None, None, default, 0, type, required])\r
-\r
- def xml_decl_handler(self, version, encoding, standalone):\r
- self.document.version = version\r
- self.document.encoding = encoding\r
- # This is still a little ugly, thanks to the pyexpat API. ;-(\r
- if standalone >= 0:\r
- if standalone:\r
- self.document.standalone = True\r
- else:\r
- self.document.standalone = False\r
-\r
-\r
-# Don't include FILTER_INTERRUPT, since that's checked separately\r
-# where allowed.\r
-_ALLOWED_FILTER_RETURNS = (FILTER_ACCEPT, FILTER_REJECT, FILTER_SKIP)\r
-\r
-class FilterVisibilityController(object):\r
- """Wrapper around a DOMBuilderFilter which implements the checks\r
- to make the whatToShow filter attribute work."""\r
-\r
- __slots__ = 'filter',\r
-\r
- def __init__(self, filter):\r
- self.filter = filter\r
-\r
- def startContainer(self, node):\r
- mask = self._nodetype_mask[node.nodeType]\r
- if self.filter.whatToShow & mask:\r
- val = self.filter.startContainer(node)\r
- if val == FILTER_INTERRUPT:\r
- raise ParseEscape\r
- if val not in _ALLOWED_FILTER_RETURNS:\r
- raise ValueError, \\r
- "startContainer() returned illegal value: " + repr(val)\r
- return val\r
- else:\r
- return FILTER_ACCEPT\r
-\r
- def acceptNode(self, node):\r
- mask = self._nodetype_mask[node.nodeType]\r
- if self.filter.whatToShow & mask:\r
- val = self.filter.acceptNode(node)\r
- if val == FILTER_INTERRUPT:\r
- raise ParseEscape\r
- if val == FILTER_SKIP:\r
- # move all child nodes to the parent, and remove this node\r
- parent = node.parentNode\r
- for child in node.childNodes[:]:\r
- parent.appendChild(child)\r
- # node is handled by the caller\r
- return FILTER_REJECT\r
- if val not in _ALLOWED_FILTER_RETURNS:\r
- raise ValueError, \\r
- "acceptNode() returned illegal value: " + repr(val)\r
- return val\r
- else:\r
- return FILTER_ACCEPT\r
-\r
- _nodetype_mask = {\r
- Node.ELEMENT_NODE: NodeFilter.SHOW_ELEMENT,\r
- Node.ATTRIBUTE_NODE: NodeFilter.SHOW_ATTRIBUTE,\r
- Node.TEXT_NODE: NodeFilter.SHOW_TEXT,\r
- Node.CDATA_SECTION_NODE: NodeFilter.SHOW_CDATA_SECTION,\r
- Node.ENTITY_REFERENCE_NODE: NodeFilter.SHOW_ENTITY_REFERENCE,\r
- Node.ENTITY_NODE: NodeFilter.SHOW_ENTITY,\r
- Node.PROCESSING_INSTRUCTION_NODE: NodeFilter.SHOW_PROCESSING_INSTRUCTION,\r
- Node.COMMENT_NODE: NodeFilter.SHOW_COMMENT,\r
- Node.DOCUMENT_NODE: NodeFilter.SHOW_DOCUMENT,\r
- Node.DOCUMENT_TYPE_NODE: NodeFilter.SHOW_DOCUMENT_TYPE,\r
- Node.DOCUMENT_FRAGMENT_NODE: NodeFilter.SHOW_DOCUMENT_FRAGMENT,\r
- Node.NOTATION_NODE: NodeFilter.SHOW_NOTATION,\r
- }\r
-\r
-\r
-class FilterCrutch(object):\r
- __slots__ = '_builder', '_level', '_old_start', '_old_end'\r
-\r
- def __init__(self, builder):\r
- self._level = 0\r
- self._builder = builder\r
- parser = builder._parser\r
- self._old_start = parser.StartElementHandler\r
- self._old_end = parser.EndElementHandler\r
- parser.StartElementHandler = self.start_element_handler\r
- parser.EndElementHandler = self.end_element_handler\r
-\r
-class Rejecter(FilterCrutch):\r
- __slots__ = ()\r
-\r
- def __init__(self, builder):\r
- FilterCrutch.__init__(self, builder)\r
- parser = builder._parser\r
- for name in ("ProcessingInstructionHandler",\r
- "CommentHandler",\r
- "CharacterDataHandler",\r
- "StartCdataSectionHandler",\r
- "EndCdataSectionHandler",\r
- "ExternalEntityRefHandler",\r
- ):\r
- setattr(parser, name, None)\r
-\r
- def start_element_handler(self, *args):\r
- self._level = self._level + 1\r
-\r
- def end_element_handler(self, *args):\r
- if self._level == 0:\r
- # restore the old handlers\r
- parser = self._builder._parser\r
- self._builder.install(parser)\r
- parser.StartElementHandler = self._old_start\r
- parser.EndElementHandler = self._old_end\r
- else:\r
- self._level = self._level - 1\r
-\r
-class Skipper(FilterCrutch):\r
- __slots__ = ()\r
-\r
- def start_element_handler(self, *args):\r
- node = self._builder.curNode\r
- self._old_start(*args)\r
- if self._builder.curNode is not node:\r
- self._level = self._level + 1\r
-\r
- def end_element_handler(self, *args):\r
- if self._level == 0:\r
- # We're popping back out of the node we're skipping, so we\r
- # shouldn't need to do anything but reset the handlers.\r
- self._builder._parser.StartElementHandler = self._old_start\r
- self._builder._parser.EndElementHandler = self._old_end\r
- self._builder = None\r
- else:\r
- self._level = self._level - 1\r
- self._old_end(*args)\r
-\r
-\r
-# framework document used by the fragment builder.\r
-# Takes a string for the doctype, subset string, and namespace attrs string.\r
-\r
-_FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID = \\r
- "http://xml.python.org/entities/fragment-builder/internal"\r
-\r
-_FRAGMENT_BUILDER_TEMPLATE = (\r
- '''\\r
-<!DOCTYPE wrapper\r
- %%s [\r
- <!ENTITY fragment-builder-internal\r
- SYSTEM "%s">\r
-%%s\r
-]>\r
-<wrapper %%s\r
->&fragment-builder-internal;</wrapper>'''\r
- % _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID)\r
-\r
-\r
-class FragmentBuilder(ExpatBuilder):\r
- """Builder which constructs document fragments given XML source\r
- text and a context node.\r
-\r
- The context node is expected to provide information about the\r
- namespace declarations which are in scope at the start of the\r
- fragment.\r
- """\r
-\r
- def __init__(self, context, options=None):\r
- if context.nodeType == DOCUMENT_NODE:\r
- self.originalDocument = context\r
- self.context = context\r
- else:\r
- self.originalDocument = context.ownerDocument\r
- self.context = context\r
- ExpatBuilder.__init__(self, options)\r
-\r
- def reset(self):\r
- ExpatBuilder.reset(self)\r
- self.fragment = None\r
-\r
- def parseFile(self, file):\r
- """Parse a document fragment from a file object, returning the\r
- fragment node."""\r
- return self.parseString(file.read())\r
-\r
- def parseString(self, string):\r
- """Parse a document fragment from a string, returning the\r
- fragment node."""\r
- self._source = string\r
- parser = self.getParser()\r
- doctype = self.originalDocument.doctype\r
- ident = ""\r
- if doctype:\r
- subset = doctype.internalSubset or self._getDeclarations()\r
- if doctype.publicId:\r
- ident = ('PUBLIC "%s" "%s"'\r
- % (doctype.publicId, doctype.systemId))\r
- elif doctype.systemId:\r
- ident = 'SYSTEM "%s"' % doctype.systemId\r
- else:\r
- subset = ""\r
- nsattrs = self._getNSattrs() # get ns decls from node's ancestors\r
- document = _FRAGMENT_BUILDER_TEMPLATE % (ident, subset, nsattrs)\r
- try:\r
- parser.Parse(document, 1)\r
- except:\r
- self.reset()\r
- raise\r
- fragment = self.fragment\r
- self.reset()\r
-## self._parser = None\r
- return fragment\r
-\r
- def _getDeclarations(self):\r
- """Re-create the internal subset from the DocumentType node.\r
-\r
- This is only needed if we don't already have the\r
- internalSubset as a string.\r
- """\r
- doctype = self.context.ownerDocument.doctype\r
- s = ""\r
- if doctype:\r
- for i in range(doctype.notations.length):\r
- notation = doctype.notations.item(i)\r
- if s:\r
- s = s + "\n "\r
- s = "%s<!NOTATION %s" % (s, notation.nodeName)\r
- if notation.publicId:\r
- s = '%s PUBLIC "%s"\n "%s">' \\r
- % (s, notation.publicId, notation.systemId)\r
- else:\r
- s = '%s SYSTEM "%s">' % (s, notation.systemId)\r
- for i in range(doctype.entities.length):\r
- entity = doctype.entities.item(i)\r
- if s:\r
- s = s + "\n "\r
- s = "%s<!ENTITY %s" % (s, entity.nodeName)\r
- if entity.publicId:\r
- s = '%s PUBLIC "%s"\n "%s"' \\r
- % (s, entity.publicId, entity.systemId)\r
- elif entity.systemId:\r
- s = '%s SYSTEM "%s"' % (s, entity.systemId)\r
- else:\r
- s = '%s "%s"' % (s, entity.firstChild.data)\r
- if entity.notationName:\r
- s = "%s NOTATION %s" % (s, entity.notationName)\r
- s = s + ">"\r
- return s\r
-\r
- def _getNSattrs(self):\r
- return ""\r
-\r
- def external_entity_ref_handler(self, context, base, systemId, publicId):\r
- if systemId == _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID:\r
- # this entref is the one that we made to put the subtree\r
- # in; all of our given input is parsed in here.\r
- old_document = self.document\r
- old_cur_node = self.curNode\r
- parser = self._parser.ExternalEntityParserCreate(context)\r
- # put the real document back, parse into the fragment to return\r
- self.document = self.originalDocument\r
- self.fragment = self.document.createDocumentFragment()\r
- self.curNode = self.fragment\r
- try:\r
- parser.Parse(self._source, 1)\r
- finally:\r
- self.curNode = old_cur_node\r
- self.document = old_document\r
- self._source = None\r
- return -1\r
- else:\r
- return ExpatBuilder.external_entity_ref_handler(\r
- self, context, base, systemId, publicId)\r
-\r
-\r
-class Namespaces:\r
- """Mix-in class for builders; adds support for namespaces."""\r
-\r
- def _initNamespaces(self):\r
- # list of (prefix, uri) ns declarations. Namespace attrs are\r
- # constructed from this and added to the element's attrs.\r
- self._ns_ordered_prefixes = []\r
-\r
- def createParser(self):\r
- """Create a new namespace-handling parser."""\r
- parser = expat.ParserCreate(namespace_separator=" ")\r
- parser.namespace_prefixes = True\r
- return parser\r
-\r
- def install(self, parser):\r
- """Insert the namespace-handlers onto the parser."""\r
- ExpatBuilder.install(self, parser)\r
- if self._options.namespace_declarations:\r
- parser.StartNamespaceDeclHandler = (\r
- self.start_namespace_decl_handler)\r
-\r
- def start_namespace_decl_handler(self, prefix, uri):\r
- """Push this namespace declaration on our storage."""\r
- self._ns_ordered_prefixes.append((prefix, uri))\r
-\r
- def start_element_handler(self, name, attributes):\r
- if ' ' in name:\r
- uri, localname, prefix, qname = _parse_ns_name(self, name)\r
- else:\r
- uri = EMPTY_NAMESPACE\r
- qname = name\r
- localname = None\r
- prefix = EMPTY_PREFIX\r
- node = minidom.Element(qname, uri, prefix, localname)\r
- node.ownerDocument = self.document\r
- _append_child(self.curNode, node)\r
- self.curNode = node\r
-\r
- if self._ns_ordered_prefixes:\r
- for prefix, uri in self._ns_ordered_prefixes:\r
- if prefix:\r
- a = minidom.Attr(_intern(self, 'xmlns:' + prefix),\r
- XMLNS_NAMESPACE, prefix, "xmlns")\r
- else:\r
- a = minidom.Attr("xmlns", XMLNS_NAMESPACE,\r
- "xmlns", EMPTY_PREFIX)\r
- d = a.childNodes[0].__dict__\r
- d['data'] = d['nodeValue'] = uri\r
- d = a.__dict__\r
- d['value'] = d['nodeValue'] = uri\r
- d['ownerDocument'] = self.document\r
- _set_attribute_node(node, a)\r
- del self._ns_ordered_prefixes[:]\r
-\r
- if attributes:\r
- _attrs = node._attrs\r
- _attrsNS = node._attrsNS\r
- for i in range(0, len(attributes), 2):\r
- aname = attributes[i]\r
- value = attributes[i+1]\r
- if ' ' in aname:\r
- uri, localname, prefix, qname = _parse_ns_name(self, aname)\r
- a = minidom.Attr(qname, uri, localname, prefix)\r
- _attrs[qname] = a\r
- _attrsNS[(uri, localname)] = a\r
- else:\r
- a = minidom.Attr(aname, EMPTY_NAMESPACE,\r
- aname, EMPTY_PREFIX)\r
- _attrs[aname] = a\r
- _attrsNS[(EMPTY_NAMESPACE, aname)] = a\r
- d = a.childNodes[0].__dict__\r
- d['data'] = d['nodeValue'] = value\r
- d = a.__dict__\r
- d['ownerDocument'] = self.document\r
- d['value'] = d['nodeValue'] = value\r
- d['ownerElement'] = node\r
-\r
- if __debug__:\r
- # This only adds some asserts to the original\r
- # end_element_handler(), so we only define this when -O is not\r
- # used. If changing one, be sure to check the other to see if\r
- # it needs to be changed as well.\r
- #\r
- def end_element_handler(self, name):\r
- curNode = self.curNode\r
- if ' ' in name:\r
- uri, localname, prefix, qname = _parse_ns_name(self, name)\r
- assert (curNode.namespaceURI == uri\r
- and curNode.localName == localname\r
- and curNode.prefix == prefix), \\r
- "element stack messed up! (namespace)"\r
- else:\r
- assert curNode.nodeName == name, \\r
- "element stack messed up - bad nodeName"\r
- assert curNode.namespaceURI == EMPTY_NAMESPACE, \\r
- "element stack messed up - bad namespaceURI"\r
- self.curNode = curNode.parentNode\r
- self._finish_end_element(curNode)\r
-\r
-\r
-class ExpatBuilderNS(Namespaces, ExpatBuilder):\r
- """Document builder that supports namespaces."""\r
-\r
- def reset(self):\r
- ExpatBuilder.reset(self)\r
- self._initNamespaces()\r
-\r
-\r
-class FragmentBuilderNS(Namespaces, FragmentBuilder):\r
- """Fragment builder that supports namespaces."""\r
-\r
- def reset(self):\r
- FragmentBuilder.reset(self)\r
- self._initNamespaces()\r
-\r
- def _getNSattrs(self):\r
- """Return string of namespace attributes from this element and\r
- ancestors."""\r
- # XXX This needs to be re-written to walk the ancestors of the\r
- # context to build up the namespace information from\r
- # declarations, elements, and attributes found in context.\r
- # Otherwise we have to store a bunch more data on the DOM\r
- # (though that *might* be more reliable -- not clear).\r
- attrs = ""\r
- context = self.context\r
- L = []\r
- while context:\r
- if hasattr(context, '_ns_prefix_uri'):\r
- for prefix, uri in context._ns_prefix_uri.items():\r
- # add every new NS decl from context to L and attrs string\r
- if prefix in L:\r
- continue\r
- L.append(prefix)\r
- if prefix:\r
- declname = "xmlns:" + prefix\r
- else:\r
- declname = "xmlns"\r
- if attrs:\r
- attrs = "%s\n %s='%s'" % (attrs, declname, uri)\r
- else:\r
- attrs = " %s='%s'" % (declname, uri)\r
- context = context.parentNode\r
- return attrs\r
-\r
-\r
-class ParseEscape(Exception):\r
- """Exception raised to short-circuit parsing in InternalSubsetExtractor."""\r
- pass\r
-\r
-class InternalSubsetExtractor(ExpatBuilder):\r
- """XML processor which can rip out the internal document type subset."""\r
-\r
- subset = None\r
-\r
- def getSubset(self):\r
- """Return the internal subset as a string."""\r
- return self.subset\r
-\r
- def parseFile(self, file):\r
- try:\r
- ExpatBuilder.parseFile(self, file)\r
- except ParseEscape:\r
- pass\r
-\r
- def parseString(self, string):\r
- try:\r
- ExpatBuilder.parseString(self, string)\r
- except ParseEscape:\r
- pass\r
-\r
- def install(self, parser):\r
- parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler\r
- parser.StartElementHandler = self.start_element_handler\r
-\r
- def start_doctype_decl_handler(self, name, publicId, systemId,\r
- has_internal_subset):\r
- if has_internal_subset:\r
- parser = self.getParser()\r
- self.subset = []\r
- parser.DefaultHandler = self.subset.append\r
- parser.EndDoctypeDeclHandler = self.end_doctype_decl_handler\r
- else:\r
- raise ParseEscape()\r
-\r
- def end_doctype_decl_handler(self):\r
- s = ''.join(self.subset).replace('\r\n', '\n').replace('\r', '\n')\r
- self.subset = s\r
- raise ParseEscape()\r
-\r
- def start_element_handler(self, name, attrs):\r
- raise ParseEscape()\r
-\r
-\r
-def parse(file, namespaces=True):\r
- """Parse a document, returning the resulting Document node.\r
-\r
- 'file' may be either a file name or an open file object.\r
- """\r
- if namespaces:\r
- builder = ExpatBuilderNS()\r
- else:\r
- builder = ExpatBuilder()\r
-\r
- if isinstance(file, StringTypes):\r
- fp = open(file, 'rb')\r
- try:\r
- result = builder.parseFile(fp)\r
- finally:\r
- fp.close()\r
- else:\r
- result = builder.parseFile(file)\r
- return result\r
-\r
-\r
-def parseString(string, namespaces=True):\r
- """Parse a document from a string, returning the resulting\r
- Document node.\r
- """\r
- if namespaces:\r
- builder = ExpatBuilderNS()\r
- else:\r
- builder = ExpatBuilder()\r
- return builder.parseString(string)\r
-\r
-\r
-def parseFragment(file, context, namespaces=True):\r
- """Parse a fragment of a document, given the context from which it\r
- was originally extracted. context should be the parent of the\r
- node(s) which are in the fragment.\r
-\r
- 'file' may be either a file name or an open file object.\r
- """\r
- if namespaces:\r
- builder = FragmentBuilderNS(context)\r
- else:\r
- builder = FragmentBuilder(context)\r
-\r
- if isinstance(file, StringTypes):\r
- fp = open(file, 'rb')\r
- try:\r
- result = builder.parseFile(fp)\r
- finally:\r
- fp.close()\r
- else:\r
- result = builder.parseFile(file)\r
- return result\r
-\r
-\r
-def parseFragmentString(string, context, namespaces=True):\r
- """Parse a fragment of a document from a string, given the context\r
- from which it was originally extracted. context should be the\r
- parent of the node(s) which are in the fragment.\r
- """\r
- if namespaces:\r
- builder = FragmentBuilderNS(context)\r
- else:\r
- builder = FragmentBuilder(context)\r
- return builder.parseString(string)\r
-\r
-\r
-def makeBuilder(options):\r
- """Create a builder based on an Options object."""\r
- if options.namespaces:\r
- return ExpatBuilderNS(options)\r
- else:\r
- return ExpatBuilder(options)\r