123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- /* DomIterator.java --
- Copyright (C) 1999, 2000, 2001, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 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();
- }
- // There's no way up or sideways from the root, so if we
- // couldn't move down to a child, there's nowhere to go.
- //
- if (here == root)
- return null;
- //
- // 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 we have exceeded the root node then stop traversing.
- if (next == root.getParentNode())
- {
- 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.
- }
- }
|