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