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 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
116
117
118 /***
119 * Constant: navigator.
120 */
121 private final static DocumentNavigator SINGLETON = new DocumentNavigator();
122
123
124
125
126
127
128
129
130 /***
131 * Default constructor.
132 */
133 public DocumentNavigator ()
134 {
135 }
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 return SINGLETON;
146 }
147
148
149
150
151
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 return new NodeIterator ((Node)contextNode) {
164 protected Node getFirstNode (Node node)
165 {
166 return node.getFirstChild();
167 }
168 protected Node getNextNode (Node node)
169 {
170 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 Node node = (Node)contextNode;
185
186 if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
187 return new NodeIterator (node) {
188 protected Node getFirstNode (Node n)
189 {
190
191 return ((Attr)n).getOwnerElement();
192 }
193 protected Node getNextNode (Node n) {
194 return null;
195 }
196 };
197 } else {
198 return new NodeIterator (node) {
199 protected Node getFirstNode (Node n)
200 {
201 return n.getParentNode();
202 }
203 protected Node getNextNode (Node n) {
204 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 Node node = (Node) o;
223 if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
224 return ((Attr) node).getOwnerElement();
225 }
226 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 return new NodeIterator ((Node)contextNode) {
239 protected Node getFirstNode (Node node)
240 {
241 return getNextNode(node);
242 }
243 protected Node getNextNode (Node node) {
244 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 return new NodeIterator ((Node)contextNode) {
259 protected Node getFirstNode (Node node)
260 {
261 return getNextNode(node);
262 }
263 protected Node getNextNode (Node node) {
264 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 return new NodeIterator ((Node)contextNode) {
279 protected Node getFirstNode (Node node)
280 {
281 if (node == null) {
282 return null;
283 }
284 else {
285 Node sibling = node.getNextSibling();
286 if (sibling == null) {
287 return getFirstNode(node.getParentNode());
288 }
289 else {
290 return sibling;
291 }
292 }
293 }
294 protected Node getNextNode (Node node) {
295 if (node == null) {
296 return null;
297 }
298 else {
299 Node n = node.getFirstChild();
300 if (n == null) n = node.getNextSibling();
301 if (n == null) return getFirstNode(node.getParentNode());
302 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 if (isElement(contextNode)) {
318 return new AttributeIterator((Node)contextNode);
319 }
320 else {
321 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
340 if (isElement(contextNode)) {
341
342 HashMap nsMap = new HashMap();
343
344
345
346
347 for (Node n = (Node)contextNode;
348 n != null;
349 n = n.getParentNode()) {
350
351
352 if (n.hasAttributes()) {
353 NamedNodeMap atts = n.getAttributes();
354 int length = atts.getLength();
355 for (int i = 0; i < length; i++) {
356 Attr att = (Attr) atts.item(i);
357
358 String attributeNamespace = att.getNamespaceURI();
359 if ("http://www.w3.org/2000/xmlns/".equals(attributeNamespace)) {
360 NamespaceNode ns =
361 new NamespaceNode((Node)contextNode, att);
362
363
364 String name = ns.getNodeName();
365 if (!nsMap.containsKey(name)) nsMap.put(name, ns);
366 }
367 else if (attributeNamespace != null) {
368 String prefix = att.getPrefix();
369 NamespaceNode ns =
370 new NamespaceNode((Node)contextNode, prefix, attributeNamespace);
371
372
373 if (!nsMap.containsKey(prefix)) nsMap.put(prefix, ns);
374
375 }
376 }
377 }
378
379
380 String myNamespace = n.getNamespaceURI();
381 if (myNamespace != null && ! "".equals(myNamespace)) {
382 String myPrefix = n.getPrefix();
383 if (!nsMap.containsKey(myPrefix)) {
384 NamespaceNode ns = new NamespaceNode((Node) contextNode, myPrefix, myNamespace);
385 nsMap.put(myPrefix, ns);
386 }
387 }
388
389 }
390
391
392 nsMap.put("xml",
393 new
394 NamespaceNode((Node)contextNode,
395 "xml",
396 "http://www.w3.org/XML/1998/namespace"));
397
398
399
400 NamespaceNode defaultNS = (NamespaceNode)nsMap.get("");
401 if (defaultNS != null && defaultNS.getNodeValue().length() == 0) {
402 nsMap.remove("");
403 }
404 return nsMap.values().iterator();
405 }
406 else {
407 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 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 if (isDocument(contextNode)) return contextNode;
432 else return ((Node)contextNode).getOwnerDocument();
433 }
434
435
436
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 String uri = ((Node)element).getNamespaceURI();
447 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 String name = ((Node)element).getLocalName();
461 if (name == null) name = ((Node)element).getNodeName();
462 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
476 String qname = ((Node)element).getNodeName();
477 if (qname == null) qname = ((Node)element).getLocalName();
478 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 String uri = ((Node)attribute).getNamespaceURI();
493 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 String name = ((Node)attribute).getLocalName();
507 if (name == null) name = ((Node)attribute).getNodeName();
508 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
522 String qname = ((Node)attribute).getNodeName();
523 if (qname == null) qname = ((Node)attribute).getLocalName();
524 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 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 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 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 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 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 if (object instanceof Node) {
604 switch (((Node)object).getNodeType()) {
605 case Node.TEXT_NODE:
606 case Node.CDATA_SECTION_NODE:
607 return true;
608 default:
609 return false;
610 }
611 } else {
612 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 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 if (isElement(object)) {
640 return getStringValue((Node)object, new StringBuffer()).toString();
641 }
642 else {
643 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 if (isText(node)) {
658 buffer.append(node.getNodeValue());
659 } else {
660 NodeList children = node.getChildNodes();
661 int length = children.getLength();
662 for (int i = 0; i < length; i++) {
663 getStringValue(children.item(i), buffer);
664 }
665 }
666 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 if (isAttribute(object)) return ((Node)object).getNodeValue();
680 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 if (isText(object)) return ((Node)object).getNodeValue();
693 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 if (isComment(object)) return ((Node)object).getNodeValue();
707 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 if (isNamespace(object)) return ((NamespaceNode)object).getNodeValue();
721 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 if (isNamespace(object)) return ((NamespaceNode)object).getLocalName();
734 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 Iterator it = getNamespaceAxisIterator(element);
748 while (it.hasNext()) {
749 NamespaceNode ns = (NamespaceNode)it.next();
750 if (prefix.equals(ns.getNodeName())) return ns.getNodeValue();
751 }
752 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
768
769 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
770 factory.setNamespaceAware(true);
771 DocumentBuilder builder = factory.newDocumentBuilder();
772 return builder.parse( uri );
773 }
774 catch (ParserConfigurationException e) {
775 throw new FunctionCallException("JAXP setup error in document() function: " + e.getMessage(), e);
776 }
777 catch (SAXException e) {
778 throw new FunctionCallException("XML error in document() function: " + e.getMessage(), e);
779 }
780 catch (IOException e) {
781 throw new FunctionCallException("I/O error in document() function: " + e.getMessage(), e);
782 }
783
784 }
785
786
787
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 ProcessingInstruction pi = (ProcessingInstruction) obj;
799
800 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 ProcessingInstruction pi = (ProcessingInstruction) obj;
814
815 return pi.getData();
816 }
817
818
819
820
821
822
823
824
825
826
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 {
847 node = getFirstNode(contextNode);
848 while (!isXPathNode(node)) {
849 node = getNextNode(node);
850 }
851 }
852
853 public boolean hasNext ()
854 {
855 return (node != null);
856 }
857
858 public Object next ()
859 {
860 if (node == null) throw new NoSuchElementException();
861 Node ret = node;
862 node = getNextNode(node);
863 while (!isXPathNode(node)) {
864 node = getNextNode(node);
865 }
866 return ret;
867 }
868
869 public void remove ()
870 {
871 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
912 if (node == null) return true;
913
914 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 return false;
921 default:
922 return true;
923 }
924 }
925
926 private Node node;
927 }
928
929
930
931
932
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 {
949 this.map = parent.getAttributes();
950 this.pos = 0;
951 for (int i = this.map.getLength()-1; i >= 0; i--) {
952 Node node = map.item(i);
953 if (! "http://www.w3.org/2000/xmlns/".equals(node.getNamespaceURI())) {
954 this.lastAttribute = i;
955 break;
956 }
957 }
958 }
959
960 public boolean hasNext ()
961 {
962 return pos <= lastAttribute;
963 }
964
965 public Object next ()
966 {
967 Node attr = map.item(pos++);
968 if (attr == null) throw new NoSuchElementException();
969 else if ("http://www.w3.org/2000/xmlns/".equals(attr.getNamespaceURI())) {
970
971
972 return next();
973 }
974 else return attr;
975 }
976
977 public void remove ()
978 {
979 throw new UnsupportedOperationException();
980 }
981
982
983 private NamedNodeMap map;
984 private int pos;
985 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 Document doc = (Document)getDocumentNode(object);
1009 if (doc != null) return doc.getElementById(elementId);
1010 else return null;
1011 }
1012
1013 }
1014
1015