View Javadoc

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