Coverage Report - org.jaxen.dom.DocumentNavigator

Classes in this File Line Coverage Branch Coverage Complexity
DocumentNavigator
87% 
96% 
2.4

 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  294
     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  2022
     {
 135  2022
     }
 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  1860
         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  704952
         return new NodeIterator ((Node)contextNode) {
 164  
                 protected Node getFirstNode (Node node)
 165  
                 {
 166  704952
                     return node.getFirstChild();
 167  
                 }
 168  704952
                 protected Node getNextNode (Node node)
 169  
                 {
 170  732678
                     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  144
         Node node = (Node)contextNode;
 185  
 
 186  138
         if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
 187  12
             return new NodeIterator (node) {
 188  
                     protected Node getFirstNode (Node n)
 189  
                     {
 190  
                         // FIXME: assumes castability.
 191  12
                         return ((Attr)n).getOwnerElement();
 192  
                     }
 193  12
                     protected Node getNextNode (Node n) {
 194  12
                         return null;
 195  
                     }
 196  
                 };
 197  
         } else {
 198  126
             return new NodeIterator (node) {
 199  
                     protected Node getFirstNode (Node n)
 200  
                     {
 201  126
                         return n.getParentNode();
 202  
                     }
 203  126
                     protected Node getNextNode (Node n) {
 204  120
                         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  145488
         Node node = (Node) o;
 223  145488
         if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
 224  762
             return ((Attr) node).getOwnerElement();
 225  
         }
 226  144726
         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  14280
         return new NodeIterator ((Node)contextNode) {
 239  
                 protected Node getFirstNode (Node node)
 240  
                 {
 241  14280
                     return getNextNode(node);
 242  
                 }
 243  14280
                 protected Node getNextNode (Node node) {
 244  68016
                     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  72
         return new NodeIterator ((Node)contextNode) {
 259  
                 protected Node getFirstNode (Node node)
 260  
                 {
 261  72
                     return getNextNode(node);
 262  
                 }
 263  72
                 protected Node getNextNode (Node node) {
 264  384
                     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  48
         return new NodeIterator ((Node)contextNode) {
 279  
                 protected Node getFirstNode (Node node)
 280  
                 {
 281  366
                     if (node == null) {
 282  48
                         return null;
 283  
                     }
 284  
                     else {
 285  318
                         Node sibling = node.getNextSibling();
 286  318
                         if (sibling == null) {
 287  102
                             return getFirstNode(node.getParentNode());
 288  
                         }
 289  
                         else {
 290  216
                             return sibling;
 291  
                         }
 292  
                     }
 293  
                 }
 294  48
                 protected Node getNextNode (Node node) {
 295  576
                     if (node == null) {
 296  0
                         return null;
 297  
                     }
 298  
                     else {
 299  576
                         Node n = node.getFirstChild();
 300  576
                         if (n == null) n = node.getNextSibling();
 301  576
                         if (n == null) return getFirstNode(node.getParentNode());
 302  360
                         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  4416
         if (isElement(contextNode)) {
 318  4128
             return new AttributeIterator((Node)contextNode);
 319  
         } 
 320  
         else {
 321  288
             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  912
         if (isElement(contextNode)) {
 341  
 
 342  462
             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  462
             for (Node n = (Node)contextNode;
 348  1974
                  n != null;
 349  1512
                  n = n.getParentNode()) {
 350  
                 
 351  
                 // 1. Look for namespace attributes
 352  1512
                 if (n.hasAttributes()) {
 353  360
                     NamedNodeMap atts = n.getAttributes();
 354  360
                     int length = atts.getLength();
 355  1410
                     for (int i = 0; i < length; i++) {
 356  1050
                         Attr att = (Attr) atts.item(i);
 357  
                         // work around crimson bug by testing URI rather than name
 358  1050
                         String attributeNamespace = att.getNamespaceURI();
 359  1050
                         if ("http://www.w3.org/2000/xmlns/".equals(attributeNamespace)) {
 360  582
                             NamespaceNode ns =
 361  
                                 new NamespaceNode((Node)contextNode, att);
 362  
                             // Add only if there's not a closer
 363  
                             // declaration in force.
 364  582
                             String name = ns.getNodeName();
 365  582
                             if (!nsMap.containsKey(name)) nsMap.put(name, ns);
 366  
                         }
 367  468
                         else if (attributeNamespace != null) {
 368  36
                             String prefix = att.getPrefix();
 369  36
                             NamespaceNode ns =
 370  
                                 new NamespaceNode((Node)contextNode, prefix, attributeNamespace);
 371  
                             // Add only if there's not a closer
 372  
                             // declaration in force.
 373  36
                             if (!nsMap.containsKey(prefix)) nsMap.put(prefix, ns);
 374  
                             
 375  
                         }
 376  
                     }
 377  
                 }
 378  
                 
 379  
                 // 2. Look for the namespace of the element itself
 380  1512
                 String myNamespace = n.getNamespaceURI();
 381  1512
                 if (myNamespace != null && ! "".equals(myNamespace)) {
 382  270
                     String myPrefix = n.getPrefix();
 383  270
                     if (!nsMap.containsKey(myPrefix)) {
 384  270
                         NamespaceNode ns = new NamespaceNode((Node) contextNode, myPrefix, myNamespace);
 385  270
                         nsMap.put(myPrefix, ns);
 386  
                     }
 387  
                 }
 388  
                 
 389  
             }
 390  
             // Section 5.4 of the XPath rec requires
 391  
             // this to be present.
 392  462
             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  462
             NamespaceNode defaultNS = (NamespaceNode)nsMap.get("");
 401  462
             if (defaultNS != null && defaultNS.getNodeValue().length() == 0) {
 402  0
                 nsMap.remove("");
 403  
             }
 404  462
             return nsMap.values().iterator();
 405  
         } 
 406  
         else {
 407  450
             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  42
         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  2136
         if (isDocument(contextNode)) return contextNode;
 432  408
         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  241452
         String uri = ((Node)element).getNamespaceURI();
 447  241452
         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  242844
         String name = ((Node)element).getLocalName();
 461  242844
         if (name == null) name = ((Node)element).getNodeName();
 462  242844
         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  174
         String qname = ((Node)element).getNodeName();
 477  174
         if (qname == null) qname = ((Node)element).getLocalName();
 478  174
         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  3048
         String uri = ((Node)attribute).getNamespaceURI();
 493  3048
         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  3042
         String name = ((Node)attribute).getLocalName();
 507  3042
         if (name == null) name = ((Node)attribute).getNodeName();
 508  3042
         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  6
         String qname = ((Node)attribute).getNodeName();
 523  6
         if (qname == null) qname = ((Node)attribute).getLocalName();
 524  6
         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  10446
         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  27492
         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  726600
         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  34086
         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  7890
         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  484314
         if (object instanceof Node) {
 604  470196
             switch (((Node)object).getNodeType()) {
 605  
                 case Node.TEXT_NODE:
 606  
                 case Node.CDATA_SECTION_NODE:
 607  463068
                     return true;
 608  
                 default:
 609  7128
                     return false;
 610  
             }
 611  
         } else {
 612  14118
             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  7752
         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  864
         if (isElement(object)) {
 640  864
             return getStringValue((Node)object, new StringBuffer()).toString();
 641  
         }
 642  
         else {
 643  0
             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  1854
         if (isText(node)) {
 658  864
             buffer.append(node.getNodeValue());
 659  
         } else {
 660  990
             NodeList children = node.getChildNodes();
 661  990
             int length = children.getLength();
 662  1980
             for (int i = 0; i < length; i++) {
 663  990
                 getStringValue(children.item(i), buffer);
 664  
             }
 665  
         }
 666  1854
         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  1872
         if (isAttribute(object)) return ((Node)object).getNodeValue();
 680  0
         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  66
         if (isText(object)) return ((Node)object).getNodeValue();
 693  0
         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  6
         if (isComment(object)) return ((Node)object).getNodeValue();
 707  0
         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  18
         if (isNamespace(object)) return ((NamespaceNode)object).getNodeValue();
 721  0
         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  762
         if (isNamespace(object)) return ((NamespaceNode)object).getLocalName();
 734  0
         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  0
         Iterator it = getNamespaceAxisIterator(element);
 748  0
         while (it.hasNext()) {
 749  0
             NamespaceNode ns = (NamespaceNode)it.next();
 750  0
             if (prefix.equals(ns.getNodeName())) return ns.getNodeValue();
 751  
         }
 752  0
         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  396
             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 770  396
             factory.setNamespaceAware(true);
 771  396
             DocumentBuilder builder = factory.newDocumentBuilder();
 772  396
             return builder.parse( uri );
 773  
         }
 774  0
         catch (ParserConfigurationException e) {
 775  0
             throw new FunctionCallException("JAXP setup error in document() function: " + e.getMessage(), e);
 776  
         }
 777  0
         catch (SAXException e) {
 778  0
            throw new FunctionCallException("XML error in document() function: " + e.getMessage(), e);
 779  
         }
 780  0
         catch (IOException e) {
 781  0
            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  48
         ProcessingInstruction pi = (ProcessingInstruction) obj;
 799  
 
 800  48
         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  18
         ProcessingInstruction pi = (ProcessingInstruction) obj;
 814  
 
 815  18
         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  719490
         {
 847  719490
             node = getFirstNode(contextNode);
 848  719538
             while (!isXPathNode(node)) {
 849  48
                 node = getNextNode(node);
 850  
             }
 851  719490
         }
 852  
 
 853  
         public boolean hasNext ()
 854  
         {
 855  2179140
             return (node != null);
 856  
         }
 857  
 
 858  
         public Object next ()
 859  
         {
 860  787386
             if (node == null) throw new NoSuchElementException();
 861  787386
             Node ret = node;
 862  787386
             node = getNextNode(node);
 863  787386
             while (!isXPathNode(node)) {
 864  0
                 node = getNextNode(node);
 865  
             }
 866  787386
             return ret;
 867  
         }
 868  
 
 869  
         public void remove ()
 870  
         {
 871  0
             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  1506924
             if (node == null) return true;
 913  
 
 914  800118
             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  48
                     return false;
 921  
                 default:
 922  800070
                     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  4128
         {
 949  4128
             this.map = parent.getAttributes();
 950  4128
             this.pos = 0;
 951  4134
             for (int i = this.map.getLength()-1; i >= 0; i--) {
 952  2400
                 Node node = map.item(i);
 953  2400
                 if (! "http://www.w3.org/2000/xmlns/".equals(node.getNamespaceURI())) {
 954  2394
                     this.lastAttribute  = i;
 955  2394
                     break;
 956  
                 }
 957  
             }
 958  4128
         }
 959  
 
 960  
         public boolean hasNext ()
 961  
         {
 962  8922
             return pos <= lastAttribute;
 963  
         }
 964  
 
 965  
         public Object next ()
 966  
         {
 967  3036
             Node attr = map.item(pos++);
 968  3036
             if (attr == null) throw new NoSuchElementException();
 969  3036
             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  0
               return next();
 973  
             }
 974  3036
             else return attr;
 975  
         }
 976  
 
 977  
         public void remove ()
 978  
         {
 979  0
             throw new UnsupportedOperationException();
 980  
         }
 981  
 
 982  
 
 983  
         private NamedNodeMap map;
 984  
         private int pos;
 985  4128
         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  42
         Document doc = (Document)getDocumentNode(object);
 1009  42
         if (doc != null) return doc.getElementById(elementId);
 1010  0
         else return null;
 1011  
     }
 1012  
 
 1013  
 }
 1014  
 
 1015  
 // end of DocumentNavigator.java