/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.xerces.dom;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;


DefaultNodeIterator implements a NodeIterator, which iterates a DOM tree in the expected depth first way.

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.

@xerces.internal
Version:$Id: NodeIteratorImpl.java 447266 2006-09-18 05:57:49Z mrglavas $
/** DefaultNodeIterator implements a NodeIterator, which iterates a * DOM tree in the expected depth first way. * * <p>The whatToShow and filter functionality is implemented as expected. * * <p>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. * * @xerces.internal * * @version $Id: NodeIteratorImpl.java 447266 2006-09-18 05:57:49Z mrglavas $ */
public class NodeIteratorImpl implements NodeIterator { // // Data //
The DocumentImpl which created this iterator, so it can be detached.
/** The DocumentImpl which created this iterator, so it can be detached. */
private DocumentImpl fDocument;
The root.
/** The root. */
private Node fRoot;
The whatToShow mask.
/** The whatToShow mask. */
private int fWhatToShow = NodeFilter.SHOW_ALL;
The NodeFilter reference.
/** The NodeFilter reference. */
private NodeFilter fNodeFilter;
If detach is called, the fDetach flag is true, otherwise flase.
/** 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.
/** The last Node returned. */
private Node fCurrentNode;
The direction of the iterator on the fCurrentNode.
 nextNode()  ==      fForward = true;
 previousNode() ==   fForward = false;
 
/** The direction of the iterator on the fCurrentNode. * <pre> * nextNode() == fForward = true; * previousNode() == fForward = false; * </pre> */
private boolean fForward = true;
When TRUE, the children of entites references are returned in the iterator.
/** When TRUE, the children of entites references are returned in the iterator. */
private boolean fEntityReferenceExpansion; // // Constructor //
Public 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
/** Return the whatToShow value */
public int getWhatToShow() { return fWhatToShow; }
Return the filter
/** Return the filter */
public NodeFilter getFilter() { return fNodeFilter; }
Return whether children entity references are included in the iterator.
/** 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.
/** 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.
/** 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.
/** 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.
/** 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.
/** 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.
/** 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.
/** 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); } }