| /* |
| * $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; |
| } |
| } |