View Javadoc

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