/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999-2002 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 "Xerces" 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, International
* Business Machines, Inc., http://www.apache.org. For more
* information on the Apache Software Foundation, please see
*
The whatToShow and filter functionality is implemented as expected. * *
This class also has method removeNode to enable iterator "fix-up" * on DOM remove. It is expected that the DOM implementation call removeNode * right before the actual DOM transformation. If not called by the DOM, * the client could call it before doing the removal. * * @version $Id: NodeIteratorImpl.java,v 1.11 2002/11/06 22:58:28 elena Exp $ */ public class NodeIteratorImpl implements NodeIterator { // // Data // /** The DocumentImpl which created this iterator, so it can be detached. */ private DocumentImpl fDocument; /** The root. */ private Node fRoot; /** The whatToShow mask. */ private int fWhatToShow = NodeFilter.SHOW_ALL; /** The NodeFilter reference. */ private NodeFilter fNodeFilter; /** If detach is called, the fDetach flag is true, otherwise flase. */ private boolean fDetach = false; // // Iterator state - current node and direction. // // Note: The current node and direction are sufficient to implement // the desired behaviour of the current pointer being _between_ // two nodes. The fCurrentNode is actually the last node returned, // and the // direction is whether the pointer is in front or behind this node. // (usually akin to whether the node was returned via nextNode()) // (eg fForward = true) or previousNode() (eg fForward = false). // Note also, if removing a Node, the fCurrentNode // can be placed on a Node which would not pass filters. /** The last Node returned. */ private Node fCurrentNode; /** The direction of the iterator on the fCurrentNode. *
* nextNode() == fForward = true; * previousNode() == fForward = false; **/ private boolean fForward = true; /** When TRUE, the children of entites references are returned in the iterator. */ private boolean fEntityReferenceExpansion; // // Constructor // /** Public constructor */ public NodeIteratorImpl( DocumentImpl document, Node root, int whatToShow, NodeFilter nodeFilter, boolean entityReferenceExpansion) { fDocument = document; fRoot = root; fCurrentNode = null; fWhatToShow = whatToShow; fNodeFilter = nodeFilter; fEntityReferenceExpansion = entityReferenceExpansion; } public Node getRoot() { return fRoot; } // Implementation Note: Note that the iterator looks at whatToShow // and filter values at each call, and therefore one _could_ add // setters for these values and alter them while iterating! /** Return the whatToShow value */ public int getWhatToShow() { return fWhatToShow; } /** Return the filter */ public NodeFilter getFilter() { return fNodeFilter; } /** Return whether children entity references are included in the iterator. */ public boolean getExpandEntityReferences() { return fEntityReferenceExpansion; } /** Return the next Node in the Iterator. The node is the next node in * depth-first order which also passes the filter, and whatToShow. * If there is no next node which passes these criteria, then return null. */ public Node nextNode() { if( fDetach) { throw new DOMException( DOMException.INVALID_STATE_ERR, DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); } // if root is null there is no next node. if (fRoot == null) return null; Node nextNode = fCurrentNode; boolean accepted = false; // the next node has not been accepted. accepted_loop: while (!accepted) { // if last direction is not forward, repeat node. if (!fForward && nextNode!=null) { //System.out.println("nextNode():!fForward:"+fCurrentNode.getNodeName()); nextNode = fCurrentNode; } else { // else get the next node via depth-first if (!fEntityReferenceExpansion && nextNode != null && nextNode.getNodeType() == Node.ENTITY_REFERENCE_NODE) { nextNode = nextNode(nextNode, false); } else { nextNode = nextNode(nextNode, true); } } fForward = true; //REVIST: should direction be set forward before null check? // nothing in the list. return null. if (nextNode == null) return null; // does node pass the filters and whatToShow? accepted = acceptNode(nextNode); if (accepted) { // if so, then the node is the current node. fCurrentNode = nextNode; return fCurrentNode; } else continue accepted_loop; } // while (!accepted) { // no nodes, or no accepted nodes. return null; } /** Return the previous Node in the Iterator. The node is the next node in * _backwards_ depth-first order which also passes the filter, and whatToShow. */ public Node previousNode() { if( fDetach) { throw new DOMException( DOMException.INVALID_STATE_ERR, DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); } // if the root is null, or the current node is null, return null. if (fRoot == null || fCurrentNode == null) return null; Node previousNode = fCurrentNode; boolean accepted = false; accepted_loop: while (!accepted) { if (fForward && previousNode != null) { //repeat last node. previousNode = fCurrentNode; } else { // get previous node in backwards depth first order. previousNode = previousNode(previousNode); } // we are going backwards fForward = false; // if the new previous node is null, we're at head or past the root, // so return null. if (previousNode == null) return null; // check if node passes filters and whatToShow. accepted = acceptNode(previousNode); if (accepted) { // if accepted, update the current node, and return it. fCurrentNode = previousNode; return fCurrentNode; } else continue accepted_loop; } // there are no nodes? return null; } /** The node is accepted if it passes the whatToShow and the filter. */ boolean acceptNode(Node node) { if (fNodeFilter == null) { return ( fWhatToShow & (1 << node.getNodeType()-1)) != 0 ; } else { return ((fWhatToShow & (1 << node.getNodeType()-1)) != 0 ) && fNodeFilter.acceptNode(node) == NodeFilter.FILTER_ACCEPT; } } /** Return node, if matches or any parent if matches. */ Node matchNodeOrParent(Node node) { // Additions and removals in the underlying data structure may occur // before any iterations, and in this case the reference_node is null. if (fCurrentNode == null) return null; // check if the removed node is an _ancestor_ of the // reference node for (Node n = fCurrentNode; n != fRoot; n = n.getParentNode()) { if (node == n) return n; } return null; } /** The method nextNode(Node, boolean) returns the next node * from the actual DOM tree. * * The boolean visitChildren determines whether to visit the children. * The result is the nextNode. */ Node nextNode(Node node, boolean visitChildren) { if (node == null) return fRoot; Node result; // only check children if we visit children. if (visitChildren) { //if hasChildren, return 1st child. if (node.hasChildNodes()) { result = node.getFirstChild(); return result; } } if (node == fRoot) { //if Root has no kids return null; } // if hasSibling, return sibling result = node.getNextSibling(); if (result != null) return result; // return parent's 1st sibling. Node parent = node.getParentNode(); while (parent != null && parent != fRoot) { result = parent.getNextSibling(); if (result != null) { return result; } else { parent = parent.getParentNode(); } } // while (parent != null && parent != fRoot) { // end of list, return null return null; } /** The method previousNode(Node) returns the previous node * from the actual DOM tree. */ Node previousNode(Node node) { Node result; // if we're at the root, return null. if (node == fRoot) return null; // get sibling result = node.getPreviousSibling(); if (result == null) { //if 1st sibling, return parent result = node.getParentNode(); return result; } // if sibling has children, keep getting last child of child. if (result.hasChildNodes() && !(!fEntityReferenceExpansion && result != null && result.getNodeType() == Node.ENTITY_REFERENCE_NODE)) { while (result.hasChildNodes()) { result = result.getLastChild(); } } return result; } /** Fix-up the iterator on a remove. Called by DOM or otherwise, * before an actual DOM remove. */ public void removeNode(Node node) { // Implementation note: Fix-up means setting the current node properly // after a remove. if (node == null) return; Node deleted = matchNodeOrParent(node); if (deleted == null) return; if (fForward) { fCurrentNode = previousNode(deleted); } else // if (!fForward) { Node next = nextNode(deleted, false); if (next!=null) { // normal case: there _are_ nodes following this in the iterator. fCurrentNode = next; } else { // the last node in the iterator is to be removed, // so we set the current node to be the previous one. fCurrentNode = previousNode(deleted); fForward = true; } } } public void detach() { fDetach = true; fDocument.removeNodeIterator(this); } }