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