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