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