1. /*
  2. * $Id: TreeWalker.java,v 1.3 2001/01/19 01:52:17 edwingo Exp $
  3. *
  4. * The Apache Software License, Version 1.1
  5. *
  6. *
  7. * Copyright (c) 2000 The Apache Software Foundation. All rights
  8. * reserved.
  9. *
  10. * Redistribution and use in source and binary forms, with or without
  11. * modification, are permitted provided that the following conditions
  12. * are met:
  13. *
  14. * 1. Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. *
  17. * 2. Redistributions in binary form must reproduce the above copyright
  18. * notice, this list of conditions and the following disclaimer in
  19. * the documentation and/or other materials provided with the
  20. * distribution.
  21. *
  22. * 3. The end-user documentation included with the redistribution,
  23. * if any, must include the following acknowledgment:
  24. * "This product includes software developed by the
  25. * Apache Software Foundation (http://www.apache.org/)."
  26. * Alternately, this acknowledgment may appear in the software itself,
  27. * if and wherever such third-party acknowledgments normally appear.
  28. *
  29. * 4. The names "Crimson" and "Apache Software Foundation" must
  30. * not be used to endorse or promote products derived from this
  31. * software without prior written permission. For written
  32. * permission, please contact apache@apache.org.
  33. *
  34. * 5. Products derived from this software may not be called "Apache",
  35. * nor may "Apache" appear in their name, without prior written
  36. * permission of the Apache Software Foundation.
  37. *
  38. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  39. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  40. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  41. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  42. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  43. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  44. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  45. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  46. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  47. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  48. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  49. * SUCH DAMAGE.
  50. * ====================================================================
  51. *
  52. * This software consists of voluntary contributions made by many
  53. * individuals on behalf of the Apache Software Foundation and was
  54. * originally based on software copyright (c) 1999, Sun Microsystems, Inc.,
  55. * http://www.sun.com. For more information on the Apache Software
  56. * Foundation, please see <http://www.apache.org/>.
  57. */
  58. package org.apache.crimson.tree;
  59. import java.util.Locale;
  60. import org.w3c.dom.Element;
  61. import org.w3c.dom.Node;
  62. /**
  63. * This class implements a preorder depth first walk over the tree rooted
  64. * at a given DOM node. The traversal is "live", and terminates either when
  65. * that given node is reached when climbing back up, or when a null parent
  66. * node is reached. It may be restarted via <em>reset()</em>.
  67. *
  68. * <P> The way this remains live is to have a "current" node to which the
  69. * walk is tied. If the tree is modified, that current node will always
  70. * still be valid ... even if it is no longer connected to the rest of
  71. * the document, or if it's reconnected at a different location. The
  72. * behavior of tree modifications is specified by DOM, and the interaction
  73. * with a walker's current node is specified entirely by knowing that only
  74. * the getNextSibling, getParentNode, and getFirstChild methods are used
  75. * for walking the tree.
  76. *
  77. * <P> For example, if the current branch is cut off, the walk will stop
  78. * when it tries to access what were parents or siblings of that node.
  79. * (That is, the walk will continue over the branch that was cut.) If
  80. * that is not the intended behaviour, one must change the "current" branch
  81. * before cutting ... much like avoiding trimming a branch off a real
  82. * tree if someone is sitting on it. The <em>removeCurrent()</em>
  83. * method encapsulates that logic.
  84. *
  85. * @author David Brownell
  86. * @version $Revision: 1.3 $
  87. */
  88. public class TreeWalker
  89. {
  90. // yes, this is really a "TreeIterator" but the DOM WG plans to
  91. // use such names in Level 2 ... so, we avoid it for now. (note
  92. // that they've also discussed a "TreeWalker" that's rather more
  93. // complex than this iterator...)
  94. private Node startPoint;
  95. private Node current;
  96. /**
  97. * Constructs a tree walker starting at the given node.
  98. */
  99. public TreeWalker (Node initial)
  100. {
  101. if (initial == null) {
  102. throw new IllegalArgumentException (XmlDocument.catalog.
  103. getMessage (Locale.getDefault (), "TW-004"));
  104. }
  105. if (!(initial instanceof NodeBase)) {
  106. throw new IllegalArgumentException (XmlDocument.catalog.
  107. getMessage (Locale.getDefault (), "TW-003"));
  108. }
  109. startPoint = current = initial;
  110. }
  111. /**
  112. * Returns the current node.
  113. */
  114. public Node getCurrent ()
  115. {
  116. return current;
  117. }
  118. /**
  119. * Advances to the next node, and makes that current. Returns
  120. * null if there are no more nodes through which to walk,
  121. * because the initial node was reached or because a null
  122. * parent was reached.
  123. *
  124. * @return the next node (which becomes current), or else null
  125. */
  126. public Node getNext ()
  127. {
  128. Node next;
  129. if (current == null)
  130. return null;
  131. switch (current.getNodeType ()) {
  132. case Node.DOCUMENT_FRAGMENT_NODE:
  133. case Node.DOCUMENT_NODE:
  134. case Node.ELEMENT_NODE:
  135. case Node.ENTITY_REFERENCE_NODE:
  136. //
  137. // For elements that can have children, visit those
  138. // children before any siblings (i.e. depth first)
  139. // and after visiting this node (i.e. preorder)
  140. //
  141. next = current.getFirstChild ();
  142. if (next != null) {
  143. current = next;
  144. return next;
  145. }
  146. // FALLTHROUGH
  147. case Node.ATTRIBUTE_NODE:
  148. // NOTE: attributes "should" have children ...
  149. case Node.CDATA_SECTION_NODE:
  150. case Node.COMMENT_NODE:
  151. case Node.DOCUMENT_TYPE_NODE:
  152. case Node.ENTITY_NODE:
  153. case Node.NOTATION_NODE:
  154. case Node.PROCESSING_INSTRUCTION_NODE:
  155. case Node.TEXT_NODE:
  156. //
  157. // For childless nodes, only look at siblings. If no
  158. // siblings, climb the tree till we get to a spot there
  159. // are siblings, or till we terminate our walk.
  160. //
  161. for (Node here = current;
  162. here != null && here != startPoint;
  163. here = here.getParentNode ()) {
  164. next = here.getNextSibling ();
  165. if (next != null) {
  166. current = next;
  167. return next;
  168. }
  169. }
  170. current = null;
  171. return null;
  172. }
  173. throw new InternalError (((NodeBase)startPoint).getMessage ("TW-000",
  174. new Object [] { Short.toString (current.
  175. getNodeType ()) }));
  176. }
  177. /**
  178. * Convenience method to walk only through elements with the specified
  179. * tag name. This just calls getNext() and filters out the nodes which
  180. * aren't desired. It returns null when the iteration completes.
  181. *
  182. * @param tag the tag to match, or null to indicate all elements
  183. * @return the next matching element, or else null
  184. */
  185. public Element getNextElement (String tag)
  186. {
  187. for (Node next = getNext ();
  188. next != null;
  189. next = getNext ()) {
  190. if (next.getNodeType () == Node.ELEMENT_NODE
  191. && (tag == null || tag.equals (next.getNodeName ())))
  192. return (Element) next;
  193. }
  194. current = null;
  195. return null;
  196. }
  197. /**
  198. * Namespace version
  199. */
  200. public Element getNextElement(String nsURI, String localName) {
  201. for (Node next = getNext(); next != null; next = getNext()) {
  202. if (next.getNodeType() == Node.ELEMENT_NODE
  203. && (nsURI == null || nsURI.equals(next.getNamespaceURI()))
  204. && (localName == null ||
  205. localName.equals(next.getLocalName()))) {
  206. return (Element)next;
  207. }
  208. }
  209. current = null;
  210. return null;
  211. }
  212. /**
  213. * Resets the walker to the state in which it was created: the
  214. * current node will be the node given to the constructor. If
  215. * the tree rooted at that node has been modified from the previous
  216. * traversal, the sequence of nodes returned by <em>getNext()</em>
  217. * will differ accordingly.
  218. */
  219. public void reset ()
  220. {
  221. current = startPoint;
  222. }
  223. /**
  224. * Removes the current node; reassigns the current node to be the
  225. * next one in the current walk that isn't a child of the (removed)
  226. * current node, and returns that new current node. In a loop, this
  227. * could be used instead of a <em>getNext()</em>.
  228. *
  229. * @return the next node (which becomes current), or else null
  230. * @throws IllegalStateException if the current node is null
  231. * or has no parent (it is a Document or DocumentFragment)
  232. */
  233. public Node removeCurrent ()
  234. {
  235. if (current == null)
  236. throw new IllegalStateException (((NodeBase)startPoint).
  237. getMessage ("TW-001"));
  238. Node toRemove = current;
  239. Node parent = current.getParentNode ();
  240. Node retval = null;
  241. if (parent == null)
  242. throw new IllegalStateException (((NodeBase)startPoint).
  243. getMessage ("TW-002"));
  244. //
  245. // Don't look at children, just siblings/parents
  246. //
  247. for (Node here = current;
  248. here != null && here != startPoint;
  249. here = here.getParentNode ()) {
  250. retval = here.getNextSibling ();
  251. if (retval != null) {
  252. current = retval;
  253. break;
  254. }
  255. }
  256. parent.removeChild (toRemove);
  257. return retval;
  258. }
  259. }