View Javadoc

1   package org.jaxen.dom;
2   
3   /*
4    * $Header: /home/projects/jaxen/scm/jaxen/src/java/main/org/jaxen/dom/DocumentNavigator.java,v 1.35 2005/04/06 09:15:15 elharo Exp $
5    * $Revision: 1.35 $
6    * $Date: 2005/04/06 09:15:15 $
7    *
8    * ====================================================================
9    *
10   * Copyright (C) 2000-2005 bob mcwhirter & James Strachan.
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or without
14   * modification, are permitted provided that the following conditions
15   * are met:
16   *
17   * 1. Redistributions of source code must retain the above copyright
18   *    notice, this list of conditions, and the following disclaimer.
19   *
20   * 2. Redistributions in binary form must reproduce the above copyright
21   *    notice, this list of conditions, and the disclaimer that follows
22   *    these conditions in the documentation and/or other materials
23   *    provided with the distribution.
24   *
25   * 3. The name "Jaxen" must not be used to endorse or promote products
26   *    derived from this software without prior written permission.  For
27   *    written permission, please contact license@jaxen.org.
28   *
29   * 4. Products derived from this software may not be called "Jaxen", nor
30   *    may "Jaxen" appear in their name, without prior written permission
31   *    from the Jaxen Project Management (pm@jaxen.org).
32   *
33   * In addition, we request (but do not require) that you include in the
34   * end-user documentation provided with the redistribution and/or in the
35   * software itself an acknowledgement equivalent to the following:
36   *     "This product includes software developed by the
37   *      Jaxen Project (http://www.jaxen.org/)."
38   * Alternatively, the acknowledgment may be graphical using the logos
39   * available at http://www.jaxen.org/
40   *
41   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44   * DISCLAIMED.  IN NO EVENT SHALL THE Jaxen AUTHORS OR THE PROJECT
45   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52   * SUCH DAMAGE.
53   *
54   * ====================================================================
55   * This software consists of voluntary contributions made by many
56   * individuals on behalf of the Jaxen Project and was originally
57   * created by bob mcwhirter <bob@werken.com> and
58   * James Strachan <jstrachan@apache.org>.  For more information on the
59   * Jaxen Project, please see <http://www.jaxen.org/>.
60   *
61   * $Id: DocumentNavigator.java,v 1.35 2005/04/06 09:15:15 elharo Exp $
62  */
63  
64  import javax.xml.parsers.DocumentBuilder;
65  import javax.xml.parsers.DocumentBuilderFactory;
66  import java.util.HashMap;
67  import java.util.Iterator;
68  import java.util.NoSuchElementException;
69  
70  import org.jaxen.DefaultNavigator;
71  import org.jaxen.FunctionCallException;
72  import org.jaxen.Navigator;
73  import org.jaxen.XPath;
74  import org.jaxen.JaxenConstants;
75  import org.w3c.dom.Attr;
76  import org.w3c.dom.Document;
77  import org.w3c.dom.NamedNodeMap;
78  import org.w3c.dom.Node;
79  import org.w3c.dom.NodeList;
80  import org.w3c.dom.ProcessingInstruction;
81  
82  /*** Interface for navigating around the W3C DOM Level 2 object model.
83   *
84   *  <p>
85   *  This class is not intended for direct usage, but is
86   *  used by the Jaxen engine during evaluation.
87   *  </p>
88   *
89   *  <p>This class implements the org.jaxen.DefaultNavigator interface
90   *  for the Jaxen XPath library, version 1.0beta3 (it is not guaranteed
91   *  to work with subsequent releases).  This adapter allows the Jaxen
92   *  library to be used to execute XPath queries against any object tree
93   *  that implements the DOM level 2 interfaces.</p>
94   *
95   *  <p>Note: DOM level 2 does not include a node representing an XML
96   *  Namespace declaration.  This navigator will return Namespace decls
97   *  as instantiations of the custom {@link NamespaceNode} class, and
98   *  users will have to check result sets to locate and isolate
99   *  these.</p>
100  *
101  *  @author David Megginson
102  *  @author James Strachan
103  *
104  *  @see XPath
105  *  @see NamespaceNode
106  */
107 public class DocumentNavigator extends DefaultNavigator
108 {
109 
110     
111     ////////////////////////////////////////////////////////////////////
112     // Constants.
113     ////////////////////////////////////////////////////////////////////
114 
115     /***
116      * Constant: singleton navigator.
117      */
118     private final static DocumentNavigator SINGLETON =
119     new DocumentNavigator();
120 
121 
122     
123     ////////////////////////////////////////////////////////////////////
124     // Constructor.
125     ////////////////////////////////////////////////////////////////////
126 
127 
128     /***
129      * Default Constructor.
130      */
131     public DocumentNavigator ()
132     {
133     }
134 
135 
136     /***
137      * Get a singleton DocumentNavigator for efficiency.
138      *
139      * @return a singleton instance of a DocumentNavigator.
140      */
141     public static Navigator getInstance ()
142     {
143         return SINGLETON;
144     }
145 
146 
147     
148     ////////////////////////////////////////////////////////////////////
149     // Implementation of org.jaxen.DefaultNavigator.
150     ////////////////////////////////////////////////////////////////////
151 
152 
153     /***
154      * Get an iterator over all of this node's children.
155      *
156      * @param contextNode the context node for the child axis.
157      * @return a possibly-empty iterator (not null)
158      */
159     public Iterator getChildAxisIterator (Object contextNode)
160     {
161         return new NodeIterator ((Node)contextNode) {
162                 protected Node getFirstNode (Node node)
163                 {
164                     return node.getFirstChild();
165                 }
166                 protected Node getNextNode (Node node)
167                 {
168                     return node.getNextSibling();
169                 }
170             };
171     }
172 
173 
174     /***
175      * Get a (single-member) iterator over this node's parent.
176      *
177      * @param contextNode the context node for the parent axis
178      * @return a possibly-empty iterator (not null)
179      */
180     public Iterator getParentAxisIterator (Object contextNode)
181     {
182         Node node = (Node)contextNode;
183 
184         if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
185             return new NodeIterator (node) {
186                     protected Node getFirstNode (Node n)
187                     {
188                         // FIXME: assumes castability.
189                         return ((Attr)n).getOwnerElement();
190                     }
191                     protected Node getNextNode (Node n) {
192                         return null;
193                     }
194                 };
195         } else {
196             return new NodeIterator (node) {
197                     protected Node getFirstNode (Node n)
198                     {
199                         return n.getParentNode();
200                     }
201                     protected Node getNextNode (Node n) {
202                         return null;
203                     }
204                 };
205         }
206     }
207 
208 
209     /***
210      * Get an iterator over all following siblings.
211      *
212      * @param contextNode the context node for the sibling iterator
213      * @return a possibly-empty iterator (not null)
214      */
215     public Iterator getFollowingSiblingAxisIterator (Object contextNode)
216     {
217         return new NodeIterator ((Node)contextNode) {
218                 protected Node getFirstNode (Node node)
219                 {
220                     return getNextNode(node);
221                 }
222                 protected Node getNextNode (Node node) {
223                     return node.getNextSibling();
224                 }
225             };
226     }
227 
228 
229     /***
230      * Get an iterator over all preceding siblings.
231      *
232      * @param contextNode the context node for the preceding sibling axis
233      * @return a possibly-empty iterator (not null)
234      */
235     public Iterator getPrecedingSiblingAxisIterator (Object contextNode)
236     {
237         return new NodeIterator ((Node)contextNode) {
238                 protected Node getFirstNode (Node node)
239                 {
240                     return getNextNode(node);
241                 }
242                 protected Node getNextNode (Node node) {
243                     return node.getPreviousSibling();
244                 }
245             };
246     }
247 
248 
249     /***
250      * Get an iterator over all following nodes, depth-first.
251      *
252      * @param contextNode the context node for the following axis
253      * @return a possibly-empty iterator (not null)
254      */
255     public Iterator getFollowingAxisIterator (Object contextNode)
256     {
257         return new NodeIterator ((Node)contextNode) {
258                 protected Node getFirstNode (Node node)
259                 {
260                     if (node == null)
261                         return null;
262                     else {
263                         Node sibling = node.getNextSibling();
264                         if (sibling == null)
265                             return getFirstNode(node.getParentNode());
266                         else
267                             return sibling;
268                     }
269                 }
270                 protected Node getNextNode (Node node) {
271                     if (node == null)
272                         return null;
273                     else {
274                         Node n = node.getFirstChild();
275                         if (n == null)
276                             n = node.getNextSibling();
277                         if (n == null)
278                             return getFirstNode(node.getParentNode());
279                         else
280                             return n;
281                     }
282                 }
283             };
284     }
285 
286 
287     /***
288      * Get an iterator over all preceding nodes, depth-first.
289      *
290      * @param contextNode the context node for the preceding axis
291      * @return a possibly-empty iterator (not null)
292      */
293     public Iterator getPrecedingAxisIterator (Object contextNode)
294     {
295         return new NodeIterator ((Node)contextNode) {
296                 protected Node getFirstNode (Node node)
297                 {
298                     if (node == null)
299                         return null;
300                     Node sibling = node.getPreviousSibling();
301                     if (sibling == null)
302                             return getFirstNode(node.getParentNode());
303                     while (sibling != null) {
304                             node = sibling;
305                             sibling = node.getLastChild();
306                     }
307                     return node;
308                 }
309                 protected Node getNextNode (Node node) {
310                     if (node == null)
311                         return null;
312                     else {
313                         Node n = node.getLastChild();
314                         if (n == null)
315                             n = node.getPreviousSibling();
316                         if (n == null)
317                             return getFirstNode(node.getParentNode());
318                         else
319                             return n;
320                     }
321                 }
322             };
323     }
324 
325     /***
326      * Get an iterator over all attributes.
327      *
328      * @param contextNode the context node for the attribute axis
329      * @return a possibly-empty iterator (not null)
330      */
331     public Iterator getAttributeAxisIterator (Object contextNode)
332     {
333         if (isElement(contextNode)) {
334             return new AttributeIterator((Node)contextNode);
335         } else {
336             return JaxenConstants.EMPTY_ITERATOR;
337         }
338     }
339 
340 
341     /***
342      * Get an iterator over all declared Namespaces.
343      *
344      * <p>Note: this iterator is not live: it takes a snapshot
345      * and that snapshot remains static during the life of
346      * the iterator (i.e. it won't reflect subsequent changes
347      * to the DOM).</p>
348      *
349      * @param contextNode the context node for the namespace axis
350      * @return a possibly-empty iterator (not null)
351      */
352     public Iterator getNamespaceAxisIterator (Object contextNode)
353     {
354         // Only elements have namespace nodes
355         if (isElement(contextNode)) {
356 
357             HashMap nsMap = new HashMap();
358 
359             // Start at the current node at walk
360             // up to the root, noting what namespace
361             // declarations are in force.
362 
363             // TODO: deal with empty URI for
364             // canceling Namespace scope
365             for (Node n = (Node)contextNode;
366                  n != null;
367                  n = n.getParentNode()) {
368                 if (n.hasAttributes()) {
369                     NamedNodeMap atts = n.getAttributes();
370                     int length = atts.getLength();
371                     for (int i = 0; i < length; i++) {
372                         Node att = atts.item(i);
373                         if (att.getNodeName().startsWith("xmlns")) {
374                             NamespaceNode ns =
375                                 new NamespaceNode((Node)contextNode, att);
376                             // Add only if there's not a closer
377                             // declaration in force.
378                             String name = ns.getNodeName();
379                             if (!nsMap.containsKey(name))
380                                 nsMap.put(name, ns);
381                         }
382                     }
383                 }
384             }
385             // Section 5.4 of the XPath rec requires
386             // this to be present.
387             nsMap.put("xml",
388                       new
389                       NamespaceNode((Node)contextNode,
390                                     "xml",
391                                     "http://www.w3.org/XML/1998/namespace"));
392 
393             // An empty default Namespace cancels
394             // any previous default.
395             NamespaceNode defaultNS = (NamespaceNode)nsMap.get("");
396             if (defaultNS != null && defaultNS.getNodeValue().length() == 0)
397                 nsMap.remove("");
398             return nsMap.values().iterator();
399         } else {
400             return JaxenConstants.EMPTY_ITERATOR;
401         }
402     }
403 
404     /*** Returns a parsed form of the given XPath string, which will be suitable
405      *  for queries on DOM documents.
406      */
407     public XPath parseXPath (String xpath) throws org.jaxen.saxpath.SAXPathException
408     {
409         return new DOMXPath(xpath);
410     }
411 
412     /***
413      * Get the top-level document node.
414      *
415      * @param contextNode any node in the document
416      * @return the root node
417      */
418     public Object getDocumentNode (Object contextNode)
419     {
420         if (isDocument(contextNode))
421             return contextNode;
422         else
423             return ((Node)contextNode).getOwnerDocument();
424     }
425 
426 
427     /***
428      * Get the Namespace URI of an element.
429      *
430      * @param object the target node
431      * @return a string (possibly empty) if the node is an element,
432      * and null otherwise
433      */
434     public String getElementNamespaceUri (Object object)
435     {
436         String uri = ((Node)object).getNamespaceURI();
437         return uri;
438     }
439 
440 
441     /***
442      * Get the local name of an element.
443      *
444      * @param object the target node
445      * @return a string representing the unqualified local name
446      * if the node is an element, or null otherwise
447      */
448     public String getElementName (Object object)
449     {
450         String name = ((Node)object).getLocalName();
451         if (name == null) name = ((Node)object).getNodeName();
452         return name;
453     }
454 
455 
456     /***
457      * Get the qualified name of an element.
458      *
459      * @param object the target node
460      * @return a string representing the qualified (i.e. possibly
461      * prefixed) name if the node is an element, or null otherwise
462      */
463     public String getElementQName (Object object)
464     {
465         String qname = ((Node)object).getNodeName();
466         if (qname == null)
467             qname = ((Node)object).getLocalName();
468         return qname;
469     }
470 
471 
472     /***
473      * Get the Namespace URI of an attribute.
474      *
475      * @param object the target node
476      */
477     public String getAttributeNamespaceUri (Object object)
478     {
479         String uri = ((Node)object).getNamespaceURI();
480         return uri;
481     }
482 
483 
484     /***
485      * Get the local name of an attribute.
486      *
487      * @param object the target node
488      * @return a string representing the unqualified local name
489      * if the node is an attribute, or null otherwise
490      */
491     public String getAttributeName (Object object)
492     {
493         String name = ((Node)object).getLocalName();
494         if (name == null)
495             name = ((Node)object).getNodeName();
496         return name;
497     }
498 
499 
500     /***
501      * Get the qualified name of an attribute.
502      *
503      * @param object the target node
504      * @return a string representing the qualified (i.e. possibly
505      * prefixed) name if the node is an attribute, or null otherwise
506      */
507     public String getAttributeQName (Object object)
508     {
509         String qname = ((Node)object).getNodeName();
510         if (qname == null)
511             qname = ((Node)object).getLocalName();
512         return qname;
513     }
514 
515 
516     /***
517      * Test if a node is a top-level document.
518      *
519      * @param object the target node
520      * @return true if the node is the document root, false otherwise
521      */
522     public boolean isDocument (Object object)
523     {
524         return (object instanceof Node) &&
525             (((Node)object).getNodeType() == Node.DOCUMENT_NODE);
526     }
527 
528 
529     /***
530      * Test if a node is a namespace.
531      *
532      * @param object the target node
533      * @return true if the node is a namespace, false otherwise
534      */
535     public boolean isNamespace (Object object)
536     {
537         return (object instanceof NamespaceNode);
538     }
539 
540 
541     /***
542      * Test if a node is an element.
543      *
544      * @param object the target node
545      * @return true if the node is an element, false otherwise
546      */
547     public boolean isElement (Object object)
548     {
549         return (object instanceof Node) &&
550             (((Node)object).getNodeType() == Node.ELEMENT_NODE);
551     }
552 
553 
554     /***
555      * Test if a node is an attribute. <code>xmlns</code> and 
556      * <code>xmlns:pre</code> attributes do not count as attributes
557      * for the purposes of XPath. 
558      *
559      * @param object the target node
560      * @return true if the node is an attribute, false otherwise
561      */
562     public boolean isAttribute (Object object)
563     {
564         return (object instanceof Node) &&
565             (((Node)object).getNodeType() == Node.ATTRIBUTE_NODE)
566             && ! "http://www.w3.org/2000/xmlns/".equals(((Node) object).getNamespaceURI());
567     }
568 
569 
570     /***
571      * Test if a node is a comment.
572      *
573      * @param object the target node
574      * @return true if the node is a comment, false otherwise
575      */
576     public boolean isComment (Object object)
577     {
578         return (object instanceof Node) &&
579             (((Node)object).getNodeType() == Node.COMMENT_NODE);
580     }
581 
582 
583     /***
584      * Test if a node is plain text.
585      *
586      * @param object the target node
587      * @return true if the node is a text node, false otherwise
588      */
589     public boolean isText (Object object)
590     {
591         if (object instanceof Node) {
592             switch (((Node)object).getNodeType()) {
593                 case Node.TEXT_NODE:
594                 case Node.CDATA_SECTION_NODE:
595                     return true;
596                 default:
597                     return false;
598             }
599         } else {
600             return false;
601         }
602     }
603 
604 
605     /***
606      * Test if a node is a processing instruction.
607      *
608      * @param object the target node
609      * @return true if the node is a processing instruction, false otherwise
610      */
611     public boolean isProcessingInstruction (Object object)
612     {
613         return (object instanceof Node) &&
614             (((Node)object).getNodeType() == Node.PROCESSING_INSTRUCTION_NODE);
615     }
616 
617 
618     /***
619      * Get the string value of an element node.
620      *
621      * @param object the target node
622      * @return the text inside the node and its descendants if the node
623      * is an element, null otherwise
624      */
625     public String getElementStringValue (Object object)
626     {
627         if (isElement(object))
628             return getStringValue((Node)object, new StringBuffer()).toString();
629         else
630             return null;
631     }
632 
633 
634     /***
635      * Construct an element's string value recursively.
636      *
637      * @param node the current node
638      * @param buffer the buffer for building the text
639      * @return the buffer passed as a parameter (for convenience)
640      */
641     private StringBuffer getStringValue (Node node, StringBuffer buffer)
642     {
643         if (isText(node)) {
644             buffer.append(node.getNodeValue());
645         } else {
646             NodeList children = node.getChildNodes();
647             int length = children.getLength();
648             for (int i = 0; i < length; i++)
649                 getStringValue(children.item(i), buffer);
650         }
651         return buffer;
652     }
653 
654 
655     /***
656      * Get the string value of an attribute node.
657      *
658      * @param object the target node
659      * @return the text of the attribute value if the node is an
660      * attribute, null otherwise
661      */
662     public String getAttributeStringValue (Object object)
663     {
664         if (isAttribute(object))
665             return ((Node)object).getNodeValue();
666         else
667             return null;
668     }
669 
670 
671     /***
672      * Get the string value of text.
673      *
674      * @param object the target node
675      * @return the string of text if the node is text, null otherwise
676      */
677     public String getTextStringValue (Object object)
678     {
679         if (isText(object))
680             return ((Node)object).getNodeValue();
681         else
682             return null;
683     }
684 
685 
686     /***
687      * Get the string value of a comment node.
688      *
689      * @param object the target node
690      * @return the text of the comment if the node is a comment,
691      * null otherwise
692      */
693     public String getCommentStringValue (Object object)
694     {
695         if (isComment(object))
696             return ((Node)object).getNodeValue();
697         else
698             return null;
699     }
700 
701 
702     /***
703      * Get the string value of a namespace node.
704      *
705      * @param object the target node
706      * @return the namespace URI as a (possibly empty) string if the
707      * node is a namespace node, null otherwise
708      */
709     public String getNamespaceStringValue (Object object)
710     {
711         if (isNamespace(object))
712             return ((NamespaceNode)object).getNodeValue();
713         else
714             return null;
715     }
716 
717     /***
718      * Get the prefix value of a Namespace node.
719      *
720      * @param object the target node
721      * @return the Namespace prefix a (possibly empty) string if the
722      * node is a namespace node, null otherwise
723      */
724     public String getNamespacePrefix (Object object)
725     {
726         if (isNamespace(object))
727             return ((NamespaceNode)object).getLocalName();
728         else
729             return null;
730     }
731 
732     /***
733      * Translate a Namespace prefix to a URI.
734      */
735     public String translateNamespacePrefixToUri (String prefix, Object element)
736     {
737         Iterator it = getNamespaceAxisIterator(element);
738         while (it.hasNext()) {
739             NamespaceNode ns = (NamespaceNode)it.next();
740             if (prefix.equals(ns.getNodeName()))
741                 return ns.getNodeValue();
742         }
743         return null;
744     }
745 
746     /***
747      * Use JAXP to load a namespace aware document from a given URI
748      *
749      * @param uri is the URI of the document to load
750      * @return the new W3C DOM Level 2 Document instance
751      * @throws FunctionCallException containing a nested exception
752      *      if a problem occurs trying to parse the given document
753      */
754     public Object getDocument(String uri) throws FunctionCallException
755     {
756         try
757         {
758             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
759             factory.setNamespaceAware(true);
760             DocumentBuilder builder = factory.newDocumentBuilder();
761             return builder.parse( uri );
762         }
763         catch (Exception e)
764         {
765             throw new FunctionCallException("Failed to parse document for URI: " + uri, e);
766         }
767     }
768 
769     public String getProcessingInstructionTarget(Object obj)
770     {
771         ProcessingInstruction pi = (ProcessingInstruction) obj;
772 
773         return pi.getTarget();
774     }
775 
776     public String getProcessingInstructionData(Object obj)
777     {
778         ProcessingInstruction pi = (ProcessingInstruction) obj;
779 
780         return pi.getData();
781     }
782 
783     
784     ////////////////////////////////////////////////////////////////////
785     // Inner class: iterate over DOM nodes.
786     ////////////////////////////////////////////////////////////////////
787 
788 
789     // FIXME: needs to recurse into
790     // DocumentFragment and EntityReference
791     // to use their children.
792 
793     /***
794      * A generic iterator over DOM nodes.
795      *
796      * <p>Concrete subclasses must implement the {@link #getFirstNode}
797      * and {@link #getNextNode} methods for a specific iteration
798      * strategy.</p>
799      */
800     abstract class NodeIterator
801     implements Iterator
802     {
803 
804 
805         /***
806          * Constructor.
807          *
808          * @param contextNode the starting node.
809          */
810         public NodeIterator (Node contextNode)
811         {
812             node = getFirstNode(contextNode);
813             while (!isXPathNode(node))
814                 node = getNextNode(node);
815         }
816 
817 
818         /***
819          * @see Iterator#hasNext
820          */
821         public boolean hasNext ()
822         {
823             return (node != null);
824         }
825 
826 
827         /***
828          * @see Iterator#next
829          */
830         public Object next ()
831         {
832             if (node == null)
833                 throw new NoSuchElementException();
834             Node ret = node;
835             node = getNextNode(node);
836             while (!isXPathNode(node))
837                 node = getNextNode(node);
838             return ret;
839         }
840 
841 
842         /***
843          * @see Iterator#remove
844          */
845         public void remove ()
846         {
847             throw new UnsupportedOperationException();
848         }
849 
850 
851         /***
852          * Get the first node for iteration.
853          *
854          * <p>This method must derive an initial node for iteration
855          * from a context node.</p>
856          *
857          * @param contextNode the starting node
858          * @return the first node in the iteration
859          * @see #getNextNode
860          */
861         protected abstract Node getFirstNode (Node contextNode);
862 
863 
864         /***
865          * Get the next node for iteration.
866          *
867          * <p>This method must locate a following node from the
868          * current context node.</p>
869          *
870          * @param contextNode the current node in the iteration
871          * @return the following node in the iteration, or null
872          * if there is none
873          * @see #getFirstNode
874          */
875         protected abstract Node getNextNode (Node contextNode);
876 
877 
878         /***
879          * Test whether a DOM node is usable by XPath.
880          *
881          * @param node the DOM node to test
882          * @return true if the node is usable, false if it should be
883          * skipped
884          */
885         private boolean isXPathNode (Node node)
886         {
887             // null is usable, because it means end
888             if (node == null)
889                 return true;
890 
891             switch (node.getNodeType()) {
892                 case Node.DOCUMENT_FRAGMENT_NODE:
893                 case Node.DOCUMENT_TYPE_NODE:
894                 case Node.ENTITY_NODE:
895                 case Node.ENTITY_REFERENCE_NODE:
896                 case Node.NOTATION_NODE:
897                     return false;
898                 default:
899                     return true;
900             }
901         }
902 
903         private Node node;
904     }
905 
906 
907     
908     ////////////////////////////////////////////////////////////////////
909     // Inner class: iterate over a DOM named node map.
910     ////////////////////////////////////////////////////////////////////
911 
912 
913     /***
914      * An iterator over an attribute list.
915      */
916     class AttributeIterator implements Iterator
917     {
918 
919         /***
920          * Constructor.
921          *
922          * @param parent The parent DOM element for the attributes.
923          */
924         AttributeIterator (Node parent)
925         {
926             this.map = parent.getAttributes();
927             this.pos = 0;
928         }
929 
930 
931         /***
932          * @see Iterator#hasNext
933          */
934         public boolean hasNext ()
935         {
936             return pos < map.getLength();
937         }
938 
939 
940         /***
941          * @see Iterator#next
942          */
943         public Object next ()
944         {
945             Node attr = map.item(pos++);
946             if (attr == null)
947                 throw new NoSuchElementException();
948             else
949                 return attr;
950         }
951 
952 
953         /***
954          * @see Iterator#remove
955          */
956         public void remove ()
957         {
958             throw new UnsupportedOperationException();
959         }
960 
961 
962         private NamedNodeMap map;
963         private int pos;
964 
965     }
966 
967     /***
968      *  Returns the element whose ID is given by elementId.
969      *  If no such element exists, returns null.
970      *  Attributes with the name "ID" are not of type ID unless so defined.
971      *  Atribute types are only known if when the parser understands DTD's or
972      *  schemas that declare attributes of type ID. When JAXP is used, you
973      *  must call <code>setValidating(true)</code> on the
974      *  DocumentBuilderFactory.
975      *
976      *  @param object   a node from the document in which to look for the id
977      *  @param elementId   id to look for
978      *
979      *  @return   element whose ID is given by elementId, or null if no such
980      *            element exists in the document or if the implementation
981      *            does not know about attribute types
982      *  @see   javax.xml.parsers.DocumentBuilderFactory
983      */
984     public Object getElementById(Object object, String elementId)
985     {
986         Document doc = (Document)getDocumentNode(object);
987         if (doc != null)
988             return doc.getElementById(elementId);
989         else
990             return null;
991     }
992 
993 }
994 
995 // end of DocumentNavigator.java