1 package org.jaxen.dom;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 import javax.xml.parsers.DocumentBuilder;
65 import javax.xml.parsers.DocumentBuilderFactory;
66 import java.util.HashMap;
67 import java.util.Iterator;
68 import java.util.NoSuchElementException;
69
70 import org.jaxen.DefaultNavigator;
71 import org.jaxen.FunctionCallException;
72 import org.jaxen.Navigator;
73 import org.jaxen.XPath;
74 import org.jaxen.JaxenConstants;
75 import org.w3c.dom.Attr;
76 import org.w3c.dom.Document;
77 import org.w3c.dom.NamedNodeMap;
78 import org.w3c.dom.Node;
79 import org.w3c.dom.NodeList;
80 import org.w3c.dom.ProcessingInstruction;
81
82 /*** Interface for navigating around the W3C DOM Level 2 object model.
83 *
84 * <p>
85 * This class is not intended for direct usage, but is
86 * used by the Jaxen engine during evaluation.
87 * </p>
88 *
89 * <p>This class implements the org.jaxen.DefaultNavigator interface
90 * for the Jaxen XPath library, version 1.0beta3 (it is not guaranteed
91 * to work with subsequent releases). This adapter allows the Jaxen
92 * library to be used to execute XPath queries against any object tree
93 * that implements the DOM level 2 interfaces.</p>
94 *
95 * <p>Note: DOM level 2 does not include a node representing an XML
96 * Namespace declaration. This navigator will return Namespace decls
97 * as instantiations of the custom {@link NamespaceNode} class, and
98 * users will have to check result sets to locate and isolate
99 * these.</p>
100 *
101 * @author David Megginson
102 * @author James Strachan
103 *
104 * @see XPath
105 * @see NamespaceNode
106 */
107 public class DocumentNavigator extends DefaultNavigator
108 {
109
110
111
112
113
114
115 /***
116 * Constant: singleton navigator.
117 */
118 private final static DocumentNavigator SINGLETON = new DocumentNavigator();
119
120
121
122
123
124
125
126
127 /***
128 * Default constructor.
129 */
130 public DocumentNavigator ()
131 {
132 }
133
134
135 /***
136 * Get a singleton DocumentNavigator for efficiency.
137 *
138 * @return a singleton instance of a DocumentNavigator.
139 */
140 public static Navigator getInstance ()
141 {
142 return SINGLETON;
143 }
144
145
146
147
148
149
150
151
152 /***
153 * Get an iterator over all of this node's children.
154 *
155 * @param contextNode the context node for the child axis.
156 * @return a possibly-empty iterator (not null)
157 */
158 public Iterator getChildAxisIterator (Object contextNode)
159 {
160 return new NodeIterator ((Node)contextNode) {
161 protected Node getFirstNode (Node node)
162 {
163 return node.getFirstChild();
164 }
165 protected Node getNextNode (Node node)
166 {
167 return node.getNextSibling();
168 }
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 Node node = (Node)contextNode;
182
183 if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
184 return new NodeIterator (node) {
185 protected Node getFirstNode (Node n)
186 {
187
188 return ((Attr)n).getOwnerElement();
189 }
190 protected Node getNextNode (Node n) {
191 return null;
192 }
193 };
194 } else {
195 return new NodeIterator (node) {
196 protected Node getFirstNode (Node n)
197 {
198 return n.getParentNode();
199 }
200 protected Node getNextNode (Node n) {
201 return null;
202 }
203 };
204 }
205 }
206
207
208 /***
209 * Get an iterator over all following siblings.
210 *
211 * @param contextNode the context node for the sibling iterator
212 * @return a possibly-empty iterator (not null)
213 */
214 public Iterator getFollowingSiblingAxisIterator (Object contextNode)
215 {
216 return new NodeIterator ((Node)contextNode) {
217 protected Node getFirstNode (Node node)
218 {
219 return getNextNode(node);
220 }
221 protected Node getNextNode (Node node) {
222 return node.getNextSibling();
223 }
224 };
225 }
226
227
228 /***
229 * Get an iterator over all preceding siblings.
230 *
231 * @param contextNode the context node for the preceding sibling axis
232 * @return a possibly-empty iterator (not null)
233 */
234 public Iterator getPrecedingSiblingAxisIterator (Object contextNode)
235 {
236 return new NodeIterator ((Node)contextNode) {
237 protected Node getFirstNode (Node node)
238 {
239 return getNextNode(node);
240 }
241 protected Node getNextNode (Node node) {
242 return node.getPreviousSibling();
243 }
244 };
245 }
246
247
248 /***
249 * Get an iterator over all following nodes, depth-first.
250 *
251 * @param contextNode the context node for the following axis
252 * @return a possibly-empty iterator (not null)
253 */
254 public Iterator getFollowingAxisIterator (Object contextNode)
255 {
256 return new NodeIterator ((Node)contextNode) {
257 protected Node getFirstNode (Node node)
258 {
259 if (node == null)
260 return null;
261 else {
262 Node sibling = node.getNextSibling();
263 if (sibling == null)
264 return getFirstNode(node.getParentNode());
265 else
266 return sibling;
267 }
268 }
269 protected Node getNextNode (Node node) {
270 if (node == null)
271 return null;
272 else {
273 Node n = node.getFirstChild();
274 if (n == null)
275 n = node.getNextSibling();
276 if (n == null)
277 return getFirstNode(node.getParentNode());
278 else
279 return n;
280 }
281 }
282 };
283 }
284
285
286 /***
287 * Get an iterator over all attributes.
288 *
289 * @param contextNode the context node for the attribute axis
290 * @return a possibly-empty iterator (not null)
291 */
292 public Iterator getAttributeAxisIterator (Object contextNode)
293 {
294 if (isElement(contextNode)) {
295 return new AttributeIterator((Node)contextNode);
296 } else {
297 return JaxenConstants.EMPTY_ITERATOR;
298 }
299 }
300
301
302 /***
303 * Get an iterator over all declared Namespaces.
304 *
305 * <p>Note: this iterator is not live: it takes a snapshot
306 * and that snapshot remains static during the life of
307 * the iterator (i.e. it won't reflect subsequent changes
308 * to the DOM).</p>
309 *
310 * @param contextNode the context node for the namespace axis
311 * @return a possibly-empty iterator (not null)
312 */
313 public Iterator getNamespaceAxisIterator (Object contextNode)
314 {
315
316 if (isElement(contextNode)) {
317
318 HashMap nsMap = new HashMap();
319
320
321
322
323
324
325
326 for (Node n = (Node)contextNode;
327 n != null;
328 n = n.getParentNode()) {
329 if (n.hasAttributes()) {
330 NamedNodeMap atts = n.getAttributes();
331 int length = atts.getLength();
332 for (int i = 0; i < length; i++) {
333 Node att = atts.item(i);
334 if (att.getNodeName().startsWith("xmlns")) {
335 NamespaceNode ns =
336 new NamespaceNode((Node)contextNode, att);
337
338
339 String name = ns.getNodeName();
340 if (!nsMap.containsKey(name))
341 nsMap.put(name, ns);
342 }
343 }
344 }
345 }
346
347
348 nsMap.put("xml",
349 new
350 NamespaceNode((Node)contextNode,
351 "xml",
352 "http://www.w3.org/XML/1998/namespace"));
353
354
355
356 NamespaceNode defaultNS = (NamespaceNode)nsMap.get("");
357 if (defaultNS != null && defaultNS.getNodeValue().length() == 0)
358 nsMap.remove("");
359 return nsMap.values().iterator();
360 } else {
361 return JaxenConstants.EMPTY_ITERATOR;
362 }
363 }
364
365 /*** Returns a parsed form of the given XPath string, which will be suitable
366 * for queries on DOM documents.
367 */
368 public XPath parseXPath (String xpath) throws org.jaxen.saxpath.SAXPathException
369 {
370 return new DOMXPath(xpath);
371 }
372
373 /***
374 * Get the top-level document node.
375 *
376 * @param contextNode any node in the document
377 * @return the root node
378 */
379 public Object getDocumentNode (Object contextNode)
380 {
381 if (isDocument(contextNode)) return contextNode;
382 else return ((Node)contextNode).getOwnerDocument();
383 }
384
385
386 /***
387 * Get the Namespace URI of an element.
388 *
389 * @param object the target node
390 * @return a string (possibly empty) if the node is an element,
391 * and null otherwise
392 */
393 public String getElementNamespaceUri (Object object)
394 {
395 String uri = ((Node)object).getNamespaceURI();
396 return uri;
397 }
398
399
400 /***
401 * Get the local name of an element.
402 *
403 * @param object the target node
404 * @return a string representing the unqualified local name
405 * if the node is an element, or null otherwise
406 */
407 public String getElementName (Object object)
408 {
409 String name = ((Node)object).getLocalName();
410 if (name == null) name = ((Node)object).getNodeName();
411 return name;
412 }
413
414
415 /***
416 * Get the qualified name of an element.
417 *
418 * @param object the target node
419 * @return a string representing the qualified (i.e. possibly
420 * prefixed) name if the node is an element, or null otherwise
421 */
422 public String getElementQName (Object object)
423 {
424 String qname = ((Node)object).getNodeName();
425 if (qname == null) qname = ((Node)object).getLocalName();
426 return qname;
427 }
428
429
430 /***
431 * Get the Namespace URI of an attribute.
432 *
433 * @param object the target node
434 */
435 public String getAttributeNamespaceUri (Object object)
436 {
437 String uri = ((Node)object).getNamespaceURI();
438 return uri;
439 }
440
441
442 /***
443 * Get the local name of an attribute.
444 *
445 * @param object the target node
446 * @return a string representing the unqualified local name
447 * if the node is an attribute, or null otherwise
448 */
449 public String getAttributeName (Object object)
450 {
451 String name = ((Node)object).getLocalName();
452 if (name == null) name = ((Node)object).getNodeName();
453 return name;
454 }
455
456
457 /***
458 * Get the qualified name of an attribute.
459 *
460 * @param object the target node
461 * @return a string representing the qualified (i.e. possibly
462 * prefixed) name if the node is an attribute, or null otherwise
463 */
464 public String getAttributeQName (Object object)
465 {
466 String qname = ((Node)object).getNodeName();
467 if (qname == null) qname = ((Node)object).getLocalName();
468 return qname;
469 }
470
471
472 /***
473 * Test if a node is a top-level document.
474 *
475 * @param object the target node
476 * @return true if the node is the document root, false otherwise
477 */
478 public boolean isDocument (Object object)
479 {
480 return (object instanceof Node) &&
481 (((Node)object).getNodeType() == Node.DOCUMENT_NODE);
482 }
483
484
485 /***
486 * Test if a node is a namespace.
487 *
488 * @param object the target node
489 * @return true if the node is a namespace, false otherwise
490 */
491 public boolean isNamespace (Object object)
492 {
493 return (object instanceof NamespaceNode);
494 }
495
496
497 /***
498 * Test if a node is an element.
499 *
500 * @param object the target node
501 * @return true if the node is an element, false otherwise
502 */
503 public boolean isElement (Object object)
504 {
505 return (object instanceof Node) &&
506 (((Node)object).getNodeType() == Node.ELEMENT_NODE);
507 }
508
509
510 /***
511 * Test if a node is an attribute. <code>xmlns</code> and
512 * <code>xmlns:pre</code> attributes do not count as attributes
513 * for the purposes of XPath.
514 *
515 * @param object the target node
516 * @return true if the node is an attribute, false otherwise
517 */
518 public boolean isAttribute (Object object)
519 {
520 return (object instanceof Node) &&
521 (((Node)object).getNodeType() == Node.ATTRIBUTE_NODE)
522 && ! "http://www.w3.org/2000/xmlns/".equals(((Node) object).getNamespaceURI());
523 }
524
525
526 /***
527 * Test if a node is a comment.
528 *
529 * @param object the target node
530 * @return true if the node is a comment, false otherwise
531 */
532 public boolean isComment (Object object)
533 {
534 return (object instanceof Node) &&
535 (((Node)object).getNodeType() == Node.COMMENT_NODE);
536 }
537
538
539 /***
540 * Test if a node is plain text.
541 *
542 * @param object the target node
543 * @return true if the node is a text node, false otherwise
544 */
545 public boolean isText (Object object)
546 {
547 if (object instanceof Node) {
548 switch (((Node)object).getNodeType()) {
549 case Node.TEXT_NODE:
550 case Node.CDATA_SECTION_NODE:
551 return true;
552 default:
553 return false;
554 }
555 } else {
556 return false;
557 }
558 }
559
560
561 /***
562 * Test if a node is a processing instruction.
563 *
564 * @param object the target node
565 * @return true if the node is a processing instruction, false otherwise
566 */
567 public boolean isProcessingInstruction (Object object)
568 {
569 return (object instanceof Node) &&
570 (((Node)object).getNodeType() == Node.PROCESSING_INSTRUCTION_NODE);
571 }
572
573
574 /***
575 * Get the string value of an element node.
576 *
577 * @param object the target node
578 * @return the text inside the node and its descendants if the node
579 * is an element, null otherwise
580 */
581 public String getElementStringValue (Object object)
582 {
583 if (isElement(object)) {
584 return getStringValue((Node)object, new StringBuffer()).toString();
585 }
586 else {
587 return null;
588 }
589 }
590
591
592 /***
593 * Construct an element's string value recursively.
594 *
595 * @param node the current node
596 * @param buffer the buffer for building the text
597 * @return the buffer passed as a parameter (for convenience)
598 */
599 private StringBuffer getStringValue (Node node, StringBuffer buffer)
600 {
601 if (isText(node)) {
602 buffer.append(node.getNodeValue());
603 } else {
604 NodeList children = node.getChildNodes();
605 int length = children.getLength();
606 for (int i = 0; i < length; i++) {
607 getStringValue(children.item(i), buffer);
608 }
609 }
610 return buffer;
611 }
612
613
614 /***
615 * Get the string value of an attribute node.
616 *
617 * @param object the target node
618 * @return the text of the attribute value if the node is an
619 * attribute, null otherwise
620 */
621 public String getAttributeStringValue (Object object)
622 {
623 if (isAttribute(object)) return ((Node)object).getNodeValue();
624 else return null;
625 }
626
627
628 /***
629 * Get the string value of text.
630 *
631 * @param object the target node
632 * @return the string of text if the node is text, null otherwise
633 */
634 public String getTextStringValue (Object object)
635 {
636 if (isText(object)) return ((Node)object).getNodeValue();
637 else return null;
638 }
639
640
641 /***
642 * Get the string value of a comment node.
643 *
644 * @param object the target node
645 * @return the text of the comment if the node is a comment,
646 * null otherwise
647 */
648 public String getCommentStringValue (Object object)
649 {
650 if (isComment(object)) return ((Node)object).getNodeValue();
651 else return null;
652 }
653
654
655 /***
656 * Get the string value of a namespace node.
657 *
658 * @param object the target node
659 * @return the namespace URI as a (possibly empty) string if the
660 * node is a namespace node, null otherwise
661 */
662 public String getNamespaceStringValue (Object object)
663 {
664 if (isNamespace(object)) return ((NamespaceNode)object).getNodeValue();
665 else return null;
666 }
667
668 /***
669 * Get the prefix value of a Namespace node.
670 *
671 * @param object the target node
672 * @return the Namespace prefix a (possibly empty) string if the
673 * node is a namespace node, null otherwise
674 */
675 public String getNamespacePrefix (Object object)
676 {
677 if (isNamespace(object)) return ((NamespaceNode)object).getLocalName();
678 else return null;
679 }
680
681 /***
682 * Translate a Namespace prefix to a URI.
683 */
684 public String translateNamespacePrefixToUri (String prefix, Object element)
685 {
686 Iterator it = getNamespaceAxisIterator(element);
687 while (it.hasNext()) {
688 NamespaceNode ns = (NamespaceNode)it.next();
689 if (prefix.equals(ns.getNodeName())) return ns.getNodeValue();
690 }
691 return null;
692 }
693
694 /***
695 * Use JAXP to load a namespace aware document from a given URI
696 *
697 * @param uri is the URI of the document to load
698 * @return the new W3C DOM Level 2 Document instance
699 * @throws FunctionCallException containing a nested exception
700 * if a problem occurs trying to parse the given document
701 */
702 public Object getDocument(String uri) throws FunctionCallException
703 {
704 try
705 {
706 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
707 factory.setNamespaceAware(true);
708 DocumentBuilder builder = factory.newDocumentBuilder();
709 return builder.parse( uri );
710 }
711 catch (Exception e)
712 {
713 throw new FunctionCallException("Failed to parse document for URI: " + uri, e);
714 }
715 }
716
717 public String getProcessingInstructionTarget(Object obj)
718 {
719 ProcessingInstruction pi = (ProcessingInstruction) obj;
720
721 return pi.getTarget();
722 }
723
724 public String getProcessingInstructionData(Object obj)
725 {
726 ProcessingInstruction pi = (ProcessingInstruction) obj;
727
728 return pi.getData();
729 }
730
731
732
733
734
735
736
737
738
739
740
741 /***
742 * A generic iterator over DOM nodes.
743 *
744 * <p>Concrete subclasses must implement the {@link #getFirstNode}
745 * and {@link #getNextNode} methods for a specific iteration
746 * strategy.</p>
747 */
748 abstract class NodeIterator
749 implements Iterator
750 {
751
752
753 /***
754 * Constructor.
755 *
756 * @param contextNode the starting node
757 */
758 public NodeIterator (Node contextNode)
759 {
760 node = getFirstNode(contextNode);
761 while (!isXPathNode(node))
762 node = getNextNode(node);
763 }
764
765
766 /***
767 * @see Iterator#hasNext
768 */
769 public boolean hasNext ()
770 {
771 return (node != null);
772 }
773
774
775 /***
776 * @see Iterator#next
777 */
778 public Object next ()
779 {
780 if (node == null) throw new NoSuchElementException();
781 Node ret = node;
782 node = getNextNode(node);
783 while (!isXPathNode(node)) {
784 node = getNextNode(node);
785 }
786 return ret;
787 }
788
789
790 /***
791 * @see Iterator#remove
792 */
793 public void remove ()
794 {
795 throw new UnsupportedOperationException();
796 }
797
798
799 /***
800 * Get the first node for iteration.
801 *
802 * <p>This method must derive an initial node for iteration
803 * from a context node.</p>
804 *
805 * @param contextNode the starting node
806 * @return the first node in the iteration
807 * @see #getNextNode
808 */
809 protected abstract Node getFirstNode (Node contextNode);
810
811
812 /***
813 * Get the next node for iteration.
814 *
815 * <p>This method must locate a following node from the
816 * current context node.</p>
817 *
818 * @param contextNode the current node in the iteration
819 * @return the following node in the iteration, or null
820 * if there is none
821 * @see #getFirstNode
822 */
823 protected abstract Node getNextNode (Node contextNode);
824
825
826 /***
827 * Test whether a DOM node is usable by XPath.
828 *
829 * @param node the DOM node to test
830 * @return true if the node is usable, false if it should be
831 * skipped
832 */
833 private boolean isXPathNode (Node node)
834 {
835
836 if (node == null)
837 return true;
838
839 switch (node.getNodeType()) {
840 case Node.DOCUMENT_FRAGMENT_NODE:
841 case Node.DOCUMENT_TYPE_NODE:
842 case Node.ENTITY_NODE:
843 case Node.ENTITY_REFERENCE_NODE:
844 case Node.NOTATION_NODE:
845 return false;
846 default:
847 return true;
848 }
849 }
850
851 private Node node;
852 }
853
854
855
856
857
858
859
860
861 /***
862 * An iterator over an attribute list.
863 */
864 private static class AttributeIterator implements Iterator
865 {
866
867 /***
868 * Constructor.
869 *
870 * @param parent the parent DOM element for the attributes.
871 */
872 AttributeIterator (Node parent)
873 {
874 this.map = parent.getAttributes();
875 this.pos = 0;
876 }
877
878
879 /***
880 * @see Iterator#hasNext
881 */
882 public boolean hasNext ()
883 {
884 return pos < map.getLength();
885 }
886
887
888 /***
889 * @see Iterator#next
890 */
891 public Object next ()
892 {
893 Node attr = map.item(pos++);
894 if (attr == null) throw new NoSuchElementException();
895 else return attr;
896 }
897
898
899 /***
900 * @see Iterator#remove
901 */
902 public void remove ()
903 {
904 throw new UnsupportedOperationException();
905 }
906
907
908 private NamedNodeMap map;
909 private int pos;
910
911 }
912
913 /***
914 * Returns the element whose ID is given by elementId.
915 * If no such element exists, returns null.
916 * Attributes with the name "ID" are not of type ID unless so defined.
917 * Attribute types are only known if when the parser understands DTD's or
918 * schemas that declare attributes of type ID. When JAXP is used, you
919 * must call <code>setValidating(true)</code> on the
920 * DocumentBuilderFactory.
921 *
922 * @param object a node from the document in which to look for the id
923 * @param elementId id to look for
924 *
925 * @return element whose ID is given by elementId, or null if no such
926 * element exists in the document or if the implementation
927 * does not know about attribute types
928 * @see javax.xml.parsers.DocumentBuilderFactory
929 */
930 public Object getElementById(Object object, String elementId)
931 {
932 Document doc = (Document)getDocumentNode(object);
933 if (doc != null) return doc.getElementById(elementId);
934 else return null;
935 }
936
937 }
938
939