| /* DomIterator.java -- |
| Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package gnu.xml.dom; |
| |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.events.Event; |
| import org.w3c.dom.events.EventListener; |
| import org.w3c.dom.events.EventTarget; |
| import org.w3c.dom.events.MutationEvent; |
| import org.w3c.dom.traversal.NodeFilter; |
| import org.w3c.dom.traversal.NodeIterator; |
| |
| /** |
| * <p> "NodeIterator" implementation, usable with any L2 DOM which |
| * supports MutationEvents. </p> |
| * |
| * @author David Brownell |
| */ |
| public final class DomIterator |
| implements NodeIterator, EventListener |
| { |
| |
| private Node reference; |
| private boolean right; |
| private boolean done; |
| |
| private final Node root; |
| private final int whatToShow; |
| private final NodeFilter filter; |
| private final boolean expandEntityReferences; |
| |
| /** |
| * Constructs and initializes an iterator. |
| */ |
| protected DomIterator(Node root, |
| int whatToShow, |
| NodeFilter filter, |
| boolean entityReferenceExpansion) |
| { |
| if (!root.isSupported("MutationEvents", "2.0")) |
| { |
| throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR, |
| "Iterator needs mutation events", root, 0); |
| } |
| |
| this.root = root; |
| this.whatToShow = whatToShow; |
| this.filter = filter; |
| this.expandEntityReferences = entityReferenceExpansion; |
| |
| // start condition: going right, seen nothing yet. |
| reference = null; |
| right = true; |
| |
| EventTarget target = (EventTarget) root; |
| target.addEventListener("DOMNodeRemoved", this, false); |
| } |
| |
| /** |
| * <b>DOM L2</b> |
| * Flags the iterator as done, unregistering its event listener so |
| * that the iterator can be garbage collected without relying on weak |
| * references (a "Java 2" feature) in the event subsystem. |
| */ |
| public void detach() |
| { |
| EventTarget target = (EventTarget) root; |
| target.removeEventListener("DOMNodeRemoved", this, false); |
| done = true; |
| } |
| |
| /** |
| * <b>DOM L2</b> |
| * Returns the flag controlling whether iteration descends |
| * through entity references. |
| */ |
| public boolean getExpandEntityReferences() |
| { |
| return expandEntityReferences; |
| } |
| |
| /** |
| * <b>DOM L2</b> |
| * Returns the filter provided during construction. |
| */ |
| public NodeFilter getFilter() |
| { |
| return filter; |
| } |
| |
| /** |
| * <b>DOM L2</b> |
| * Returns the root of the tree this is iterating through. |
| */ |
| public Node getRoot() |
| { |
| return root; |
| } |
| |
| /** |
| * <b>DOM L2</b> |
| * Returns the mask of flags provided during construction. |
| */ |
| public int getWhatToShow() |
| { |
| return whatToShow; |
| } |
| |
| /** |
| * <b>DOM L2</b> |
| * Returns the next node in a forward iteration, masked and filtered. |
| * Note that the node may be read-only due to entity expansions. |
| * A null return indicates the iteration is complete, but may still |
| * be processed backwards. |
| */ |
| public Node nextNode() |
| { |
| if (done) |
| { |
| throw new DomDOMException(DOMException.INVALID_STATE_ERR); |
| } |
| right = true; |
| return walk(true); |
| } |
| |
| /** |
| * <b>DOM L2</b> |
| * Returns the next node in a backward iteration, masked and filtered. |
| * Note that the node may be read-only due to entity expansions. |
| * A null return indicates the iteration is complete, but may still |
| * be processed forwards. |
| */ |
| public Node previousNode() |
| { |
| if (done) |
| { |
| throw new DomDOMException(DOMException.INVALID_STATE_ERR); |
| } |
| Node previous = reference; |
| right = false; |
| walk(false); |
| return previous; |
| } |
| |
| private boolean shouldShow(Node node) |
| // raises Runtime exceptions indirectly, via acceptNode() |
| { |
| if ((whatToShow & (1 << (node.getNodeType() - 1))) == 0) |
| { |
| return false; |
| } |
| if (filter == null) |
| { |
| return true; |
| } |
| return filter.acceptNode(node) == NodeFilter.FILTER_ACCEPT; |
| } |
| |
| // |
| // scenario: root = 1, sequence = 1 2 ... 3 4 |
| // forward walk: 1 2 ... 3 4 null |
| // then backward: 4 3 ... 2 1 null |
| // |
| // At the leftmost end, "previous" == null |
| // At the rightmost end, "previous" == 4 |
| // |
| // The current draft spec really seem to make no sense re the |
| // role of the reference node, so what it says is ignored here. |
| // |
| private Node walk(boolean forward) |
| { |
| Node here = reference; |
| |
| while ((here = successor(here, forward)) != null |
| && !shouldShow(here)) |
| { |
| continue; |
| } |
| if (here != null || !forward) |
| { |
| reference = here; |
| } |
| return here; |
| } |
| |
| private boolean isLeaf(Node here) |
| { |
| boolean leaf = !here.hasChildNodes(); |
| if (!leaf && !expandEntityReferences) |
| { |
| leaf = (here.getNodeType() == Node.ENTITY_REFERENCE_NODE); |
| } |
| return leaf; |
| } |
| |
| // |
| // Returns the immediate successor in a forward (or backward) |
| // document order walk, sans filtering ... except that it knows |
| // how to stop, returning null when done. This is a depth first |
| // preorder traversal when run in the forward direction. |
| // |
| private Node successor(Node here, boolean forward) |
| { |
| Node next; |
| |
| // the "leftmost" end is funky |
| if (here == null) |
| { |
| return forward ? root : null; |
| } |
| |
| // |
| // Forward, this is preorder: children before siblings. |
| // Backward, it's postorder: we saw the children already. |
| // |
| if (forward && !isLeaf(here)) |
| { |
| return here.getFirstChild(); |
| } |
| |
| // |
| // Siblings ... if forward, we visit them, if backwards |
| // we visit their children first. |
| // |
| if (forward) |
| { |
| if ((next = here.getNextSibling()) != null) |
| { |
| return next; |
| } |
| } |
| else if ((next = here.getPreviousSibling()) != null) |
| { |
| if (isLeaf(next)) |
| { |
| return next; |
| } |
| next = next.getLastChild(); |
| while (!isLeaf(next)) |
| { |
| next = next.getLastChild(); |
| } |
| return next; |
| } |
| |
| // |
| // We can't go down or lateral -- it's up, then. The logic is |
| // the converse of what's above: backwards is easy (the parent |
| // is next), forwards isn't. |
| // |
| next = here.getParentNode(); |
| if (!forward) |
| { |
| return next; |
| } |
| |
| Node temp = null; |
| while (next != null |
| && next != root |
| && (temp = next.getNextSibling()) == null) |
| { |
| next = next.getParentNode(); |
| } |
| if (next == root) |
| { |
| return null; |
| } |
| return temp; |
| } |
| |
| /** |
| * Not for public use. This lets the iterator know when its |
| * reference node will be removed from the tree, so that a new |
| * one may be selected. |
| * |
| * <p> This version works by watching removal events as they |
| * bubble up. So, don't prevent them from bubbling. |
| */ |
| public void handleEvent(Event e) |
| { |
| MutationEvent event; |
| Node ancestor, removed; |
| |
| if (reference == null |
| || !"DOMNodeRemoved".equals(e.getType()) |
| || e.getEventPhase() != Event.BUBBLING_PHASE) |
| { |
| return; |
| } |
| |
| event = (MutationEvent) e; |
| removed = (Node) event.getTarget(); |
| |
| // See if the removal will cause trouble for this iterator |
| // by being the reference node or an ancestor of it. |
| for (ancestor = reference; |
| ancestor != null && ancestor != root; |
| ancestor = ancestor.getParentNode()) |
| { |
| if (ancestor == removed) |
| { |
| break; |
| } |
| } |
| if (ancestor != removed) |
| { |
| return; |
| } |
| |
| // OK, it'll cause trouble. We want to make the "next" |
| // node in our current traversal direction seem right. |
| // So we pick the nearest node that's not getting removed, |
| // but go in the _opposite_ direction from our current |
| // traversal ... so the "next" doesn't skip anything. |
| Node candidate; |
| |
| search: |
| while ((candidate = walk(!right)) != null) |
| { |
| for (ancestor = candidate; |
| ancestor != null && ancestor != root; |
| ancestor = ancestor.getParentNode()) |
| { |
| if (ancestor == removed) |
| { |
| continue search; |
| } |
| } |
| return; |
| } |
| |
| // The current DOM WD talks about a special case here; |
| // I've not yet seen it. |
| } |
| |
| } |
| |