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