##### Coverage Report - org.jaxen.dom.DocumentNavigator

Classes in this File | Line Coverage | Branch Coverage | Complexity
DocumentNavigator |
87% |
96% |
| 2.4;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