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