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.36 2005/04/12 17:06:11 elharo Exp $
5    * $Revision: 1.36 $
6    * $Date: 2005/04/12 17:06:11 $
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.36 2005/04/12 17:06:11 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 attributes.
289      *
290      * @param contextNode the context node for the attribute axis
291      * @return a possibly-empty iterator (not null)
292      */
293     public Iterator getAttributeAxisIterator (Object contextNode)
294     {
295         if (isElement(contextNode)) {
296             return new AttributeIterator((Node)contextNode);
297         } else {
298             return JaxenConstants.EMPTY_ITERATOR;
299         }
300     }
301 
302 
303     /***
304      * Get an iterator over all declared Namespaces.
305      *
306      * <p>Note: this iterator is not live: it takes a snapshot
307      * and that snapshot remains static during the life of
308      * the iterator (i.e. it won't reflect subsequent changes
309      * to the DOM).</p>
310      *
311      * @param contextNode the context node for the namespace axis
312      * @return a possibly-empty iterator (not null)
313      */
314     public Iterator getNamespaceAxisIterator (Object contextNode)
315     {
316         // Only elements have namespace nodes
317         if (isElement(contextNode)) {
318 
319             HashMap nsMap = new HashMap();
320 
321             // Start at the current node at walk
322             // up to the root, noting what namespace
323             // declarations are in force.
324 
325             // TODO: deal with empty URI for
326             // canceling Namespace scope
327             for (Node n = (Node)contextNode;
328                  n != null;
329                  n = n.getParentNode()) {
330                 if (n.hasAttributes()) {
331                     NamedNodeMap atts = n.getAttributes();
332                     int length = atts.getLength();
333                     for (int i = 0; i < length; i++) {
334                         Node att = atts.item(i);
335                         if (att.getNodeName().startsWith("xmlns")) {
336                             NamespaceNode ns =
337                                 new NamespaceNode((Node)contextNode, att);
338                             // Add only if there's not a closer
339                             // declaration in force.
340                             String name = ns.getNodeName();
341                             if (!nsMap.containsKey(name))
342                                 nsMap.put(name, ns);
343                         }
344                     }
345                 }
346             }
347             // Section 5.4 of the XPath rec requires
348             // this to be present.
349             nsMap.put("xml",
350                       new
351                       NamespaceNode((Node)contextNode,
352                                     "xml",
353                                     "http://www.w3.org/XML/1998/namespace"));
354 
355             // An empty default Namespace cancels
356             // any previous default.
357             NamespaceNode defaultNS = (NamespaceNode)nsMap.get("");
358             if (defaultNS != null && defaultNS.getNodeValue().length() == 0)
359                 nsMap.remove("");
360             return nsMap.values().iterator();
361         } else {
362             return JaxenConstants.EMPTY_ITERATOR;
363         }
364     }
365 
366     /*** Returns a parsed form of the given XPath string, which will be suitable
367      *  for queries on DOM documents.
368      */
369     public XPath parseXPath (String xpath) throws org.jaxen.saxpath.SAXPathException
370     {
371         return new DOMXPath(xpath);
372     }
373 
374     /***
375      * Get the top-level document node.
376      *
377      * @param contextNode any node in the document
378      * @return the root node
379      */
380     public Object getDocumentNode (Object contextNode)
381     {
382         if (isDocument(contextNode))
383             return contextNode;
384         else
385             return ((Node)contextNode).getOwnerDocument();
386     }
387 
388 
389     /***
390      * Get the Namespace URI of an element.
391      *
392      * @param object the target node
393      * @return a string (possibly empty) if the node is an element,
394      * and null otherwise
395      */
396     public String getElementNamespaceUri (Object object)
397     {
398         String uri = ((Node)object).getNamespaceURI();
399         return uri;
400     }
401 
402 
403     /***
404      * Get the local name of an element.
405      *
406      * @param object the target node
407      * @return a string representing the unqualified local name
408      * if the node is an element, or null otherwise
409      */
410     public String getElementName (Object object)
411     {
412         String name = ((Node)object).getLocalName();
413         if (name == null) name = ((Node)object).getNodeName();
414         return name;
415     }
416 
417 
418     /***
419      * Get the qualified name of an element.
420      *
421      * @param object the target node
422      * @return a string representing the qualified (i.e. possibly
423      * prefixed) name if the node is an element, or null otherwise
424      */
425     public String getElementQName (Object object)
426     {
427         String qname = ((Node)object).getNodeName();
428         if (qname == null)
429             qname = ((Node)object).getLocalName();
430         return qname;
431     }
432 
433 
434     /***
435      * Get the Namespace URI of an attribute.
436      *
437      * @param object the target node
438      */
439     public String getAttributeNamespaceUri (Object object)
440     {
441         String uri = ((Node)object).getNamespaceURI();
442         return uri;
443     }
444 
445 
446     /***
447      * Get the local name of an attribute.
448      *
449      * @param object the target node
450      * @return a string representing the unqualified local name
451      * if the node is an attribute, or null otherwise
452      */
453     public String getAttributeName (Object object)
454     {
455         String name = ((Node)object).getLocalName();
456         if (name == null)
457             name = ((Node)object).getNodeName();
458         return name;
459     }
460 
461 
462     /***
463      * Get the qualified name of an attribute.
464      *
465      * @param object the target node
466      * @return a string representing the qualified (i.e. possibly
467      * prefixed) name if the node is an attribute, or null otherwise
468      */
469     public String getAttributeQName (Object object)
470     {
471         String qname = ((Node)object).getNodeName();
472         if (qname == null)
473             qname = ((Node)object).getLocalName();
474         return qname;
475     }
476 
477 
478     /***
479      * Test if a node is a top-level document.
480      *
481      * @param object the target node
482      * @return true if the node is the document root, false otherwise
483      */
484     public boolean isDocument (Object object)
485     {
486         return (object instanceof Node) &&
487             (((Node)object).getNodeType() == Node.DOCUMENT_NODE);
488     }
489 
490 
491     /***
492      * Test if a node is a namespace.
493      *
494      * @param object the target node
495      * @return true if the node is a namespace, false otherwise
496      */
497     public boolean isNamespace (Object object)
498     {
499         return (object instanceof NamespaceNode);
500     }
501 
502 
503     /***
504      * Test if a node is an element.
505      *
506      * @param object the target node
507      * @return true if the node is an element, false otherwise
508      */
509     public boolean isElement (Object object)
510     {
511         return (object instanceof Node) &&
512             (((Node)object).getNodeType() == Node.ELEMENT_NODE);
513     }
514 
515 
516     /***
517      * Test if a node is an attribute. <code>xmlns</code> and 
518      * <code>xmlns:pre</code> attributes do not count as attributes
519      * for the purposes of XPath. 
520      *
521      * @param object the target node
522      * @return true if the node is an attribute, false otherwise
523      */
524     public boolean isAttribute (Object object)
525     {
526         return (object instanceof Node) &&
527             (((Node)object).getNodeType() == Node.ATTRIBUTE_NODE)
528             && ! "http://www.w3.org/2000/xmlns/".equals(((Node) object).getNamespaceURI());
529     }
530 
531 
532     /***
533      * Test if a node is a comment.
534      *
535      * @param object the target node
536      * @return true if the node is a comment, false otherwise
537      */
538     public boolean isComment (Object object)
539     {
540         return (object instanceof Node) &&
541             (((Node)object).getNodeType() == Node.COMMENT_NODE);
542     }
543 
544 
545     /***
546      * Test if a node is plain text.
547      *
548      * @param object the target node
549      * @return true if the node is a text node, false otherwise
550      */
551     public boolean isText (Object object)
552     {
553         if (object instanceof Node) {
554             switch (((Node)object).getNodeType()) {
555                 case Node.TEXT_NODE:
556                 case Node.CDATA_SECTION_NODE:
557                     return true;
558                 default:
559                     return false;
560             }
561         } else {
562             return false;
563         }
564     }
565 
566 
567     /***
568      * Test if a node is a processing instruction.
569      *
570      * @param object the target node
571      * @return true if the node is a processing instruction, false otherwise
572      */
573     public boolean isProcessingInstruction (Object object)
574     {
575         return (object instanceof Node) &&
576             (((Node)object).getNodeType() == Node.PROCESSING_INSTRUCTION_NODE);
577     }
578 
579 
580     /***
581      * Get the string value of an element node.
582      *
583      * @param object the target node
584      * @return the text inside the node and its descendants if the node
585      * is an element, null otherwise
586      */
587     public String getElementStringValue (Object object)
588     {
589         if (isElement(object))
590             return getStringValue((Node)object, new StringBuffer()).toString();
591         else
592             return null;
593     }
594 
595 
596     /***
597      * Construct an element's string value recursively.
598      *
599      * @param node the current node
600      * @param buffer the buffer for building the text
601      * @return the buffer passed as a parameter (for convenience)
602      */
603     private StringBuffer getStringValue (Node node, StringBuffer buffer)
604     {
605         if (isText(node)) {
606             buffer.append(node.getNodeValue());
607         } else {
608             NodeList children = node.getChildNodes();
609             int length = children.getLength();
610             for (int i = 0; i < length; i++)
611                 getStringValue(children.item(i), buffer);
612         }
613         return buffer;
614     }
615 
616 
617     /***
618      * Get the string value of an attribute node.
619      *
620      * @param object the target node
621      * @return the text of the attribute value if the node is an
622      * attribute, null otherwise
623      */
624     public String getAttributeStringValue (Object object)
625     {
626         if (isAttribute(object))
627             return ((Node)object).getNodeValue();
628         else
629             return null;
630     }
631 
632 
633     /***
634      * Get the string value of text.
635      *
636      * @param object the target node
637      * @return the string of text if the node is text, null otherwise
638      */
639     public String getTextStringValue (Object object)
640     {
641         if (isText(object))
642             return ((Node)object).getNodeValue();
643         else
644             return null;
645     }
646 
647 
648     /***
649      * Get the string value of a comment node.
650      *
651      * @param object the target node
652      * @return the text of the comment if the node is a comment,
653      * null otherwise
654      */
655     public String getCommentStringValue (Object object)
656     {
657         if (isComment(object))
658             return ((Node)object).getNodeValue();
659         else
660             return null;
661     }
662 
663 
664     /***
665      * Get the string value of a namespace node.
666      *
667      * @param object the target node
668      * @return the namespace URI as a (possibly empty) string if the
669      * node is a namespace node, null otherwise
670      */
671     public String getNamespaceStringValue (Object object)
672     {
673         if (isNamespace(object))
674             return ((NamespaceNode)object).getNodeValue();
675         else
676             return null;
677     }
678 
679     /***
680      * Get the prefix value of a Namespace node.
681      *
682      * @param object the target node
683      * @return the Namespace prefix a (possibly empty) string if the
684      * node is a namespace node, null otherwise
685      */
686     public String getNamespacePrefix (Object object)
687     {
688         if (isNamespace(object))
689             return ((NamespaceNode)object).getLocalName();
690         else
691             return null;
692     }
693 
694     /***
695      * Translate a Namespace prefix to a URI.
696      */
697     public String translateNamespacePrefixToUri (String prefix, Object element)
698     {
699         Iterator it = getNamespaceAxisIterator(element);
700         while (it.hasNext()) {
701             NamespaceNode ns = (NamespaceNode)it.next();
702             if (prefix.equals(ns.getNodeName()))
703                 return ns.getNodeValue();
704         }
705         return null;
706     }
707 
708     /***
709      * Use JAXP to load a namespace aware document from a given URI
710      *
711      * @param uri is the URI of the document to load
712      * @return the new W3C DOM Level 2 Document instance
713      * @throws FunctionCallException containing a nested exception
714      *      if a problem occurs trying to parse the given document
715      */
716     public Object getDocument(String uri) throws FunctionCallException
717     {
718         try
719         {
720             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
721             factory.setNamespaceAware(true);
722             DocumentBuilder builder = factory.newDocumentBuilder();
723             return builder.parse( uri );
724         }
725         catch (Exception e)
726         {
727             throw new FunctionCallException("Failed to parse document for URI: " + uri, e);
728         }
729     }
730 
731     public String getProcessingInstructionTarget(Object obj)
732     {
733         ProcessingInstruction pi = (ProcessingInstruction) obj;
734 
735         return pi.getTarget();
736     }
737 
738     public String getProcessingInstructionData(Object obj)
739     {
740         ProcessingInstruction pi = (ProcessingInstruction) obj;
741 
742         return pi.getData();
743     }
744 
745     
746     ////////////////////////////////////////////////////////////////////
747     // Inner class: iterate over DOM nodes.
748     ////////////////////////////////////////////////////////////////////
749 
750 
751     // FIXME: needs to recurse into
752     // DocumentFragment and EntityReference
753     // to use their children.
754 
755     /***
756      * A generic iterator over DOM nodes.
757      *
758      * <p>Concrete subclasses must implement the {@link #getFirstNode}
759      * and {@link #getNextNode} methods for a specific iteration
760      * strategy.</p>
761      */
762     abstract class NodeIterator
763     implements Iterator
764     {
765 
766 
767         /***
768          * Constructor.
769          *
770          * @param contextNode the starting node
771          */
772         public NodeIterator (Node contextNode)
773         {
774             node = getFirstNode(contextNode);
775             while (!isXPathNode(node))
776                 node = getNextNode(node);
777         }
778 
779 
780         /***
781          * @see Iterator#hasNext
782          */
783         public boolean hasNext ()
784         {
785             return (node != null);
786         }
787 
788 
789         /***
790          * @see Iterator#next
791          */
792         public Object next ()
793         {
794             if (node == null) throw new NoSuchElementException();
795             Node ret = node;
796             node = getNextNode(node);
797             while (!isXPathNode(node)) {
798                 node = getNextNode(node);
799             }
800             return ret;
801         }
802 
803 
804         /***
805          * @see Iterator#remove
806          */
807         public void remove ()
808         {
809             throw new UnsupportedOperationException();
810         }
811 
812 
813         /***
814          * Get the first node for iteration.
815          *
816          * <p>This method must derive an initial node for iteration
817          * from a context node.</p>
818          *
819          * @param contextNode the starting node
820          * @return the first node in the iteration
821          * @see #getNextNode
822          */
823         protected abstract Node getFirstNode (Node contextNode);
824 
825 
826         /***
827          * Get the next node for iteration.
828          *
829          * <p>This method must locate a following node from the
830          * current context node.</p>
831          *
832          * @param contextNode the current node in the iteration
833          * @return the following node in the iteration, or null
834          * if there is none
835          * @see #getFirstNode
836          */
837         protected abstract Node getNextNode (Node contextNode);
838 
839 
840         /***
841          * Test whether a DOM node is usable by XPath.
842          *
843          * @param node the DOM node to test
844          * @return true if the node is usable, false if it should be
845          * skipped
846          */
847         private boolean isXPathNode (Node node)
848         {
849             // null is usable, because it means end
850             if (node == null)
851                 return true;
852 
853             switch (node.getNodeType()) {
854                 case Node.DOCUMENT_FRAGMENT_NODE:
855                 case Node.DOCUMENT_TYPE_NODE:
856                 case Node.ENTITY_NODE:
857                 case Node.ENTITY_REFERENCE_NODE:
858                 case Node.NOTATION_NODE:
859                     return false;
860                 default:
861                     return true;
862             }
863         }
864 
865         private Node node;
866     }
867 
868 
869     
870     ////////////////////////////////////////////////////////////////////
871     // Inner class: iterate over a DOM named node map.
872     ////////////////////////////////////////////////////////////////////
873 
874 
875     /***
876      * An iterator over an attribute list.
877      */
878     class AttributeIterator implements Iterator
879     {
880 
881         /***
882          * Constructor.
883          *
884          * @param parent The parent DOM element for the attributes.
885          */
886         AttributeIterator (Node parent)
887         {
888             this.map = parent.getAttributes();
889             this.pos = 0;
890         }
891 
892 
893         /***
894          * @see Iterator#hasNext
895          */
896         public boolean hasNext ()
897         {
898             return pos < map.getLength();
899         }
900 
901 
902         /***
903          * @see Iterator#next
904          */
905         public Object next ()
906         {
907             Node attr = map.item(pos++);
908             if (attr == null)
909                 throw new NoSuchElementException();
910             else
911                 return attr;
912         }
913 
914 
915         /***
916          * @see Iterator#remove
917          */
918         public void remove ()
919         {
920             throw new UnsupportedOperationException();
921         }
922 
923 
924         private NamedNodeMap map;
925         private int pos;
926 
927     }
928 
929     /***
930      *  Returns the element whose ID is given by elementId.
931      *  If no such element exists, returns null.
932      *  Attributes with the name "ID" are not of type ID unless so defined.
933      *  Atribute types are only known if when the parser understands DTD's or
934      *  schemas that declare attributes of type ID. When JAXP is used, you
935      *  must call <code>setValidating(true)</code> on the
936      *  DocumentBuilderFactory.
937      *
938      *  @param object   a node from the document in which to look for the id
939      *  @param elementId   id to look for
940      *
941      *  @return   element whose ID is given by elementId, or null if no such
942      *            element exists in the document or if the implementation
943      *            does not know about attribute types
944      *  @see   javax.xml.parsers.DocumentBuilderFactory
945      */
946     public Object getElementById(Object object, String elementId)
947     {
948         Document doc = (Document)getDocumentNode(object);
949         if (doc != null)
950             return doc.getElementById(elementId);
951         else
952             return null;
953     }
954 
955 }
956 
957 // end of DocumentNavigator.java