"""Simple implementation of the Level 1 DOM.
Namespaces and other minor Level 2 features are also supported.
parseString("<foo><bar/></foo>")
* convenience methods for getting elements and text.
* bring some of the writer and linearizer code into conformance with this
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
prefix = EMPTY_PREFIX # non-null only for NS elements and attributes
def toxml(self, encoding = None):
return self.toprettyxml("", "", encoding)
def toprettyxml(self, indent="\t", newl="\n", encoding = None):
# indent = the indentation string to prepend, per level
# newl = the newline string to append
# Can't use codecs.getwriter to preserve 2.0 compatibility
writer = codecs.lookup(encoding)[3](writer)
if self.nodeType == Node.DOCUMENT_NODE:
# Can pass encoding only to document, to put it into XML header
self.writexml(writer, "", indent, newl, encoding)
self.writexml(writer, "", indent, newl)
def _get_childNodes(self):
def _get_firstChild(self):
return self.childNodes[0]
def _get_lastChild(self):
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
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)
self.appendChild(newChild)
index = self.childNodes.index(refChild)
raise xml.dom.NotFoundErr()
if newChild.nodeType in _nodeTypes_with_children:
self.childNodes.insert(index, newChild)
newChild.nextSibling = refChild
refChild.previousSibling = newChild
node = self.childNodes[index-1]
node.nextSibling = newChild
newChild.previousSibling = node
newChild.previousSibling = None
newChild.parentNode = self
def appendChild(self, node):
if node.nodeType == self.DOCUMENT_FRAGMENT_NODE:
for c in tuple(node.childNodes):
### The DOM does not clearly specify what to return in this case
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:
if node.parentNode is not None:
node.parentNode.removeChild(node)
_append_child(self, 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.parentNode is not None:
newChild.parentNode.removeChild(newChild)
index = self.childNodes.index(oldChild)
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):
newChild.nextSibling = oldChild.nextSibling
newChild.previousSibling = oldChild.previousSibling
oldChild.nextSibling = None
oldChild.previousSibling = None
if newChild.previousSibling:
newChild.previousSibling.nextSibling = newChild
newChild.nextSibling.previousSibling = newChild
def removeChild(self, oldChild):
self.childNodes.remove(oldChild)
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:
oldChild.parentNode = None
for child in self.childNodes:
if child.nodeType == Node.TEXT_NODE:
# empty text node; discard
L[-1].nextSibling = child.nextSibling
child.nextSibling.previousSibling = child.previousSibling
elif L and L[-1].nodeType == child.nodeType:
node.data = node.data + child.data
node.nextSibling = child.nextSibling
child.nextSibling.previousSibling = node
if child.nodeType == Node.ELEMENT_NODE:
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
# Node interfaces from Level 3 (WD 9 April 2002)
def isSameNode(self, other):
def getInterface(self, feature):
if self.isSupported(feature, 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
def getUserData(self, key):
return self._user_data[key][0]
except (AttributeError, KeyError):
def setUserData(self, key, data, handler):
# ignore handlers passed for None
def _call_user_data_handler(self, operation, src, dst):
if hasattr(self, "_user_data"):
for key, (data, handler) in self._user_data.items():
handler.handle(operation, key, data, src, dst)
self.parentNode = self.ownerDocument = None
for child in self.childNodes:
self.childNodes = NodeList()
self.previousSibling = None
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
node.__dict__["previousSibling"] = last
last.__dict__["nextSibling"] = node
node.__dict__["parentNode"] = self
# return True iff node is part of a document tree
if node.nodeType == Node.DOCUMENT_NODE:
def _write_data(writer, data):
"Writes datachars to writer."
data = data.replace("&", "&").replace("<", "<"). \
replace("\"", """).replace(">", ">")
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):
_get_elements_by_tagName_helper(node, name, 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)):
_get_elements_by_tagName_ns_helper(node, nsURI, localName, rc)
class DocumentFragment(Node):
nodeType = Node.DOCUMENT_FRAGMENT_NODE
nodeName = "#document-fragment"
_child_node_types = (Node.ELEMENT_NODE,
Node.ENTITY_REFERENCE_NODE,
Node.PROCESSING_INSTRUCTION_NODE,
self.childNodes = NodeList()
nodeType = Node.ATTRIBUTE_NODE
_child_node_types = (Node.TEXT_NODE, Node.ENTITY_REFERENCE_NODE)
def __init__(self, qName, namespaceURI=EMPTY_NAMESPACE, localName=None,
# skip setattr for performance
d["nodeName"] = d["name"] = qName
d["namespaceURI"] = namespaceURI
d['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):
return self.nodeName.split(":", 1)[-1]
def _get_specified(self):
def __setattr__(self, name, value):
if name in ("value", "nodeValue"):
d["value"] = d["nodeValue"] = value
d2 = self.childNodes[0].__dict__
d2["data"] = d2["nodeValue"] = value
if self.ownerElement is not None:
_clear_id_cache(self.ownerElement)
elif name in ("name", "nodeName"):
d["name"] = d["nodeName"] = value
if self.ownerElement is not None:
_clear_id_cache(self.ownerElement)
def _set_prefix(self, prefix):
nsuri = self.namespaceURI
if nsuri and nsuri != XMLNS_NAMESPACE:
raise xml.dom.NamespaceErr(
"illegal use of 'xmlns' prefix for the wrong namespace")
newName = "%s:%s" % (prefix, self.localName)
_clear_id_cache(self.ownerElement)
d['nodeName'] = d['name'] = newName
def _set_value(self, value):
d['value'] = d['nodeValue'] = value
_clear_id_cache(self.ownerElement)
self.childNodes[0].data = value
# 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.
del elem._attrs[self.nodeName]
del elem._attrsNS[(self.namespaceURI, self.localName)]
elem._magic_id_nodes -= 1
self.ownerDocument._magic_id_count -= 1
for child in self.childNodes:
if doc is None or elem is None:
info = doc._get_elem_info(elem)
return info.isIdNS(self.namespaceURI, self.localName)
return info.isId(self.nodeName)
def _get_schemaType(self):
if doc is None or elem is None:
info = doc._get_elem_info(elem)
return info.getAttributeTypeNS(self.namespaceURI, self.localName)
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
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._ownerElement = ownerElement
return self[self._attrs.keys()[index]]
for node in self._attrs.values():
L.append((node.nodeName, node.value))
for node in self._attrs.values():
L.append(((node.namespaceURI, node.localName), node.value))
if isinstance(key, StringTypes):
return key in self._attrs
return key in self._attrsNS
return self._attrs.keys()