uk.ac.starlink.util.NodeDescendants Maven / Gradle / Ivy
package uk.ac.starlink.util;
import org.w3c.dom.*;
import java.util.Iterator;
/**
* Represents the set of Nodes in the tree rooted at a particular DOM
* Node. The tree includes the root node.
*
* This supports two ways of traversing the tree, either using an
* iterator, or providing an object which visits each node in the tree
* in turn.
*
*
Note that the {@link #iterator} and {@link #visitTree} methods
* below share state -- namely the state which this object represents
* -- so you should not use simultaneously the result of two such
* method calls on the same object.
*/
public class NodeDescendants {
// Object which allows easy traversal of trees. If you feel the
// need to make this more sophisticated, consider whether a full
// implementation of the DOM traversal standard interface would be more appropriate.
// Or whether what you really want is XPath.
/** Maps node types to the SHOW...
masks */
static private int[] nodeToMaskMap;
/**
* Indicates that all nodes should be included in a traversal of,
* or iteration through, a tree. To visit only certain nodes,
* use one of the other SHOW...
constants.
*
*
Note: this mechanism is taken from the DOM2
* Traversal specification, though it is not an implementation
* of that set of interfaces. As noted there, not all of the SHOW...
values are
* useful, since not all of the associated Node
types
* can appear as descendants of a Node, othe than in rather
* special circumstances. The constants are included here for
* completeness, however.
*
* @see #NodeDescendants(Node,int)
*/
public static final int SHOW_ALL = 0xFFFFFFFF;
public static final int SHOW_ELEMENT = 0x00000001;
public static final int SHOW_ATTRIBUTE = 0x00000002;
public static final int SHOW_TEXT = 0x00000004;
public static final int SHOW_CDATA_SECTION = 0x00000008;
public static final int SHOW_ENTITY_REFERENCE = 0x00000010;
public static final int SHOW_ENTITY = 0x00000020;
public static final int SHOW_PROCESSING_INSTRUCTION = 0x00000040;
public static final int SHOW_COMMENT = 0x00000080;
public static final int SHOW_DOCUMENT = 0x00000100;
public static final int SHOW_DOCUMENT_TYPE = 0x00000200;
public static final int SHOW_DOCUMENT_FRAGMENT = 0x00000400;
public static final int SHOW_NOTATION = 0x00000800;
static {
// See comments on nodeTypeMap in DOMUtils.
nodeToMaskMap = new int[16];
nodeToMaskMap[Node.ATTRIBUTE_NODE] = SHOW_ATTRIBUTE;
nodeToMaskMap[Node.CDATA_SECTION_NODE] = SHOW_CDATA_SECTION;
nodeToMaskMap[Node.COMMENT_NODE] = SHOW_COMMENT;
nodeToMaskMap[Node.DOCUMENT_FRAGMENT_NODE] = SHOW_DOCUMENT_FRAGMENT;
nodeToMaskMap[Node.DOCUMENT_NODE] = SHOW_DOCUMENT;
nodeToMaskMap[Node.DOCUMENT_TYPE_NODE] = SHOW_DOCUMENT_TYPE;
nodeToMaskMap[Node.ELEMENT_NODE] = SHOW_ELEMENT;
nodeToMaskMap[Node.ENTITY_NODE] = SHOW_ENTITY;
nodeToMaskMap[Node.ENTITY_REFERENCE_NODE] = SHOW_ENTITY_REFERENCE;
nodeToMaskMap[Node.NOTATION_NODE] = SHOW_NOTATION;
nodeToMaskMap[Node.PROCESSING_INSTRUCTION_NODE]
= SHOW_PROCESSING_INSTRUCTION;
nodeToMaskMap[Node.TEXT_NODE] = SHOW_TEXT;
}
/** The initial node */
private final Node initialNode;
/** The current node in the traversal. */
private Node currentNode;
/** A subtree. */
private NodeDescendants subtree;
/** The mask of Node types to show */
private int whatToShow;
/**
* Creates a new NodeDescendant
object.
* Equivalent to NodeDescendant(node, SHOW_ALL)
.
*
* @param node the node which is to be the root of the tree
*/
public NodeDescendants(Node node) {
this(node, SHOW_ALL);
}
/**
* Creates a new NodeDescendant
object. This
* represents the set of Nodes in the tree rooted at the given Node.
*
*
You can configure the set to include only certain
* nodes. If the whatToShow
parameter is given as
* {@link #SHOW_ALL}, then all nodes are returned. If the
* parameter has one of the other SHOW_...
values, or
* more than one or'ed together (using |
), then only
* the indicated node types are included in the set, and returned
* by any iterator or examined by any visitor.
*
*
For example, if you create the NodeDescendant
* using the constructor:
*
* NodeDescendants tree = new NodeDescendants
* (mynode, NodeDescendants.SHOW_ALL);
*
* (which is equivalent to the NodeDescendants(Node)
* constructor), then the set represents all the nodes in the tree
* which are reachable from the node mynode
by the
* getFirstChild
and similar methods. If, however,
* the object was constructed with
*
* NodeDescendants tree = new NodeDescendants
* (mynode,
* NodeDescendants.SHOW_TEXT|NodeDescendants.SHOW_CDATA_SECTION);
*
* then all Text and CDATA nodes would be included in the set, and
* only these would be returned by the iterator or visited by the visitor.
*
* @param node the node which is to be the root of the tree
* @param whatToShow code indicating which node types should be
* included in the set
*/
public NodeDescendants(Node node, int whatToShow) {
initialNode = node;
this.whatToShow = whatToShow;
reset();
}
/**
* Sets the object back to its initial state. This allows you to
* extract a second iterator either after an earlier one has
* finished, or before.
*/
public void reset() {
currentNode = initialNode;
subtree = null; // marker for first time through
}
/**
* Sets the object back to its initial state, but with a
* (possibly) different constraint on which nodes are included in the set.
*
* @param whatToShow code indicating which node types should be
* included in the set. See {@link #NodeDescendants(Node,int)}
*/
public void reset(int whatToShow) {
reset();
this.whatToShow = whatToShow;
}
/**
* Returns the next node in the set, irrespective of any
* constraints represented in whatToShow
.
*
* @return the next node, or null
when all of the
* elements in the set have been returned
*/
private Node nextNode() {
if (currentNode == null)
return null;
Node ret;
if (subtree == null) {
// first time through
assert currentNode != null;
ret = currentNode;
currentNode = currentNode.getFirstChild();
if (currentNode != null)
subtree = new NodeDescendants(currentNode, whatToShow);
return ret;
}
ret = subtree.nextNode();
if (ret == null) {
currentNode = currentNode.getNextSibling();
if (currentNode != null) {
subtree = new NodeDescendants(currentNode, whatToShow);
ret = subtree.nextNode();
}
}
if (ret == null)
// end of list
reset(); // for next time?
return ret;
}
/**
* Returns the next node in the traversal. This returns only
* nodes matching those declared in the whatToShow
* parameter of the initial constructor.
*
* @return the next included node, or null
when all
* of the elements in the set have been returned
*/
private Node nextFilteredNode() {
if (whatToShow == SHOW_ALL)
return nextNode();
Node ret;
do {
ret = nextNode();
} while (ret != null
&& (nodeToMaskMap[ret.getNodeType()] & whatToShow) == 0);
return ret;
}
/**
* Visits each of the nodes in the tree. This method
* iterates through all the descendants of the given node,
* starting with that node, and presents each of them to the given
* NodeVisitor
. If that object's
* visitNode
method returns non-null, then the
* traversal is stopped and the returned object immediately
* returned as the value of this method. If the
* visitNode
method always returns null and so the
* traversal completes, then the method returns null also.
*
* @param v a visitor which has each node presented to it in turn
*/
public Object visitTree(Visitor v) {
if (v == null)
throw new IllegalArgumentException
("NodeDescendants.visitTree given null visitor");
for (Node n = nextFilteredNode(); n != null; n = nextFilteredNode()) {
Object ret = v.visitNode(n);
if (ret != null)
return ret; // JUMP OUT
}
return null;
}
/**
* Obtains an iterator which iterates over the nodes in the set of
* descendants. The traversal happens in depth-first order.
*
* @return an iterator which returns in turn the given node, then
* all of its descendants.
*/
public Iterator iterator() {
return new Iterator() {
private Node nextNode = nextFilteredNode();
public boolean hasNext() {
return nextNode != null;
}
public Node next() {
Node ret = nextNode;
nextNode = nextFilteredNode();
return ret;
}
public void remove() {
throw new UnsupportedOperationException
("NodeDescendants.iterator does not support element removal");
}
};
}
/**
* The Visitor
processes a single node in a
* tree. This allows an object to visit each of the nodes in a tree.
*
* @see #visitTree
*/
public interface Visitor {
/**
* Visit a node in a tree. If the object doing the visiting
* wishes to stop the traversal early, it should return
* non-null. Otherwise it should return null.
* @param n the node being visited
* @return null if the traversal is to continue after this
* node; non-null if it is to stop
*/
public Object visitNode(Node n);
}
}