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