blob: bec378c502933df90de3f2039a9eb7ee82a1e51a [file] [log] [blame]
/*
* $Id$
*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Crimson" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, Sun Microsystems, Inc.,
* http://www.sun.com. For more information on the Apache Software
* Foundation, please see <http://www.apache.org/>.
*/
package org.apache.xerces.tree;
import java.util.Locale;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* This class implements a preorder depth first walk over the tree rooted
* at a given DOM node. The traversal is "live", and terminates either when
* that given node is reached when climbing back up, or when a null parent
* node is reached. It may be restarted via <em>reset()</em>.
*
* <P> The way this remains live is to have a "current" node to which the
* walk is tied. If the tree is modified, that current node will always
* still be valid ... even if it is no longer connected to the rest of
* the document, or if it's reconnected at a different location. The
* behavior of tree modifications is specified by DOM, and the interaction
* with a walker's current node is specified entirely by knowing that only
* the getNextSibling, getParentNode, and getFirstChild methods are used
* for walking the tree.
*
* <P> For example, if the current branch is cut off, the walk will stop
* when it tries to access what were parents or siblings of that node.
* (That is, the walk will continue over the branch that was cut.) If
* that is not the intended behaviour, one must change the "current" branch
* before cutting ... much like avoiding trimming a branch off a real
* tree if someone is sitting on it. The <em>removeCurrent()</em>
* method encapsulates that logic.
*
* @author David Brownell
* @version $Revision$
*/
public class TreeWalker
{
// yes, this is really a "TreeIterator" but the DOM WG plans to
// use such names in Level 2 ... so, we avoid it for now. (note
// that they've also discussed a "TreeWalker" that's rather more
// complex than this iterator...)
private Node startPoint;
private Node current;
/**
* Constructs a tree walker starting at the given node.
*/
public TreeWalker (Node initial)
{
if (initial == null) {
throw new IllegalArgumentException (XmlDocument.catalog.
getMessage (Locale.getDefault (), "TW-004"));
}
if (!(initial instanceof NodeBase)) {
throw new IllegalArgumentException (XmlDocument.catalog.
getMessage (Locale.getDefault (), "TW-003"));
}
startPoint = current = initial;
}
/**
* Returns the current node.
*/
public Node getCurrent ()
{
return current;
}
/**
* Advances to the next node, and makes that current. Returns
* null if there are no more nodes through which to walk,
* because the initial node was reached or because a null
* parent was reached.
*
* @return the next node (which becomes current), or else null
*/
public Node getNext ()
{
Node next;
if (current == null)
return null;
switch (current.getNodeType ()) {
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.DOCUMENT_NODE:
case Node.ELEMENT_NODE:
//
// For elements that can have children, visit those
// children before any siblings (i.e. depth first)
// and after visiting this node (i.e. preorder)
//
next = current.getFirstChild ();
if (next != null) {
current = next;
return next;
}
// FALLTHROUGH
case Node.ATTRIBUTE_NODE:
// NOTE: attributes "should" have children ...
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
case Node.DOCUMENT_TYPE_NODE:
case Node.ENTITY_REFERENCE_NODE:
case Node.ENTITY_NODE:
case Node.PROCESSING_INSTRUCTION_NODE:
case Node.TEXT_NODE:
//
// For childless nodes, only look at siblings. If no
// siblings, climb the tree till we get to a spot there
// are siblings, or till we terminate our walk.
//
for (Node here = current;
here != null && here != startPoint;
here = here.getParentNode ()) {
next = here.getNextSibling ();
if (next != null) {
current = next;
return next;
}
}
current = null;
return null;
}
throw new InternalError (((NodeBase)startPoint).getMessage ("TW-000",
new Object [] { Short.toString (current.
getNodeType ()) }));
}
/**
* Convenience method to walk only through elements with the specified
* tag name. This just calls getNext() and filters out the nodes which
* aren't desired. It returns null when the iteration completes.
*
* @param tag the tag to match, or null to indicate all elements
* @return the next matching element, or else null
*/
public Element getNextElement (String tag)
{
for (Node next = getNext ();
next != null;
next = getNext ()) {
if (next.getNodeType () == Node.ELEMENT_NODE
&& (tag == null || tag.equals (next.getNodeName ())))
return (Element) next;
}
current = null;
return null;
}
/**
* Namespace version
*/
public Element getNextElement(String nsURI, String tag) {
for (Node next = getNext(); next != null; next = getNext()) {
if (next.getNodeType () == Node.ELEMENT_NODE
&& (nsURI == null || nsURI.equals(next.getNamespaceURI()))
&& (tag == null || tag.equals(next.getNodeName()))) {
return (Element)next;
}
}
current = null;
return null;
}
/**
* Resets the walker to the state in which it was created: the
* current node will be the node given to the constructor. If
* the tree rooted at that node has been modified from the previous
* traversal, the sequence of nodes returned by <em>getNext()</em>
* will differ accordingly.
*/
public void reset ()
{
current = startPoint;
}
/**
* Removes the current node; reassigns the current node to be the
* next one in the current walk that isn't a child of the (removed)
* current node, and returns that new current node. In a loop, this
* could be used instead of a <em>getNext()</em>.
*
* @return the next node (which becomes current), or else null
* @throws IllegalStateException if the current node is null
* or has no parent (it is a Document or DocumentFragment)
*/
public Node removeCurrent ()
{
if (current == null)
throw new IllegalStateException (((NodeBase)startPoint).
getMessage ("TW-001"));
Node toRemove = current;
Node parent = current.getParentNode ();
Node retval = null;
if (parent == null)
throw new IllegalStateException (((NodeBase)startPoint).
getMessage ("TW-002"));
//
// Don't look at children, just siblings/parents
//
for (Node here = current;
here != null && here != startPoint;
here = here.getParentNode ()) {
retval = here.getNextSibling ();
if (retval != null) {
current = retval;
break;
}
}
parent.removeChild (toRemove);
return retval;
}
}