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