Show More
Commit Description:
Merge pull request #17 from nattee/master...
Commit Description:
Merge pull request #17 from nattee/master
upgrade to current working snapshot
References:
File last commit:
Show/Diff file:
Action:
lib/assets/Lib/xml/dom/minidom.py
| 1986 lines
| 67.2 KiB
| text/x-python
| PythonLexer
|
r584 | """Simple implementation of the Level 1 DOM. | |||
Namespaces and other minor Level 2 features are also supported. | ||||
parse("foo.xml") | ||||
parseString("<foo><bar/></foo>") | ||||
Todo: | ||||
===== | ||||
* convenience methods for getting elements and text. | ||||
* more testing | ||||
* bring some of the writer and linearizer code into conformance with this | ||||
interface | ||||
* SAX 2 namespaces | ||||
""" | ||||
import io | ||||
import xml.dom | ||||
from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE, domreg | ||||
from xml.dom.minicompat import * | ||||
from xml.dom.xmlbuilder import DOMImplementationLS, DocumentLS | ||||
# This is used by the ID-cache invalidation checks; the list isn't | ||||
# actually complete, since the nodes being checked will never be the | ||||
# DOCUMENT_NODE or DOCUMENT_FRAGMENT_NODE. (The node being checked is | ||||
# the node being added or removed, not the node being modified.) | ||||
# | ||||
_nodeTypes_with_children = (xml.dom.Node.ELEMENT_NODE, | ||||
xml.dom.Node.ENTITY_REFERENCE_NODE) | ||||
class Node(xml.dom.Node): | ||||
namespaceURI = None # this is non-null only for elements and attributes | ||||
parentNode = None | ||||
ownerDocument = None | ||||
nextSibling = None | ||||
previousSibling = None | ||||
prefix = EMPTY_PREFIX # non-null only for NS elements and attributes | ||||
def __bool__(self): | ||||
return True | ||||
def toxml(self, encoding=None): | ||||
return self.toprettyxml("", "", encoding) | ||||
def toprettyxml(self, indent="\t", newl="\n", encoding=None): | ||||
if encoding is None: | ||||
writer = io.StringIO() | ||||
else: | ||||
writer = io.TextIOWrapper(io.BytesIO(), | ||||
encoding=encoding, | ||||
errors="xmlcharrefreplace", | ||||
newline='\n') | ||||
if self.nodeType == Node.DOCUMENT_NODE: | ||||
# Can pass encoding only to document, to put it into XML header | ||||
self.writexml(writer, "", indent, newl, encoding) | ||||
else: | ||||
self.writexml(writer, "", indent, newl) | ||||
if encoding is None: | ||||
return writer.getvalue() | ||||
else: | ||||
return writer.detach().getvalue() | ||||
def hasChildNodes(self): | ||||
return bool(self.childNodes) | ||||
def _get_childNodes(self): | ||||
return self.childNodes | ||||
def _get_firstChild(self): | ||||
if self.childNodes: | ||||
return self.childNodes[0] | ||||
def _get_lastChild(self): | ||||
if self.childNodes: | ||||
return self.childNodes[-1] | ||||
def insertBefore(self, newChild, refChild): | ||||
if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: | ||||
for c in tuple(newChild.childNodes): | ||||
self.insertBefore(c, refChild) | ||||
### The DOM does not clearly specify what to return in this case | ||||
return newChild | ||||
if newChild.nodeType not in self._child_node_types: | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"%s cannot be child of %s" % (repr(newChild), repr(self))) | ||||
if newChild.parentNode is not None: | ||||
newChild.parentNode.removeChild(newChild) | ||||
if refChild is None: | ||||
self.appendChild(newChild) | ||||
else: | ||||
try: | ||||
index = self.childNodes.index(refChild) | ||||
except ValueError: | ||||
raise xml.dom.NotFoundErr() | ||||
if newChild.nodeType in _nodeTypes_with_children: | ||||
_clear_id_cache(self) | ||||
self.childNodes.insert(index, newChild) | ||||
newChild.nextSibling = refChild | ||||
refChild.previousSibling = newChild | ||||
if index: | ||||
node = self.childNodes[index-1] | ||||
node.nextSibling = newChild | ||||
newChild.previousSibling = node | ||||
else: | ||||
newChild.previousSibling = None | ||||
newChild.parentNode = self | ||||
return newChild | ||||
def appendChild(self, node): | ||||
if node.nodeType == self.DOCUMENT_FRAGMENT_NODE: | ||||
for c in tuple(node.childNodes): | ||||
self.appendChild(c) | ||||
### The DOM does not clearly specify what to return in this case | ||||
return node | ||||
if node.nodeType not in self._child_node_types: | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"%s cannot be child of %s" % (repr(node), repr(self))) | ||||
elif node.nodeType in _nodeTypes_with_children: | ||||
_clear_id_cache(self) | ||||
if node.parentNode is not None: | ||||
node.parentNode.removeChild(node) | ||||
_append_child(self, node) | ||||
node.nextSibling = None | ||||
return node | ||||
def replaceChild(self, newChild, oldChild): | ||||
if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: | ||||
refChild = oldChild.nextSibling | ||||
self.removeChild(oldChild) | ||||
return self.insertBefore(newChild, refChild) | ||||
if newChild.nodeType not in self._child_node_types: | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"%s cannot be child of %s" % (repr(newChild), repr(self))) | ||||
if newChild is oldChild: | ||||
return | ||||
if newChild.parentNode is not None: | ||||
newChild.parentNode.removeChild(newChild) | ||||
try: | ||||
index = self.childNodes.index(oldChild) | ||||
except ValueError: | ||||
raise xml.dom.NotFoundErr() | ||||
self.childNodes[index] = newChild | ||||
newChild.parentNode = self | ||||
oldChild.parentNode = None | ||||
if (newChild.nodeType in _nodeTypes_with_children | ||||
or oldChild.nodeType in _nodeTypes_with_children): | ||||
_clear_id_cache(self) | ||||
newChild.nextSibling = oldChild.nextSibling | ||||
newChild.previousSibling = oldChild.previousSibling | ||||
oldChild.nextSibling = None | ||||
oldChild.previousSibling = None | ||||
if newChild.previousSibling: | ||||
newChild.previousSibling.nextSibling = newChild | ||||
if newChild.nextSibling: | ||||
newChild.nextSibling.previousSibling = newChild | ||||
return oldChild | ||||
def removeChild(self, oldChild): | ||||
try: | ||||
self.childNodes.remove(oldChild) | ||||
except ValueError: | ||||
raise xml.dom.NotFoundErr() | ||||
if oldChild.nextSibling is not None: | ||||
oldChild.nextSibling.previousSibling = oldChild.previousSibling | ||||
if oldChild.previousSibling is not None: | ||||
oldChild.previousSibling.nextSibling = oldChild.nextSibling | ||||
oldChild.nextSibling = oldChild.previousSibling = None | ||||
if oldChild.nodeType in _nodeTypes_with_children: | ||||
_clear_id_cache(self) | ||||
oldChild.parentNode = None | ||||
return oldChild | ||||
def normalize(self): | ||||
L = [] | ||||
for child in self.childNodes: | ||||
if child.nodeType == Node.TEXT_NODE: | ||||
if not child.data: | ||||
# empty text node; discard | ||||
if L: | ||||
L[-1].nextSibling = child.nextSibling | ||||
if child.nextSibling: | ||||
child.nextSibling.previousSibling = child.previousSibling | ||||
child.unlink() | ||||
elif L and L[-1].nodeType == child.nodeType: | ||||
# collapse text node | ||||
node = L[-1] | ||||
node.data = node.data + child.data | ||||
node.nextSibling = child.nextSibling | ||||
if child.nextSibling: | ||||
child.nextSibling.previousSibling = node | ||||
child.unlink() | ||||
else: | ||||
L.append(child) | ||||
else: | ||||
L.append(child) | ||||
if child.nodeType == Node.ELEMENT_NODE: | ||||
child.normalize() | ||||
self.childNodes[:] = L | ||||
def cloneNode(self, deep): | ||||
return _clone_node(self, deep, self.ownerDocument or self) | ||||
def isSupported(self, feature, version): | ||||
return self.ownerDocument.implementation.hasFeature(feature, version) | ||||
def _get_localName(self): | ||||
# Overridden in Element and Attr where localName can be Non-Null | ||||
return None | ||||
# Node interfaces from Level 3 (WD 9 April 2002) | ||||
def isSameNode(self, other): | ||||
return self is other | ||||
def getInterface(self, feature): | ||||
if self.isSupported(feature, None): | ||||
return self | ||||
else: | ||||
return None | ||||
# The "user data" functions use a dictionary that is only present | ||||
# if some user data has been set, so be careful not to assume it | ||||
# exists. | ||||
def getUserData(self, key): | ||||
try: | ||||
return self._user_data[key][0] | ||||
except (AttributeError, KeyError): | ||||
return None | ||||
def setUserData(self, key, data, handler): | ||||
old = None | ||||
try: | ||||
d = self._user_data | ||||
except AttributeError: | ||||
d = {} | ||||
self._user_data = d | ||||
if key in d: | ||||
old = d[key][0] | ||||
if data is None: | ||||
# ignore handlers passed for None | ||||
handler = None | ||||
if old is not None: | ||||
del d[key] | ||||
else: | ||||
d[key] = (data, handler) | ||||
return old | ||||
def _call_user_data_handler(self, operation, src, dst): | ||||
if hasattr(self, "_user_data"): | ||||
for key, (data, handler) in list(self._user_data.items()): | ||||
if handler is not None: | ||||
handler.handle(operation, key, data, src, dst) | ||||
# minidom-specific API: | ||||
def unlink(self): | ||||
self.parentNode = self.ownerDocument = None | ||||
if self.childNodes: | ||||
for child in self.childNodes: | ||||
child.unlink() | ||||
self.childNodes = NodeList() | ||||
self.previousSibling = None | ||||
self.nextSibling = None | ||||
# A Node is its own context manager, to ensure that an unlink() call occurs. | ||||
# This is similar to how a file object works. | ||||
def __enter__(self): | ||||
return self | ||||
def __exit__(self, et, ev, tb): | ||||
self.unlink() | ||||
defproperty(Node, "firstChild", doc="First child node, or None.") | ||||
defproperty(Node, "lastChild", doc="Last child node, or None.") | ||||
defproperty(Node, "localName", doc="Namespace-local name of this node.") | ||||
def _append_child(self, node): | ||||
# fast path with less checks; usable by DOM builders if careful | ||||
childNodes = self.childNodes | ||||
if childNodes: | ||||
last = childNodes[-1] | ||||
node.previousSibling = last | ||||
last.nextSibling = node | ||||
childNodes.append(node) | ||||
node.parentNode = self | ||||
def _in_document(node): | ||||
# return True iff node is part of a document tree | ||||
while node is not None: | ||||
if node.nodeType == Node.DOCUMENT_NODE: | ||||
return True | ||||
node = node.parentNode | ||||
return False | ||||
def _write_data(writer, data): | ||||
"Writes datachars to writer." | ||||
if data: | ||||
data = data.replace("&", "&").replace("<", "<"). \ | ||||
replace("\"", """).replace(">", ">") | ||||
writer.write(data) | ||||
def _get_elements_by_tagName_helper(parent, name, rc): | ||||
for node in parent.childNodes: | ||||
if node.nodeType == Node.ELEMENT_NODE and \ | ||||
(name == "*" or node.tagName == name): | ||||
rc.append(node) | ||||
_get_elements_by_tagName_helper(node, name, rc) | ||||
return rc | ||||
def _get_elements_by_tagName_ns_helper(parent, nsURI, localName, rc): | ||||
for node in parent.childNodes: | ||||
if node.nodeType == Node.ELEMENT_NODE: | ||||
if ((localName == "*" or node.localName == localName) and | ||||
(nsURI == "*" or node.namespaceURI == nsURI)): | ||||
rc.append(node) | ||||
_get_elements_by_tagName_ns_helper(node, nsURI, localName, rc) | ||||
return rc | ||||
class DocumentFragment(Node): | ||||
nodeType = Node.DOCUMENT_FRAGMENT_NODE | ||||
nodeName = "#document-fragment" | ||||
nodeValue = None | ||||
attributes = None | ||||
parentNode = None | ||||
_child_node_types = (Node.ELEMENT_NODE, | ||||
Node.TEXT_NODE, | ||||
Node.CDATA_SECTION_NODE, | ||||
Node.ENTITY_REFERENCE_NODE, | ||||
Node.PROCESSING_INSTRUCTION_NODE, | ||||
Node.COMMENT_NODE, | ||||
Node.NOTATION_NODE) | ||||
def __init__(self): | ||||
self.childNodes = NodeList() | ||||
class Attr(Node): | ||||
__slots__=('_name', '_value', 'namespaceURI', | ||||
'_prefix', 'childNodes', '_localName', 'ownerDocument', 'ownerElement') | ||||
nodeType = Node.ATTRIBUTE_NODE | ||||
attributes = None | ||||
specified = False | ||||
_is_id = False | ||||
_child_node_types = (Node.TEXT_NODE, Node.ENTITY_REFERENCE_NODE) | ||||
def __init__(self, qName, namespaceURI=EMPTY_NAMESPACE, localName=None, | ||||
prefix=None): | ||||
self.ownerElement = None | ||||
self._name = qName | ||||
self.namespaceURI = namespaceURI | ||||
self._prefix = prefix | ||||
self.childNodes = NodeList() | ||||
# Add the single child node that represents the value of the attr | ||||
self.childNodes.append(Text()) | ||||
# nodeValue and value are set elsewhere | ||||
def _get_localName(self): | ||||
try: | ||||
return self._localName | ||||
except AttributeError: | ||||
return self.nodeName.split(":", 1)[-1] | ||||
def _get_name(self): | ||||
return self.name | ||||
def _get_specified(self): | ||||
return self.specified | ||||
def _get_name(self): | ||||
return self._name | ||||
def _set_name(self, value): | ||||
self._name = value | ||||
if self.ownerElement is not None: | ||||
_clear_id_cache(self.ownerElement) | ||||
nodeName = name = property(_get_name, _set_name) | ||||
def _get_value(self): | ||||
return self._value | ||||
def _set_value(self, value): | ||||
self._value = value | ||||
self.childNodes[0].data = value | ||||
if self.ownerElement is not None: | ||||
_clear_id_cache(self.ownerElement) | ||||
self.childNodes[0].data = value | ||||
nodeValue = value = property(_get_value, _set_value) | ||||
def _get_prefix(self): | ||||
return self._prefix | ||||
def _set_prefix(self, prefix): | ||||
nsuri = self.namespaceURI | ||||
if prefix == "xmlns": | ||||
if nsuri and nsuri != XMLNS_NAMESPACE: | ||||
raise xml.dom.NamespaceErr( | ||||
"illegal use of 'xmlns' prefix for the wrong namespace") | ||||
self._prefix = prefix | ||||
if prefix is None: | ||||
newName = self.localName | ||||
else: | ||||
newName = "%s:%s" % (prefix, self.localName) | ||||
if self.ownerElement: | ||||
_clear_id_cache(self.ownerElement) | ||||
self.name = newName | ||||
prefix = property(_get_prefix, _set_prefix) | ||||
def unlink(self): | ||||
# This implementation does not call the base implementation | ||||
# since most of that is not needed, and the expense of the | ||||
# method call is not warranted. We duplicate the removal of | ||||
# children, but that's all we needed from the base class. | ||||
elem = self.ownerElement | ||||
if elem is not None: | ||||
del elem._attrs[self.nodeName] | ||||
del elem._attrsNS[(self.namespaceURI, self.localName)] | ||||
if self._is_id: | ||||
self._is_id = False | ||||
elem._magic_id_nodes -= 1 | ||||
self.ownerDocument._magic_id_count -= 1 | ||||
for child in self.childNodes: | ||||
child.unlink() | ||||
del self.childNodes[:] | ||||
def _get_isId(self): | ||||
if self._is_id: | ||||
return True | ||||
doc = self.ownerDocument | ||||
elem = self.ownerElement | ||||
if doc is None or elem is None: | ||||
return False | ||||
info = doc._get_elem_info(elem) | ||||
if info is None: | ||||
return False | ||||
if self.namespaceURI: | ||||
return info.isIdNS(self.namespaceURI, self.localName) | ||||
else: | ||||
return info.isId(self.nodeName) | ||||
def _get_schemaType(self): | ||||
doc = self.ownerDocument | ||||
elem = self.ownerElement | ||||
if doc is None or elem is None: | ||||
return _no_type | ||||
info = doc._get_elem_info(elem) | ||||
if info is None: | ||||
return _no_type | ||||
if self.namespaceURI: | ||||
return info.getAttributeTypeNS(self.namespaceURI, self.localName) | ||||
else: | ||||
return info.getAttributeType(self.nodeName) | ||||
defproperty(Attr, "isId", doc="True if this attribute is an ID.") | ||||
defproperty(Attr, "localName", doc="Namespace-local name of this attribute.") | ||||
defproperty(Attr, "schemaType", doc="Schema type for this attribute.") | ||||
class NamedNodeMap(object): | ||||
"""The attribute list is a transient interface to the underlying | ||||
dictionaries. Mutations here will change the underlying element's | ||||
dictionary. | ||||
Ordering is imposed artificially and does not reflect the order of | ||||
attributes as found in an input document. | ||||
""" | ||||
__slots__ = ('_attrs', '_attrsNS', '_ownerElement') | ||||
def __init__(self, attrs, attrsNS, ownerElement): | ||||
self._attrs = attrs | ||||
self._attrsNS = attrsNS | ||||
self._ownerElement = ownerElement | ||||
def _get_length(self): | ||||
return len(self._attrs) | ||||
def item(self, index): | ||||
try: | ||||
return self[list(self._attrs.keys())[index]] | ||||
except IndexError: | ||||
return None | ||||
def items(self): | ||||
L = [] | ||||
for node in self._attrs.values(): | ||||
L.append((node.nodeName, node.value)) | ||||
return L | ||||
def itemsNS(self): | ||||
L = [] | ||||
for node in self._attrs.values(): | ||||
L.append(((node.namespaceURI, node.localName), node.value)) | ||||
return L | ||||
def __contains__(self, key): | ||||
if isinstance(key, str): | ||||
return key in self._attrs | ||||
else: | ||||
return key in self._attrsNS | ||||
def keys(self): | ||||
return self._attrs.keys() | ||||
def keysNS(self): | ||||
return self._attrsNS.keys() | ||||
def values(self): | ||||
return self._attrs.values() | ||||
def get(self, name, value=None): | ||||
return self._attrs.get(name, value) | ||||
__len__ = _get_length | ||||
def _cmp(self, other): | ||||
if self._attrs is getattr(other, "_attrs", None): | ||||
return 0 | ||||
else: | ||||
return (id(self) > id(other)) - (id(self) < id(other)) | ||||
def __eq__(self, other): | ||||
return self._cmp(other) == 0 | ||||
def __ge__(self, other): | ||||
return self._cmp(other) >= 0 | ||||
def __gt__(self, other): | ||||
return self._cmp(other) > 0 | ||||
def __le__(self, other): | ||||
return self._cmp(other) <= 0 | ||||
def __lt__(self, other): | ||||
return self._cmp(other) < 0 | ||||
def __ne__(self, other): | ||||
return self._cmp(other) != 0 | ||||
def __getitem__(self, attname_or_tuple): | ||||
if isinstance(attname_or_tuple, tuple): | ||||
return self._attrsNS[attname_or_tuple] | ||||
else: | ||||
return self._attrs[attname_or_tuple] | ||||
# same as set | ||||
def __setitem__(self, attname, value): | ||||
if isinstance(value, str): | ||||
try: | ||||
node = self._attrs[attname] | ||||
except KeyError: | ||||
node = Attr(attname) | ||||
node.ownerDocument = self._ownerElement.ownerDocument | ||||
self.setNamedItem(node) | ||||
node.value = value | ||||
else: | ||||
if not isinstance(value, Attr): | ||||
raise TypeError("value must be a string or Attr object") | ||||
node = value | ||||
self.setNamedItem(node) | ||||
def getNamedItem(self, name): | ||||
try: | ||||
return self._attrs[name] | ||||
except KeyError: | ||||
return None | ||||
def getNamedItemNS(self, namespaceURI, localName): | ||||
try: | ||||
return self._attrsNS[(namespaceURI, localName)] | ||||
except KeyError: | ||||
return None | ||||
def removeNamedItem(self, name): | ||||
n = self.getNamedItem(name) | ||||
if n is not None: | ||||
_clear_id_cache(self._ownerElement) | ||||
del self._attrs[n.nodeName] | ||||
del self._attrsNS[(n.namespaceURI, n.localName)] | ||||
if hasattr(n, 'ownerElement'): | ||||
n.ownerElement = None | ||||
return n | ||||
else: | ||||
raise xml.dom.NotFoundErr() | ||||
def removeNamedItemNS(self, namespaceURI, localName): | ||||
n = self.getNamedItemNS(namespaceURI, localName) | ||||
if n is not None: | ||||
_clear_id_cache(self._ownerElement) | ||||
del self._attrsNS[(n.namespaceURI, n.localName)] | ||||
del self._attrs[n.nodeName] | ||||
if hasattr(n, 'ownerElement'): | ||||
n.ownerElement = None | ||||
return n | ||||
else: | ||||
raise xml.dom.NotFoundErr() | ||||
def setNamedItem(self, node): | ||||
if not isinstance(node, Attr): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"%s cannot be child of %s" % (repr(node), repr(self))) | ||||
old = self._attrs.get(node.name) | ||||
if old: | ||||
old.unlink() | ||||
self._attrs[node.name] = node | ||||
self._attrsNS[(node.namespaceURI, node.localName)] = node | ||||
node.ownerElement = self._ownerElement | ||||
_clear_id_cache(node.ownerElement) | ||||
return old | ||||
def setNamedItemNS(self, node): | ||||
return self.setNamedItem(node) | ||||
def __delitem__(self, attname_or_tuple): | ||||
node = self[attname_or_tuple] | ||||
_clear_id_cache(node.ownerElement) | ||||
node.unlink() | ||||
def __getstate__(self): | ||||
return self._attrs, self._attrsNS, self._ownerElement | ||||
def __setstate__(self, state): | ||||
self._attrs, self._attrsNS, self._ownerElement = state | ||||
defproperty(NamedNodeMap, "length", | ||||
doc="Number of nodes in the NamedNodeMap.") | ||||
AttributeList = NamedNodeMap | ||||
class TypeInfo(object): | ||||
__slots__ = 'namespace', 'name' | ||||
def __init__(self, namespace, name): | ||||
self.namespace = namespace | ||||
self.name = name | ||||
def __repr__(self): | ||||
if self.namespace: | ||||
return "<TypeInfo %r (from %r)>" % (self.name, self.namespace) | ||||
else: | ||||
return "<TypeInfo %r>" % self.name | ||||
def _get_name(self): | ||||
return self.name | ||||
def _get_namespace(self): | ||||
return self.namespace | ||||
_no_type = TypeInfo(None, None) | ||||
class Element(Node): | ||||
__slots__=('ownerDocument', 'parentNode', 'tagName', 'nodeName', 'prefix', | ||||
'namespaceURI', '_localName', 'childNodes', '_attrs', '_attrsNS', | ||||
'nextSibling', 'previousSibling') | ||||
nodeType = Node.ELEMENT_NODE | ||||
nodeValue = None | ||||
schemaType = _no_type | ||||
_magic_id_nodes = 0 | ||||
_child_node_types = (Node.ELEMENT_NODE, | ||||
Node.PROCESSING_INSTRUCTION_NODE, | ||||
Node.COMMENT_NODE, | ||||
Node.TEXT_NODE, | ||||
Node.CDATA_SECTION_NODE, | ||||
Node.ENTITY_REFERENCE_NODE) | ||||
def __init__(self, tagName, namespaceURI=EMPTY_NAMESPACE, prefix=None, | ||||
localName=None): | ||||
self.parentNode = None | ||||
self.tagName = self.nodeName = tagName | ||||
self.prefix = prefix | ||||
self.namespaceURI = namespaceURI | ||||
self.childNodes = NodeList() | ||||
self.nextSibling = self.previousSibling = None | ||||
# Attribute dictionaries are lazily created | ||||
# attributes are double-indexed: | ||||
# tagName -> Attribute | ||||
# URI,localName -> Attribute | ||||
# in the future: consider lazy generation | ||||
# of attribute objects this is too tricky | ||||
# for now because of headaches with | ||||
# namespaces. | ||||
self._attrs = None | ||||
self._attrsNS = None | ||||
def _ensure_attributes(self): | ||||
if self._attrs is None: | ||||
self._attrs = {} | ||||
self._attrsNS = {} | ||||
def _get_localName(self): | ||||
try: | ||||
return self._localName | ||||
except AttributeError: | ||||
return self.tagName.split(":", 1)[-1] | ||||
def _get_tagName(self): | ||||
return self.tagName | ||||
def unlink(self): | ||||
if self._attrs is not None: | ||||
for attr in list(self._attrs.values()): | ||||
attr.unlink() | ||||
self._attrs = None | ||||
self._attrsNS = None | ||||
Node.unlink(self) | ||||
def getAttribute(self, attname): | ||||
if self._attrs is None: | ||||
return "" | ||||
try: | ||||
return self._attrs[attname].value | ||||
except KeyError: | ||||
return "" | ||||
def getAttributeNS(self, namespaceURI, localName): | ||||
if self._attrsNS is None: | ||||
return "" | ||||
try: | ||||
return self._attrsNS[(namespaceURI, localName)].value | ||||
except KeyError: | ||||
return "" | ||||
def setAttribute(self, attname, value): | ||||
attr = self.getAttributeNode(attname) | ||||
if attr is None: | ||||
attr = Attr(attname) | ||||
attr.value = value # also sets nodeValue | ||||
attr.ownerDocument = self.ownerDocument | ||||
self.setAttributeNode(attr) | ||||
elif value != attr.value: | ||||
attr.value = value | ||||
if attr.isId: | ||||
_clear_id_cache(self) | ||||
def setAttributeNS(self, namespaceURI, qualifiedName, value): | ||||
prefix, localname = _nssplit(qualifiedName) | ||||
attr = self.getAttributeNodeNS(namespaceURI, localname) | ||||
if attr is None: | ||||
attr = Attr(qualifiedName, namespaceURI, localname, prefix) | ||||
attr.value = value | ||||
attr.ownerDocument = self.ownerDocument | ||||
self.setAttributeNode(attr) | ||||
else: | ||||
if value != attr.value: | ||||
attr.value = value | ||||
if attr.isId: | ||||
_clear_id_cache(self) | ||||
if attr.prefix != prefix: | ||||
attr.prefix = prefix | ||||
attr.nodeName = qualifiedName | ||||
def getAttributeNode(self, attrname): | ||||
if self._attrs is None: | ||||
return None | ||||
return self._attrs.get(attrname) | ||||
def getAttributeNodeNS(self, namespaceURI, localName): | ||||
if self._attrsNS is None: | ||||
return None | ||||
return self._attrsNS.get((namespaceURI, localName)) | ||||
def setAttributeNode(self, attr): | ||||
if attr.ownerElement not in (None, self): | ||||
raise xml.dom.InuseAttributeErr("attribute node already owned") | ||||
self._ensure_attributes() | ||||
old1 = self._attrs.get(attr.name, None) | ||||
if old1 is not None: | ||||
self.removeAttributeNode(old1) | ||||
old2 = self._attrsNS.get((attr.namespaceURI, attr.localName), None) | ||||
if old2 is not None and old2 is not old1: | ||||
self.removeAttributeNode(old2) | ||||
_set_attribute_node(self, attr) | ||||
if old1 is not attr: | ||||
# It might have already been part of this node, in which case | ||||
# it doesn't represent a change, and should not be returned. | ||||
return old1 | ||||
if old2 is not attr: | ||||
return old2 | ||||
setAttributeNodeNS = setAttributeNode | ||||
def removeAttribute(self, name): | ||||
if self._attrsNS is None: | ||||
raise xml.dom.NotFoundErr() | ||||
try: | ||||
attr = self._attrs[name] | ||||
except KeyError: | ||||
raise xml.dom.NotFoundErr() | ||||
self.removeAttributeNode(attr) | ||||
def removeAttributeNS(self, namespaceURI, localName): | ||||
if self._attrsNS is None: | ||||
raise xml.dom.NotFoundErr() | ||||
try: | ||||
attr = self._attrsNS[(namespaceURI, localName)] | ||||
except KeyError: | ||||
raise xml.dom.NotFoundErr() | ||||
self.removeAttributeNode(attr) | ||||
def removeAttributeNode(self, node): | ||||
if node is None: | ||||
raise xml.dom.NotFoundErr() | ||||
try: | ||||
self._attrs[node.name] | ||||
except KeyError: | ||||
raise xml.dom.NotFoundErr() | ||||
_clear_id_cache(self) | ||||
node.unlink() | ||||
# Restore this since the node is still useful and otherwise | ||||
# unlinked | ||||
node.ownerDocument = self.ownerDocument | ||||
removeAttributeNodeNS = removeAttributeNode | ||||
def hasAttribute(self, name): | ||||
if self._attrs is None: | ||||
return False | ||||
return name in self._attrs | ||||
def hasAttributeNS(self, namespaceURI, localName): | ||||
if self._attrsNS is None: | ||||
return False | ||||
return (namespaceURI, localName) in self._attrsNS | ||||
def getElementsByTagName(self, name): | ||||
return _get_elements_by_tagName_helper(self, name, NodeList()) | ||||
def getElementsByTagNameNS(self, namespaceURI, localName): | ||||
return _get_elements_by_tagName_ns_helper( | ||||
self, namespaceURI, localName, NodeList()) | ||||
def __repr__(self): | ||||
return "<DOM Element: %s at %#x>" % (self.tagName, id(self)) | ||||
def writexml(self, writer, indent="", addindent="", newl=""): | ||||
# indent = current indentation | ||||
# addindent = indentation to add to higher levels | ||||
# newl = newline string | ||||
writer.write(indent+"<" + self.tagName) | ||||
attrs = self._get_attributes() | ||||
a_names = sorted(attrs.keys()) | ||||
for a_name in a_names: | ||||
writer.write(" %s=\"" % a_name) | ||||
_write_data(writer, attrs[a_name].value) | ||||
writer.write("\"") | ||||
if self.childNodes: | ||||
writer.write(">") | ||||
if (len(self.childNodes) == 1 and | ||||
self.childNodes[0].nodeType == Node.TEXT_NODE): | ||||
self.childNodes[0].writexml(writer, '', '', '') | ||||
else: | ||||
writer.write(newl) | ||||
for node in self.childNodes: | ||||
node.writexml(writer, indent+addindent, addindent, newl) | ||||
writer.write(indent) | ||||
writer.write("</%s>%s" % (self.tagName, newl)) | ||||
else: | ||||
writer.write("/>%s"%(newl)) | ||||
def _get_attributes(self): | ||||
self._ensure_attributes() | ||||
return NamedNodeMap(self._attrs, self._attrsNS, self) | ||||
def hasAttributes(self): | ||||
if self._attrs: | ||||
return True | ||||
else: | ||||
return False | ||||
# DOM Level 3 attributes, based on the 22 Oct 2002 draft | ||||
def setIdAttribute(self, name): | ||||
idAttr = self.getAttributeNode(name) | ||||
self.setIdAttributeNode(idAttr) | ||||
def setIdAttributeNS(self, namespaceURI, localName): | ||||
idAttr = self.getAttributeNodeNS(namespaceURI, localName) | ||||
self.setIdAttributeNode(idAttr) | ||||
def setIdAttributeNode(self, idAttr): | ||||
if idAttr is None or not self.isSameNode(idAttr.ownerElement): | ||||
raise xml.dom.NotFoundErr() | ||||
if _get_containing_entref(self) is not None: | ||||
raise xml.dom.NoModificationAllowedErr() | ||||
if not idAttr._is_id: | ||||
idAttr._is_id = True | ||||
self._magic_id_nodes += 1 | ||||
self.ownerDocument._magic_id_count += 1 | ||||
_clear_id_cache(self) | ||||
defproperty(Element, "attributes", | ||||
doc="NamedNodeMap of attributes on the element.") | ||||
defproperty(Element, "localName", | ||||
doc="Namespace-local name of this element.") | ||||
def _set_attribute_node(element, attr): | ||||
_clear_id_cache(element) | ||||
element._ensure_attributes() | ||||
element._attrs[attr.name] = attr | ||||
element._attrsNS[(attr.namespaceURI, attr.localName)] = attr | ||||
# This creates a circular reference, but Element.unlink() | ||||
# breaks the cycle since the references to the attribute | ||||
# dictionaries are tossed. | ||||
attr.ownerElement = element | ||||
class Childless: | ||||
"""Mixin that makes childless-ness easy to implement and avoids | ||||
the complexity of the Node methods that deal with children. | ||||
""" | ||||
__slots__ = () | ||||
attributes = None | ||||
childNodes = EmptyNodeList() | ||||
firstChild = None | ||||
lastChild = None | ||||
def _get_firstChild(self): | ||||
return None | ||||
def _get_lastChild(self): | ||||
return None | ||||
def appendChild(self, node): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
self.nodeName + " nodes cannot have children") | ||||
def hasChildNodes(self): | ||||
return False | ||||
def insertBefore(self, newChild, refChild): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
self.nodeName + " nodes do not have children") | ||||
def removeChild(self, oldChild): | ||||
raise xml.dom.NotFoundErr( | ||||
self.nodeName + " nodes do not have children") | ||||
def normalize(self): | ||||
# For childless nodes, normalize() has nothing to do. | ||||
pass | ||||
def replaceChild(self, newChild, oldChild): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
self.nodeName + " nodes do not have children") | ||||
class ProcessingInstruction(Childless, Node): | ||||
nodeType = Node.PROCESSING_INSTRUCTION_NODE | ||||
__slots__ = ('target', 'data') | ||||
def __init__(self, target, data): | ||||
self.target = target | ||||
self.data = data | ||||
# nodeValue is an alias for data | ||||
def _get_nodeValue(self): | ||||
return self.data | ||||
def _set_nodeValue(self, value): | ||||
self.data = data | ||||
nodeValue = property(_get_nodeValue, _set_nodeValue) | ||||
# nodeName is an alias for target | ||||
def _get_nodeName(self): | ||||
return self.target | ||||
def _set_nodeName(self, value): | ||||
self.target = value | ||||
nodeName = property(_get_nodeName, _set_nodeName) | ||||
def writexml(self, writer, indent="", addindent="", newl=""): | ||||
writer.write("%s<?%s %s?>%s" % (indent,self.target, self.data, newl)) | ||||
class CharacterData(Childless, Node): | ||||
__slots__=('_data', 'ownerDocument','parentNode', 'previousSibling', 'nextSibling') | ||||
def __init__(self): | ||||
self.ownerDocument = self.parentNode = None | ||||
self.previousSibling = self.nextSibling = None | ||||
self._data = '' | ||||
Node.__init__(self) | ||||
def _get_length(self): | ||||
return len(self.data) | ||||
__len__ = _get_length | ||||
def _get_data(self): | ||||
return self._data | ||||
def _set_data(self, data): | ||||
self._data = data | ||||
data = nodeValue = property(_get_data, _set_data) | ||||
def __repr__(self): | ||||
data = self.data | ||||
if len(data) > 10: | ||||
dotdotdot = "..." | ||||
else: | ||||
dotdotdot = "" | ||||
return '<DOM %s node "%r%s">' % ( | ||||
self.__class__.__name__, data[0:10], dotdotdot) | ||||
def substringData(self, offset, count): | ||||
if offset < 0: | ||||
raise xml.dom.IndexSizeErr("offset cannot be negative") | ||||
if offset >= len(self.data): | ||||
raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") | ||||
if count < 0: | ||||
raise xml.dom.IndexSizeErr("count cannot be negative") | ||||
return self.data[offset:offset+count] | ||||
def appendData(self, arg): | ||||
self.data = self.data + arg | ||||
def insertData(self, offset, arg): | ||||
if offset < 0: | ||||
raise xml.dom.IndexSizeErr("offset cannot be negative") | ||||
if offset >= len(self.data): | ||||
raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") | ||||
if arg: | ||||
self.data = "%s%s%s" % ( | ||||
self.data[:offset], arg, self.data[offset:]) | ||||
def deleteData(self, offset, count): | ||||
if offset < 0: | ||||
raise xml.dom.IndexSizeErr("offset cannot be negative") | ||||
if offset >= len(self.data): | ||||
raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") | ||||
if count < 0: | ||||
raise xml.dom.IndexSizeErr("count cannot be negative") | ||||
if count: | ||||
self.data = self.data[:offset] + self.data[offset+count:] | ||||
def replaceData(self, offset, count, arg): | ||||
if offset < 0: | ||||
raise xml.dom.IndexSizeErr("offset cannot be negative") | ||||
if offset >= len(self.data): | ||||
raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") | ||||
if count < 0: | ||||
raise xml.dom.IndexSizeErr("count cannot be negative") | ||||
if count: | ||||
self.data = "%s%s%s" % ( | ||||
self.data[:offset], arg, self.data[offset+count:]) | ||||
defproperty(CharacterData, "length", doc="Length of the string data.") | ||||
class Text(CharacterData): | ||||
__slots__ = () | ||||
nodeType = Node.TEXT_NODE | ||||
nodeName = "#text" | ||||
attributes = None | ||||
def splitText(self, offset): | ||||
if offset < 0 or offset > len(self.data): | ||||
raise xml.dom.IndexSizeErr("illegal offset value") | ||||
newText = self.__class__() | ||||
newText.data = self.data[offset:] | ||||
newText.ownerDocument = self.ownerDocument | ||||
next = self.nextSibling | ||||
if self.parentNode and self in self.parentNode.childNodes: | ||||
if next is None: | ||||
self.parentNode.appendChild(newText) | ||||
else: | ||||
self.parentNode.insertBefore(newText, next) | ||||
self.data = self.data[:offset] | ||||
return newText | ||||
def writexml(self, writer, indent="", addindent="", newl=""): | ||||
_write_data(writer, "%s%s%s" % (indent, self.data, newl)) | ||||
# DOM Level 3 (WD 9 April 2002) | ||||
def _get_wholeText(self): | ||||
L = [self.data] | ||||
n = self.previousSibling | ||||
while n is not None: | ||||
if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): | ||||
L.insert(0, n.data) | ||||
n = n.previousSibling | ||||
else: | ||||
break | ||||
n = self.nextSibling | ||||
while n is not None: | ||||
if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): | ||||
L.append(n.data) | ||||
n = n.nextSibling | ||||
else: | ||||
break | ||||
return ''.join(L) | ||||
def replaceWholeText(self, content): | ||||
# XXX This needs to be seriously changed if minidom ever | ||||
# supports EntityReference nodes. | ||||
parent = self.parentNode | ||||
n = self.previousSibling | ||||
while n is not None: | ||||
if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): | ||||
next = n.previousSibling | ||||
parent.removeChild(n) | ||||
n = next | ||||
else: | ||||
break | ||||
n = self.nextSibling | ||||
if not content: | ||||
parent.removeChild(self) | ||||
while n is not None: | ||||
if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): | ||||
next = n.nextSibling | ||||
parent.removeChild(n) | ||||
n = next | ||||
else: | ||||
break | ||||
if content: | ||||
self.data = content | ||||
return self | ||||
else: | ||||
return None | ||||
def _get_isWhitespaceInElementContent(self): | ||||
if self.data.strip(): | ||||
return False | ||||
elem = _get_containing_element(self) | ||||
if elem is None: | ||||
return False | ||||
info = self.ownerDocument._get_elem_info(elem) | ||||
if info is None: | ||||
return False | ||||
else: | ||||
return info.isElementContent() | ||||
defproperty(Text, "isWhitespaceInElementContent", | ||||
doc="True iff this text node contains only whitespace" | ||||
" and is in element content.") | ||||
defproperty(Text, "wholeText", | ||||
doc="The text of all logically-adjacent text nodes.") | ||||
def _get_containing_element(node): | ||||
c = node.parentNode | ||||
while c is not None: | ||||
if c.nodeType == Node.ELEMENT_NODE: | ||||
return c | ||||
c = c.parentNode | ||||
return None | ||||
def _get_containing_entref(node): | ||||
c = node.parentNode | ||||
while c is not None: | ||||
if c.nodeType == Node.ENTITY_REFERENCE_NODE: | ||||
return c | ||||
c = c.parentNode | ||||
return None | ||||
class Comment(CharacterData): | ||||
nodeType = Node.COMMENT_NODE | ||||
nodeName = "#comment" | ||||
def __init__(self, data): | ||||
CharacterData.__init__(self) | ||||
self._data = data | ||||
def writexml(self, writer, indent="", addindent="", newl=""): | ||||
if "--" in self.data: | ||||
raise ValueError("'--' is not allowed in a comment node") | ||||
writer.write("%s<!--%s-->%s" % (indent, self.data, newl)) | ||||
class CDATASection(Text): | ||||
__slots__ = () | ||||
nodeType = Node.CDATA_SECTION_NODE | ||||
nodeName = "#cdata-section" | ||||
def writexml(self, writer, indent="", addindent="", newl=""): | ||||
if self.data.find("]]>") >= 0: | ||||
raise ValueError("']]>' not allowed in a CDATA section") | ||||
writer.write("<![CDATA[%s]]>" % self.data) | ||||
class ReadOnlySequentialNamedNodeMap(object): | ||||
__slots__ = '_seq', | ||||
def __init__(self, seq=()): | ||||
# seq should be a list or tuple | ||||
self._seq = seq | ||||
def __len__(self): | ||||
return len(self._seq) | ||||
def _get_length(self): | ||||
return len(self._seq) | ||||
def getNamedItem(self, name): | ||||
for n in self._seq: | ||||
if n.nodeName == name: | ||||
return n | ||||
def getNamedItemNS(self, namespaceURI, localName): | ||||
for n in self._seq: | ||||
if n.namespaceURI == namespaceURI and n.localName == localName: | ||||
return n | ||||
def __getitem__(self, name_or_tuple): | ||||
if isinstance(name_or_tuple, tuple): | ||||
node = self.getNamedItemNS(*name_or_tuple) | ||||
else: | ||||
node = self.getNamedItem(name_or_tuple) | ||||
if node is None: | ||||
raise KeyError(name_or_tuple) | ||||
return node | ||||
def item(self, index): | ||||
if index < 0: | ||||
return None | ||||
try: | ||||
return self._seq[index] | ||||
except IndexError: | ||||
return None | ||||
def removeNamedItem(self, name): | ||||
raise xml.dom.NoModificationAllowedErr( | ||||
"NamedNodeMap instance is read-only") | ||||
def removeNamedItemNS(self, namespaceURI, localName): | ||||
raise xml.dom.NoModificationAllowedErr( | ||||
"NamedNodeMap instance is read-only") | ||||
def setNamedItem(self, node): | ||||
raise xml.dom.NoModificationAllowedErr( | ||||
"NamedNodeMap instance is read-only") | ||||
def setNamedItemNS(self, node): | ||||
raise xml.dom.NoModificationAllowedErr( | ||||
"NamedNodeMap instance is read-only") | ||||
def __getstate__(self): | ||||
return [self._seq] | ||||
def __setstate__(self, state): | ||||
self._seq = state[0] | ||||
defproperty(ReadOnlySequentialNamedNodeMap, "length", | ||||
doc="Number of entries in the NamedNodeMap.") | ||||
class Identified: | ||||
"""Mix-in class that supports the publicId and systemId attributes.""" | ||||
__slots__ = 'publicId', 'systemId' | ||||
def _identified_mixin_init(self, publicId, systemId): | ||||
self.publicId = publicId | ||||
self.systemId = systemId | ||||
def _get_publicId(self): | ||||
return self.publicId | ||||
def _get_systemId(self): | ||||
return self.systemId | ||||
class DocumentType(Identified, Childless, Node): | ||||
nodeType = Node.DOCUMENT_TYPE_NODE | ||||
nodeValue = None | ||||
name = None | ||||
publicId = None | ||||
systemId = None | ||||
internalSubset = None | ||||
def __init__(self, qualifiedName): | ||||
self.entities = ReadOnlySequentialNamedNodeMap() | ||||
self.notations = ReadOnlySequentialNamedNodeMap() | ||||
if qualifiedName: | ||||
prefix, localname = _nssplit(qualifiedName) | ||||
self.name = localname | ||||
self.nodeName = self.name | ||||
def _get_internalSubset(self): | ||||
return self.internalSubset | ||||
def cloneNode(self, deep): | ||||
if self.ownerDocument is None: | ||||
# it's ok | ||||
clone = DocumentType(None) | ||||
clone.name = self.name | ||||
clone.nodeName = self.name | ||||
operation = xml.dom.UserDataHandler.NODE_CLONED | ||||
if deep: | ||||
clone.entities._seq = [] | ||||
clone.notations._seq = [] | ||||
for n in self.notations._seq: | ||||
notation = Notation(n.nodeName, n.publicId, n.systemId) | ||||
clone.notations._seq.append(notation) | ||||
n._call_user_data_handler(operation, n, notation) | ||||
for e in self.entities._seq: | ||||
entity = Entity(e.nodeName, e.publicId, e.systemId, | ||||
e.notationName) | ||||
entity.actualEncoding = e.actualEncoding | ||||
entity.encoding = e.encoding | ||||
entity.version = e.version | ||||
clone.entities._seq.append(entity) | ||||
e._call_user_data_handler(operation, n, entity) | ||||
self._call_user_data_handler(operation, self, clone) | ||||
return clone | ||||
else: | ||||
return None | ||||
def writexml(self, writer, indent="", addindent="", newl=""): | ||||
writer.write("<!DOCTYPE ") | ||||
writer.write(self.name) | ||||
if self.publicId: | ||||
writer.write("%s PUBLIC '%s'%s '%s'" | ||||
% (newl, self.publicId, newl, self.systemId)) | ||||
elif self.systemId: | ||||
writer.write("%s SYSTEM '%s'" % (newl, self.systemId)) | ||||
if self.internalSubset is not None: | ||||
writer.write(" [") | ||||
writer.write(self.internalSubset) | ||||
writer.write("]") | ||||
writer.write(">"+newl) | ||||
class Entity(Identified, Node): | ||||
attributes = None | ||||
nodeType = Node.ENTITY_NODE | ||||
nodeValue = None | ||||
actualEncoding = None | ||||
encoding = None | ||||
version = None | ||||
def __init__(self, name, publicId, systemId, notation): | ||||
self.nodeName = name | ||||
self.notationName = notation | ||||
self.childNodes = NodeList() | ||||
self._identified_mixin_init(publicId, systemId) | ||||
def _get_actualEncoding(self): | ||||
return self.actualEncoding | ||||
def _get_encoding(self): | ||||
return self.encoding | ||||
def _get_version(self): | ||||
return self.version | ||||
def appendChild(self, newChild): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"cannot append children to an entity node") | ||||
def insertBefore(self, newChild, refChild): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"cannot insert children below an entity node") | ||||
def removeChild(self, oldChild): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"cannot remove children from an entity node") | ||||
def replaceChild(self, newChild, oldChild): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"cannot replace children of an entity node") | ||||
class Notation(Identified, Childless, Node): | ||||
nodeType = Node.NOTATION_NODE | ||||
nodeValue = None | ||||
def __init__(self, name, publicId, systemId): | ||||
self.nodeName = name | ||||
self._identified_mixin_init(publicId, systemId) | ||||
class DOMImplementation(DOMImplementationLS): | ||||
_features = [("core", "1.0"), | ||||
("core", "2.0"), | ||||
("core", None), | ||||
("xml", "1.0"), | ||||
("xml", "2.0"), | ||||
("xml", None), | ||||
("ls-load", "3.0"), | ||||
("ls-load", None), | ||||
] | ||||
def hasFeature(self, feature, version): | ||||
if version == "": | ||||
version = None | ||||
return (feature.lower(), version) in self._features | ||||
def createDocument(self, namespaceURI, qualifiedName, doctype): | ||||
if doctype and doctype.parentNode is not None: | ||||
raise xml.dom.WrongDocumentErr( | ||||
"doctype object owned by another DOM tree") | ||||
doc = self._create_document() | ||||
add_root_element = not (namespaceURI is None | ||||
and qualifiedName is None | ||||
and doctype is None) | ||||
if not qualifiedName and add_root_element: | ||||
# The spec is unclear what to raise here; SyntaxErr | ||||
# would be the other obvious candidate. Since Xerces raises | ||||
# InvalidCharacterErr, and since SyntaxErr is not listed | ||||
# for createDocument, that seems to be the better choice. | ||||
# XXX: need to check for illegal characters here and in | ||||
# createElement. | ||||
# DOM Level III clears this up when talking about the return value | ||||
# of this function. If namespaceURI, qName and DocType are | ||||
# Null the document is returned without a document element | ||||
# Otherwise if doctype or namespaceURI are not None | ||||
# Then we go back to the above problem | ||||
raise xml.dom.InvalidCharacterErr("Element with no name") | ||||
if add_root_element: | ||||
prefix, localname = _nssplit(qualifiedName) | ||||
if prefix == "xml" \ | ||||
and namespaceURI != "http://www.w3.org/XML/1998/namespace": | ||||
raise xml.dom.NamespaceErr("illegal use of 'xml' prefix") | ||||
if prefix and not namespaceURI: | ||||
raise xml.dom.NamespaceErr( | ||||
"illegal use of prefix without namespaces") | ||||
element = doc.createElementNS(namespaceURI, qualifiedName) | ||||
if doctype: | ||||
doc.appendChild(doctype) | ||||
doc.appendChild(element) | ||||
if doctype: | ||||
doctype.parentNode = doctype.ownerDocument = doc | ||||
doc.doctype = doctype | ||||
doc.implementation = self | ||||
return doc | ||||
def createDocumentType(self, qualifiedName, publicId, systemId): | ||||
doctype = DocumentType(qualifiedName) | ||||
doctype.publicId = publicId | ||||
doctype.systemId = systemId | ||||
return doctype | ||||
# DOM Level 3 (WD 9 April 2002) | ||||
def getInterface(self, feature): | ||||
if self.hasFeature(feature, None): | ||||
return self | ||||
else: | ||||
return None | ||||
# internal | ||||
def _create_document(self): | ||||
return Document() | ||||
class ElementInfo(object): | ||||
"""Object that represents content-model information for an element. | ||||
This implementation is not expected to be used in practice; DOM | ||||
builders should provide implementations which do the right thing | ||||
using information available to it. | ||||
""" | ||||
__slots__ = 'tagName', | ||||
def __init__(self, name): | ||||
self.tagName = name | ||||
def getAttributeType(self, aname): | ||||
return _no_type | ||||
def getAttributeTypeNS(self, namespaceURI, localName): | ||||
return _no_type | ||||
def isElementContent(self): | ||||
return False | ||||
def isEmpty(self): | ||||
"""Returns true iff this element is declared to have an EMPTY | ||||
content model.""" | ||||
return False | ||||
def isId(self, aname): | ||||
"""Returns true iff the named attribute is a DTD-style ID.""" | ||||
return False | ||||
def isIdNS(self, namespaceURI, localName): | ||||
"""Returns true iff the identified attribute is a DTD-style ID.""" | ||||
return False | ||||
def __getstate__(self): | ||||
return self.tagName | ||||
def __setstate__(self, state): | ||||
self.tagName = state | ||||
def _clear_id_cache(node): | ||||
if node.nodeType == Node.DOCUMENT_NODE: | ||||
node._id_cache.clear() | ||||
node._id_search_stack = None | ||||
elif _in_document(node): | ||||
node.ownerDocument._id_cache.clear() | ||||
node.ownerDocument._id_search_stack= None | ||||
class Document(Node, DocumentLS): | ||||
__slots__ = ('_elem_info', 'doctype', | ||||
'_id_search_stack', 'childNodes', '_id_cache') | ||||
_child_node_types = (Node.ELEMENT_NODE, Node.PROCESSING_INSTRUCTION_NODE, | ||||
Node.COMMENT_NODE, Node.DOCUMENT_TYPE_NODE) | ||||
implementation = DOMImplementation() | ||||
nodeType = Node.DOCUMENT_NODE | ||||
nodeName = "#document" | ||||
nodeValue = None | ||||
attributes = None | ||||
parentNode = None | ||||
previousSibling = nextSibling = None | ||||
# Document attributes from Level 3 (WD 9 April 2002) | ||||
actualEncoding = None | ||||
encoding = None | ||||
standalone = None | ||||
version = None | ||||
strictErrorChecking = False | ||||
errorHandler = None | ||||
documentURI = None | ||||
_magic_id_count = 0 | ||||
def __init__(self): | ||||
self.doctype = None | ||||
self.childNodes = NodeList() | ||||
# mapping of (namespaceURI, localName) -> ElementInfo | ||||
# and tagName -> ElementInfo | ||||
self._elem_info = {} | ||||
self._id_cache = {} | ||||
self._id_search_stack = None | ||||
def _get_elem_info(self, element): | ||||
if element.namespaceURI: | ||||
key = element.namespaceURI, element.localName | ||||
else: | ||||
key = element.tagName | ||||
return self._elem_info.get(key) | ||||
def _get_actualEncoding(self): | ||||
return self.actualEncoding | ||||
def _get_doctype(self): | ||||
return self.doctype | ||||
def _get_documentURI(self): | ||||
return self.documentURI | ||||
def _get_encoding(self): | ||||
return self.encoding | ||||
def _get_errorHandler(self): | ||||
return self.errorHandler | ||||
def _get_standalone(self): | ||||
return self.standalone | ||||
def _get_strictErrorChecking(self): | ||||
return self.strictErrorChecking | ||||
def _get_version(self): | ||||
return self.version | ||||
def appendChild(self, node): | ||||
if node.nodeType not in self._child_node_types: | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"%s cannot be child of %s" % (repr(node), repr(self))) | ||||
if node.parentNode is not None: | ||||
# This needs to be done before the next test since this | ||||
# may *be* the document element, in which case it should | ||||
# end up re-ordered to the end. | ||||
node.parentNode.removeChild(node) | ||||
if node.nodeType == Node.ELEMENT_NODE \ | ||||
and self._get_documentElement(): | ||||
raise xml.dom.HierarchyRequestErr( | ||||
"two document elements disallowed") | ||||
return Node.appendChild(self, node) | ||||
def removeChild(self, oldChild): | ||||
try: | ||||
self.childNodes.remove(oldChild) | ||||
except ValueError: | ||||
raise xml.dom.NotFoundErr() | ||||
oldChild.nextSibling = oldChild.previousSibling = None | ||||
oldChild.parentNode = None | ||||
if self.documentElement is oldChild: | ||||
self.documentElement = None | ||||
return oldChild | ||||
def _get_documentElement(self): | ||||
for node in self.childNodes: | ||||
if node.nodeType == Node.ELEMENT_NODE: | ||||
return node | ||||
def unlink(self): | ||||
if self.doctype is not None: | ||||
self.doctype.unlink() | ||||
self.doctype = None | ||||
Node.unlink(self) | ||||
def cloneNode(self, deep): | ||||
if not deep: | ||||
return None | ||||
clone = self.implementation.createDocument(None, None, None) | ||||
clone.encoding = self.encoding | ||||
clone.standalone = self.standalone | ||||
clone.version = self.version | ||||
for n in self.childNodes: | ||||
childclone = _clone_node(n, deep, clone) | ||||
assert childclone.ownerDocument.isSameNode(clone) | ||||
clone.childNodes.append(childclone) | ||||
if childclone.nodeType == Node.DOCUMENT_NODE: | ||||
assert clone.documentElement is None | ||||
elif childclone.nodeType == Node.DOCUMENT_TYPE_NODE: | ||||
assert clone.doctype is None | ||||
clone.doctype = childclone | ||||
childclone.parentNode = clone | ||||
self._call_user_data_handler(xml.dom.UserDataHandler.NODE_CLONED, | ||||
self, clone) | ||||
return clone | ||||
def createDocumentFragment(self): | ||||
d = DocumentFragment() | ||||
d.ownerDocument = self | ||||
return d | ||||
def createElement(self, tagName): | ||||
e = Element(tagName) | ||||
e.ownerDocument = self | ||||
return e | ||||
def createTextNode(self, data): | ||||
if not isinstance(data, str): | ||||
raise TypeError("node contents must be a string") | ||||
t = Text() | ||||
t.data = data | ||||
t.ownerDocument = self | ||||
return t | ||||
def createCDATASection(self, data): | ||||
if not isinstance(data, str): | ||||
raise TypeError("node contents must be a string") | ||||
c = CDATASection() | ||||
c.data = data | ||||
c.ownerDocument = self | ||||
return c | ||||
def createComment(self, data): | ||||
c = Comment(data) | ||||
c.ownerDocument = self | ||||
return c | ||||
def createProcessingInstruction(self, target, data): | ||||
p = ProcessingInstruction(target, data) | ||||
p.ownerDocument = self | ||||
return p | ||||
def createAttribute(self, qName): | ||||
a = Attr(qName) | ||||
a.ownerDocument = self | ||||
a.value = "" | ||||
return a | ||||
def createElementNS(self, namespaceURI, qualifiedName): | ||||
prefix, localName = _nssplit(qualifiedName) | ||||
e = Element(qualifiedName, namespaceURI, prefix) | ||||
e.ownerDocument = self | ||||
return e | ||||
def createAttributeNS(self, namespaceURI, qualifiedName): | ||||
prefix, localName = _nssplit(qualifiedName) | ||||
a = Attr(qualifiedName, namespaceURI, localName, prefix) | ||||
a.ownerDocument = self | ||||
a.value = "" | ||||
return a | ||||
# A couple of implementation-specific helpers to create node types | ||||
# not supported by the W3C DOM specs: | ||||
def _create_entity(self, name, publicId, systemId, notationName): | ||||
e = Entity(name, publicId, systemId, notationName) | ||||
e.ownerDocument = self | ||||
return e | ||||
def _create_notation(self, name, publicId, systemId): | ||||
n = Notation(name, publicId, systemId) | ||||
n.ownerDocument = self | ||||
return n | ||||
def getElementById(self, id): | ||||
if id in self._id_cache: | ||||
return self._id_cache[id] | ||||
if not (self._elem_info or self._magic_id_count): | ||||
return None | ||||
stack = self._id_search_stack | ||||
if stack is None: | ||||
# we never searched before, or the cache has been cleared | ||||
stack = [self.documentElement] | ||||
self._id_search_stack = stack | ||||
elif not stack: | ||||
# Previous search was completed and cache is still valid; | ||||
# no matching node. | ||||
return None | ||||
result = None | ||||
while stack: | ||||
node = stack.pop() | ||||
# add child elements to stack for continued searching | ||||
stack.extend([child for child in node.childNodes | ||||
if child.nodeType in _nodeTypes_with_children]) | ||||
# check this node | ||||
info = self._get_elem_info(node) | ||||
if info: | ||||
# We have to process all ID attributes before | ||||
# returning in order to get all the attributes set to | ||||
# be IDs using Element.setIdAttribute*(). | ||||
for attr in node.attributes.values(): | ||||
if attr.namespaceURI: | ||||
if info.isIdNS(attr.namespaceURI, attr.localName): | ||||
self._id_cache[attr.value] = node | ||||
if attr.value == id: | ||||
result = node | ||||
elif not node._magic_id_nodes: | ||||
break | ||||
elif info.isId(attr.name): | ||||
self._id_cache[attr.value] = node | ||||
if attr.value == id: | ||||
result = node | ||||
elif not node._magic_id_nodes: | ||||
break | ||||
elif attr._is_id: | ||||
self._id_cache[attr.value] = node | ||||
if attr.value == id: | ||||
result = node | ||||
elif node._magic_id_nodes == 1: | ||||
break | ||||
elif node._magic_id_nodes: | ||||
for attr in node.attributes.values(): | ||||
if attr._is_id: | ||||
self._id_cache[attr.value] = node | ||||
if attr.value == id: | ||||
result = node | ||||
if result is not None: | ||||
break | ||||
return result | ||||
def getElementsByTagName(self, name): | ||||
return _get_elements_by_tagName_helper(self, name, NodeList()) | ||||
def getElementsByTagNameNS(self, namespaceURI, localName): | ||||
return _get_elements_by_tagName_ns_helper( | ||||
self, namespaceURI, localName, NodeList()) | ||||
def isSupported(self, feature, version): | ||||
return self.implementation.hasFeature(feature, version) | ||||
def importNode(self, node, deep): | ||||
if node.nodeType == Node.DOCUMENT_NODE: | ||||
raise xml.dom.NotSupportedErr("cannot import document nodes") | ||||
elif node.nodeType == Node.DOCUMENT_TYPE_NODE: | ||||
raise xml.dom.NotSupportedErr("cannot import document type nodes") | ||||
return _clone_node(node, deep, self) | ||||
def writexml(self, writer, indent="", addindent="", newl="", encoding=None): | ||||
if encoding is None: | ||||
writer.write('<?xml version="1.0" ?>'+newl) | ||||
else: | ||||
writer.write('<?xml version="1.0" encoding="%s"?>%s' % ( | ||||
encoding, newl)) | ||||
for node in self.childNodes: | ||||
node.writexml(writer, indent, addindent, newl) | ||||
# DOM Level 3 (WD 9 April 2002) | ||||
def renameNode(self, n, namespaceURI, name): | ||||
if n.ownerDocument is not self: | ||||
raise xml.dom.WrongDocumentErr( | ||||
"cannot rename nodes from other documents;\n" | ||||
"expected %s,\nfound %s" % (self, n.ownerDocument)) | ||||
if n.nodeType not in (Node.ELEMENT_NODE, Node.ATTRIBUTE_NODE): | ||||
raise xml.dom.NotSupportedErr( | ||||
"renameNode() only applies to element and attribute nodes") | ||||
if namespaceURI != EMPTY_NAMESPACE: | ||||
if ':' in name: | ||||
prefix, localName = name.split(':', 1) | ||||
if ( prefix == "xmlns" | ||||
and namespaceURI != xml.dom.XMLNS_NAMESPACE): | ||||
raise xml.dom.NamespaceErr( | ||||
"illegal use of 'xmlns' prefix") | ||||
else: | ||||
if ( name == "xmlns" | ||||
and namespaceURI != xml.dom.XMLNS_NAMESPACE | ||||
and n.nodeType == Node.ATTRIBUTE_NODE): | ||||
raise xml.dom.NamespaceErr( | ||||
"illegal use of the 'xmlns' attribute") | ||||
prefix = None | ||||
localName = name | ||||
else: | ||||
prefix = None | ||||
localName = None | ||||
if n.nodeType == Node.ATTRIBUTE_NODE: | ||||
element = n.ownerElement | ||||
if element is not None: | ||||
is_id = n._is_id | ||||
element.removeAttributeNode(n) | ||||
else: | ||||
element = None | ||||
n.prefix = prefix | ||||
n._localName = localName | ||||
n.namespaceURI = namespaceURI | ||||
n.nodeName = name | ||||
if n.nodeType == Node.ELEMENT_NODE: | ||||
n.tagName = name | ||||
else: | ||||
# attribute node | ||||
n.name = name | ||||
if element is not None: | ||||
element.setAttributeNode(n) | ||||
if is_id: | ||||
element.setIdAttributeNode(n) | ||||
# It's not clear from a semantic perspective whether we should | ||||
# call the user data handlers for the NODE_RENAMED event since | ||||
# we're re-using the existing node. The draft spec has been | ||||
# interpreted as meaning "no, don't call the handler unless a | ||||
# new node is created." | ||||
return n | ||||
defproperty(Document, "documentElement", | ||||
doc="Top-level element of this document.") | ||||
def _clone_node(node, deep, newOwnerDocument): | ||||
""" | ||||
Clone a node and give it the new owner document. | ||||
Called by Node.cloneNode and Document.importNode | ||||
""" | ||||
if node.ownerDocument.isSameNode(newOwnerDocument): | ||||
operation = xml.dom.UserDataHandler.NODE_CLONED | ||||
else: | ||||
operation = xml.dom.UserDataHandler.NODE_IMPORTED | ||||
if node.nodeType == Node.ELEMENT_NODE: | ||||
clone = newOwnerDocument.createElementNS(node.namespaceURI, | ||||
node.nodeName) | ||||
for attr in node.attributes.values(): | ||||
clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value) | ||||
a = clone.getAttributeNodeNS(attr.namespaceURI, attr.localName) | ||||
a.specified = attr.specified | ||||
if deep: | ||||
for child in node.childNodes: | ||||
c = _clone_node(child, deep, newOwnerDocument) | ||||
clone.appendChild(c) | ||||
elif node.nodeType == Node.DOCUMENT_FRAGMENT_NODE: | ||||
clone = newOwnerDocument.createDocumentFragment() | ||||
if deep: | ||||
for child in node.childNodes: | ||||
c = _clone_node(child, deep, newOwnerDocument) | ||||
clone.appendChild(c) | ||||
elif node.nodeType == Node.TEXT_NODE: | ||||
clone = newOwnerDocument.createTextNode(node.data) | ||||
elif node.nodeType == Node.CDATA_SECTION_NODE: | ||||
clone = newOwnerDocument.createCDATASection(node.data) | ||||
elif node.nodeType == Node.PROCESSING_INSTRUCTION_NODE: | ||||
clone = newOwnerDocument.createProcessingInstruction(node.target, | ||||
node.data) | ||||
elif node.nodeType == Node.COMMENT_NODE: | ||||
clone = newOwnerDocument.createComment(node.data) | ||||
elif node.nodeType == Node.ATTRIBUTE_NODE: | ||||
clone = newOwnerDocument.createAttributeNS(node.namespaceURI, | ||||
node.nodeName) | ||||
clone.specified = True | ||||
clone.value = node.value | ||||
elif node.nodeType == Node.DOCUMENT_TYPE_NODE: | ||||
assert node.ownerDocument is not newOwnerDocument | ||||
operation = xml.dom.UserDataHandler.NODE_IMPORTED | ||||
clone = newOwnerDocument.implementation.createDocumentType( | ||||
node.name, node.publicId, node.systemId) | ||||
clone.ownerDocument = newOwnerDocument | ||||
if deep: | ||||
clone.entities._seq = [] | ||||
clone.notations._seq = [] | ||||
for n in node.notations._seq: | ||||
notation = Notation(n.nodeName, n.publicId, n.systemId) | ||||
notation.ownerDocument = newOwnerDocument | ||||
clone.notations._seq.append(notation) | ||||
if hasattr(n, '_call_user_data_handler'): | ||||
n._call_user_data_handler(operation, n, notation) | ||||
for e in node.entities._seq: | ||||
entity = Entity(e.nodeName, e.publicId, e.systemId, | ||||
e.notationName) | ||||
entity.actualEncoding = e.actualEncoding | ||||
entity.encoding = e.encoding | ||||
entity.version = e.version | ||||
entity.ownerDocument = newOwnerDocument | ||||
clone.entities._seq.append(entity) | ||||
if hasattr(e, '_call_user_data_handler'): | ||||
e._call_user_data_handler(operation, n, entity) | ||||
else: | ||||
# Note the cloning of Document and DocumentType nodes is | ||||
# implementation specific. minidom handles those cases | ||||
# directly in the cloneNode() methods. | ||||
raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node)) | ||||
# Check for _call_user_data_handler() since this could conceivably | ||||
# used with other DOM implementations (one of the FourThought | ||||
# DOMs, perhaps?). | ||||
if hasattr(node, '_call_user_data_handler'): | ||||
node._call_user_data_handler(operation, node, clone) | ||||
return clone | ||||
def _nssplit(qualifiedName): | ||||
fields = qualifiedName.split(':', 1) | ||||
if len(fields) == 2: | ||||
return fields | ||||
else: | ||||
return (None, fields[0]) | ||||
def _do_pulldom_parse(func, args, kwargs): | ||||
events = func(*args, **kwargs) | ||||
toktype, rootNode = events.getEvent() | ||||
events.expandNode(rootNode) | ||||
events.clear() | ||||
return rootNode | ||||
def parse(file, parser=None, bufsize=None): | ||||
"""Parse a file into a DOM by filename or file object.""" | ||||
if parser is None and not bufsize: | ||||
from xml.dom import expatbuilder | ||||
return expatbuilder.parse(file) | ||||
else: | ||||
from xml.dom import pulldom | ||||
return _do_pulldom_parse(pulldom.parse, (file,), | ||||
{'parser': parser, 'bufsize': bufsize}) | ||||
def parseString(string, parser=None): | ||||
"""Parse a file into a DOM from a string.""" | ||||
if parser is None: | ||||
from xml.dom import expatbuilder | ||||
return expatbuilder.parseString(string) | ||||
else: | ||||
from xml.dom import pulldom | ||||
return _do_pulldom_parse(pulldom.parseString, (string,), | ||||
{'parser': parser}) | ||||
def getDOMImplementation(features=None): | ||||
if features: | ||||
if isinstance(features, str): | ||||
features = domreg._parse_feature_string(features) | ||||
for f, v in features: | ||||
if not Document.implementation.hasFeature(f, v): | ||||
return None | ||||
return Document.implementation | ||||