123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- from __future__ import absolute_import, division, unicode_literals
- from xml.dom import Node
- from ..constants import namespaces, voidElements, spaceCharacters
- __all__ = ["DOCUMENT", "DOCTYPE", "TEXT", "ELEMENT", "COMMENT", "ENTITY", "UNKNOWN",
- "TreeWalker", "NonRecursiveTreeWalker"]
- DOCUMENT = Node.DOCUMENT_NODE
- DOCTYPE = Node.DOCUMENT_TYPE_NODE
- TEXT = Node.TEXT_NODE
- ELEMENT = Node.ELEMENT_NODE
- COMMENT = Node.COMMENT_NODE
- ENTITY = Node.ENTITY_NODE
- UNKNOWN = "<#UNKNOWN#>"
- spaceCharacters = "".join(spaceCharacters)
- class TreeWalker(object):
- """Walks a tree yielding tokens
- Tokens are dicts that all have a ``type`` field specifying the type of the
- token.
- """
- def __init__(self, tree):
- """Creates a TreeWalker
- :arg tree: the tree to walk
- """
- self.tree = tree
- def __iter__(self):
- raise NotImplementedError
- def error(self, msg):
- """Generates an error token with the given message
- :arg msg: the error message
- :returns: SerializeError token
- """
- return {"type": "SerializeError", "data": msg}
- def emptyTag(self, namespace, name, attrs, hasChildren=False):
- """Generates an EmptyTag token
- :arg namespace: the namespace of the token--can be ``None``
- :arg name: the name of the element
- :arg attrs: the attributes of the element as a dict
- :arg hasChildren: whether or not to yield a SerializationError because
- this tag shouldn't have children
- :returns: EmptyTag token
- """
- yield {"type": "EmptyTag", "name": name,
- "namespace": namespace,
- "data": attrs}
- if hasChildren:
- yield self.error("Void element has children")
- def startTag(self, namespace, name, attrs):
- """Generates a StartTag token
- :arg namespace: the namespace of the token--can be ``None``
- :arg name: the name of the element
- :arg attrs: the attributes of the element as a dict
- :returns: StartTag token
- """
- return {"type": "StartTag",
- "name": name,
- "namespace": namespace,
- "data": attrs}
- def endTag(self, namespace, name):
- """Generates an EndTag token
- :arg namespace: the namespace of the token--can be ``None``
- :arg name: the name of the element
- :returns: EndTag token
- """
- return {"type": "EndTag",
- "name": name,
- "namespace": namespace}
- def text(self, data):
- """Generates SpaceCharacters and Characters tokens
- Depending on what's in the data, this generates one or more
- ``SpaceCharacters`` and ``Characters`` tokens.
- For example:
- >>> from html5lib.treewalkers.base import TreeWalker
- >>> # Give it an empty tree just so it instantiates
- >>> walker = TreeWalker([])
- >>> list(walker.text(''))
- []
- >>> list(walker.text(' '))
- [{u'data': ' ', u'type': u'SpaceCharacters'}]
- >>> list(walker.text(' abc ')) # doctest: +NORMALIZE_WHITESPACE
- [{u'data': ' ', u'type': u'SpaceCharacters'},
- {u'data': u'abc', u'type': u'Characters'},
- {u'data': u' ', u'type': u'SpaceCharacters'}]
- :arg data: the text data
- :returns: one or more ``SpaceCharacters`` and ``Characters`` tokens
- """
- data = data
- middle = data.lstrip(spaceCharacters)
- left = data[:len(data) - len(middle)]
- if left:
- yield {"type": "SpaceCharacters", "data": left}
- data = middle
- middle = data.rstrip(spaceCharacters)
- right = data[len(middle):]
- if middle:
- yield {"type": "Characters", "data": middle}
- if right:
- yield {"type": "SpaceCharacters", "data": right}
- def comment(self, data):
- """Generates a Comment token
- :arg data: the comment
- :returns: Comment token
- """
- return {"type": "Comment", "data": data}
- def doctype(self, name, publicId=None, systemId=None):
- """Generates a Doctype token
- :arg name:
- :arg publicId:
- :arg systemId:
- :returns: the Doctype token
- """
- return {"type": "Doctype",
- "name": name,
- "publicId": publicId,
- "systemId": systemId}
- def entity(self, name):
- """Generates an Entity token
- :arg name: the entity name
- :returns: an Entity token
- """
- return {"type": "Entity", "name": name}
- def unknown(self, nodeType):
- """Handles unknown node types"""
- return self.error("Unknown node type: " + nodeType)
- class NonRecursiveTreeWalker(TreeWalker):
- def getNodeDetails(self, node):
- raise NotImplementedError
- def getFirstChild(self, node):
- raise NotImplementedError
- def getNextSibling(self, node):
- raise NotImplementedError
- def getParentNode(self, node):
- raise NotImplementedError
- def __iter__(self):
- currentNode = self.tree
- while currentNode is not None:
- details = self.getNodeDetails(currentNode)
- type, details = details[0], details[1:]
- hasChildren = False
- if type == DOCTYPE:
- yield self.doctype(*details)
- elif type == TEXT:
- for token in self.text(*details):
- yield token
- elif type == ELEMENT:
- namespace, name, attributes, hasChildren = details
- if (not namespace or namespace == namespaces["html"]) and name in voidElements:
- for token in self.emptyTag(namespace, name, attributes,
- hasChildren):
- yield token
- hasChildren = False
- else:
- yield self.startTag(namespace, name, attributes)
- elif type == COMMENT:
- yield self.comment(details[0])
- elif type == ENTITY:
- yield self.entity(details[0])
- elif type == DOCUMENT:
- hasChildren = True
- else:
- yield self.unknown(details[0])
- if hasChildren:
- firstChild = self.getFirstChild(currentNode)
- else:
- firstChild = None
- if firstChild is not None:
- currentNode = firstChild
- else:
- while currentNode is not None:
- details = self.getNodeDetails(currentNode)
- type, details = details[0], details[1:]
- if type == ELEMENT:
- namespace, name, attributes, hasChildren = details
- if (namespace and namespace != namespaces["html"]) or name not in voidElements:
- yield self.endTag(namespace, name)
- if self.tree is currentNode:
- currentNode = None
- break
- nextSibling = self.getNextSibling(currentNode)
- if nextSibling is not None:
- currentNode = nextSibling
- break
- else:
- currentNode = self.getParentNode(currentNode)
|