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