/* * © Copyright IBM Corp. 2012 * * Licensed 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 com.ibm.commons.xml; import java.util.*; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ranges.Range; /** * * Iterator over the objects in a Range. * * @author dloverin * Jun 9, 2006 * */ public class RangeIterator implements ListIterator { private Node _currentNode; // hold place for traversal private int _currentOffset; // current offset in container of _currentNode private Node _commonAncestor; // derived from _range private Range _range; private Map _descentPath; // descent from the common ancestor to the starting container private int _currentIndex; // index of iterator private boolean _firstChild; // if true start at first child, not common ancestor /** * NOT READY FOR USE YET. PLEASE DO NOT USE! * * Create a new iterator that will return each node encounters while * walking the dom tree from the beginning of the range to the end of * the range. * The walk of the range begins at the common ancestor of the range endpoints * and continues in a depth first traversal of the tree. * * Parent containers are included in the iteration after reaching the last child in * a container. * * @param range */ public RangeIterator(Range range) { this(range, false); } /** * Same iterator with an option to start the iterator at the range's first node. * @param range - range must be normalized. * @param firstChild - if true start the the range's first child instead of * the common ancestor. */ public RangeIterator(Range range, boolean firstChild) { if (range == null || range.getStartContainer() == null || range.getEndContainer() == null) { throw new NullPointerException(); } _range = range; _firstChild = firstChild; initIterator(); } public boolean hasNext() { if (_commonAncestor == null) { return false; } if (_currentNode == null) { return true; } if (isAtEndOfRange(_currentNode, _currentOffset)) { return false; } return true; } public Object next() { if (_commonAncestor == null) { throw new NoSuchElementException(); } if (_currentNode == null) { if (_firstChild) { _currentNode = _range.getStartContainer(); if (_currentNode.hasChildNodes()) { _currentNode = _currentNode.getChildNodes().item(_range.getStartOffset()); _currentOffset = 0; } else { _currentOffset = _range.getStartOffset(); } } else { _currentNode = _commonAncestor; _currentOffset = ((Integer)_descentPath.get(_commonAncestor)).intValue(); } _currentIndex++; return _currentNode; } if (isAtEndOfRange(_currentNode, _currentOffset)) { throw new NoSuchElementException(); } if (_currentNode.hasChildNodes()) { Integer offsetInt = (Integer)_descentPath.get(_currentNode); if (offsetInt == null) { _currentNode = _currentNode.getFirstChild(); _currentIndex++; _currentOffset = 0; return _currentNode; } else { Node tempNode = _currentNode.getChildNodes().item(offsetInt.intValue()); if (tempNode != null) { _currentNode = tempNode; _currentIndex++; offsetInt = (Integer)_descentPath.get(_currentNode); _currentOffset = (offsetInt == null) ? 0 : offsetInt.intValue(); return _currentNode; } } } // when we are at the end of the sibling for a node, // go to the parent and get the next sibing in the parent. Node nextNode = _currentNode.getNextSibling(); _currentOffset++; while (nextNode == null) { _currentNode = _currentNode.getParentNode(); if (_currentNode == _commonAncestor) { throw new NoSuchElementException(); } nextNode = _currentNode.getNextSibling(); _currentOffset = 0; } _currentNode = nextNode; _currentIndex++; return _currentNode; } /** * currently not supported */ public void remove() { throw new UnsupportedOperationException(); } /** * init state of this iterator * */ private void initIterator() { _currentIndex = 0; // init the common ascestor _commonAncestor = _range.getCommonAncestorContainer(); // this can hapend when an element has been removed from the model // but the changes have not been flushed. if (_commonAncestor == null) { return; } // create a map of nodes to child index to create a path // to descend the tree from the common ancestor to the // starting container _descentPath = new HashMap(); Node node = _range.getStartContainer(); int offset = _range.getStartOffset(); Node ancestorParent = _commonAncestor.getParentNode(); while (node != ancestorParent) { _descentPath.put(node, Integer.valueOf(offset)); offset = getOffsetInContainer(node); node = node.getParentNode(); } } /** * Helper method to get the offset of a given node with respect to its * parent. * * @param node - * may not be null * @return int - zero-based offset of node in its container or -1 if the * node as no container. */ public static int getOffsetInContainer(Node node) { Node parent = node.getParentNode(); if (parent != null) { NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = (Node) children.item(i); if (child == node) { return i; } } throw new IllegalStateException(); // how could the child not be found? } return -1; } /** * NOT SUPPORTED * @param arg0 */ public void add(Object arg0) { throw new UnsupportedOperationException(); } public boolean hasPrevious() { return _currentIndex == 0 ? true : false; } public int nextIndex() { return _currentIndex + 1; } public Object previous() { if (_currentIndex == 0) { throw new NoSuchElementException(); } // return the current node Node retNode = _currentNode; // update the position of the current node for the next operation Node prev = _currentNode.getPreviousSibling(); if (prev == null) { _currentNode = _currentNode.getParentNode(); } else { // get the last child of this node if it has children if (prev.hasChildNodes()) { NodeList children = prev.getChildNodes(); _currentNode = children.item(children.getLength() - 1); } else { _currentNode = prev; } } _currentIndex--; return retNode; } public int previousIndex() { return _currentIndex - 1; } /** * NOT SUPPORTED * * @param arg0 */ public void set(Object arg0) { throw new UnsupportedOperationException(); } /** * Test if node is at the end of the range of this iterator. * @param node * @return true node is the last node in the range. */ private boolean isAtEndOfRange(Node node, int offset) { // don't compare offsets on the text node since we are jumping node to node. if (node.getNodeType() == Node.TEXT_NODE && node == _range.getEndContainer()) { return true; } int result = DOMUtil.compareLocations(node, offset, _range.getEndContainer(), _range.getEndOffset()); if (result >= 0) { return true; } Node currentNode = node; Node endNode = _range.getEndContainer(); if (currentNode.hasChildNodes()) { return false; } // check if all the parents between the current node and the end node are at their last child. // if so then we are done. Node parent = currentNode.getParentNode(); while (parent != null) { NodeList children = parent.getChildNodes(); if (children.item(children.getLength() - 1) != currentNode) { break; } if (parent == endNode) { return true; // found no nodes to iterate to } currentNode = parent; parent = currentNode.getParentNode(); } return false; } /** * Map a node and offset into an actual node. * * @param node * @param offset * @return */ // private Node getEffectiveNode(Node node, int offset) { // if (node.hasChildNodes()) { // NodeList children = node.getChildNodes(); // if (offset == children.getLength()) { // offset--; // } // node = children.item(offset); // } // // return node; // } // }