View Javadoc

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