Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
DocumentNavigator |
|
| 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 |