1
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 package org.jaxen.dom;
53
54 import java.lang.reflect.InvocationTargetException;
55 import java.lang.reflect.Method;
56 import java.util.HashMap;
57
58 import org.jaxen.pattern.Pattern;
59 import org.w3c.dom.DOMException;
60 import org.w3c.dom.Document;
61 import org.w3c.dom.NamedNodeMap;
62 import org.w3c.dom.Node;
63 import org.w3c.dom.NodeList;
64 import org.w3c.dom.UserDataHandler;
65
66
67 /***
68 * Extension DOM2/DOM3 node type for a namespace node.
69 *
70 * <p>This class implements the DOM2 and DOM3 {@link Node} interface
71 * to allow namespace nodes to be included in the result
72 * set of an XPath selectNodes operation, even though DOM does
73 * not model namespaces in scope as separate nodes.</p>
74 *
75 * <p>
76 * While all of the DOM2 methods are implemented with reasonable
77 * defaults, there will be some unexpected surprises, so users are
78 * advised to test for NamespaceNodes and filter them out from the
79 * result sets as early as possible.
80 * </p>
81 *
82 * <ol>
83 *
84 * <li>The {@link #getNodeType} method returns {@link #NAMESPACE_NODE},
85 * which is not one of the usual DOM2 node types. Generic code may
86 * fall unexpectedly out of switch statements, for example.</li>
87 *
88 * <li>The {@link #getOwnerDocument} method returns the owner document
89 * of the parent node, but that owner document will know nothing about
90 * the namespace node.</p>
91 *
92 * <li>The {@link #isSupported} method always returns false.</li>
93 *
94 * <li> The DOM3 methods sometimes throw UnsupportedOperationException.
95 * They're here only to allow this class to be compiled with Java 1.5.
96 * Do not call or rely on them.</li>
97 * </ol>
98 *
99 * <p>All attempts to modify a <code>NamespaceNode</code> will fail with a {@link
100 * DOMException} ({@link
101 * DOMException#NO_MODIFICATION_ALLOWED_ERR}).</p>
102 *
103 * @author David Megginson
104 * @author Elliotte Rusty Harold
105 * @see DocumentNavigator
106 */
107 public class NamespaceNode implements Node
108 {
109
110 /***
111 * Constant: this is a NamespaceNode.
112 *
113 * @see #getNodeType
114 */
115 public final static short NAMESPACE_NODE = Pattern.NAMESPACE_NODE;
116
117
118
119
120
121 /***
122 * Create a new NamespaceNode.
123 *
124 * @param parent the DOM node to which the namespace is attached
125 * @param name the namespace prefix
126 * @param value the namespace URI
127 */
128 public NamespaceNode (Node parent, String name, String value)
129 {
130 this.parent = parent;
131 this.name = name;
132 this.value = value;
133 }
134
135
136 /***
137 * Constructor.
138 *
139 * @param parent the DOM node to which the namespace is attached
140 * @param attribute the DOM attribute object containing the
141 * namespace declaration
142 */
143 NamespaceNode (Node parent, Node attribute)
144 {
145 String attributeName = attribute.getNodeName();
146
147 if (attributeName.equals("xmlns")) {
148 this.name = "";
149 }
150 else if (attributeName.startsWith("xmlns:")) {
151 this.name = attributeName.substring(6);
152 }
153 else {
154 this.name = attributeName;
155 }
156 this.parent = parent;
157 this.value = attribute.getNodeValue();
158 }
159
160
161
162
163
164
165
166
167 /***
168 * Get the namespace prefix.
169 *
170 * @return the namespace prefix, or "" for the default namespace
171 */
172 public String getNodeName ()
173 {
174 return name;
175 }
176
177
178 /***
179 * Get the namespace URI.
180 *
181 * @return the namespace URI
182 */
183 public String getNodeValue ()
184 {
185 return value;
186 }
187
188
189 /***
190 * Change the namespace URI (always fails).
191 *
192 * @param value the new URI
193 * @throws DOMException always
194 */
195 public void setNodeValue (String value) throws DOMException
196 {
197 disallowModification();
198 }
199
200
201 /***
202 * Get the node type.
203 *
204 * @return always {@link #NAMESPACE_NODE}.
205 */
206 public short getNodeType ()
207 {
208 return NAMESPACE_NODE;
209 }
210
211
212 /***
213 * Get the parent node.
214 *
215 * <p>This method returns the element that was queried for Namespaces
216 * in effect, <em>not</em> necessarily the actual element containing
217 * the Namespace declaration.</p>
218 *
219 * @return the parent node (not null)
220 */
221 public Node getParentNode ()
222 {
223 return parent;
224 }
225
226
227 /***
228 * Get the list of child nodes.
229 *
230 * @return an empty node list
231 */
232 public NodeList getChildNodes ()
233 {
234 return new EmptyNodeList();
235 }
236
237
238 /***
239 * Get the first child node.
240 *
241 * @return null
242 */
243 public Node getFirstChild ()
244 {
245 return null;
246 }
247
248
249 /***
250 * Get the last child node.
251 *
252 * @return null
253 */
254 public Node getLastChild ()
255 {
256 return null;
257 }
258
259
260 /***
261 * Get the previous sibling node.
262 *
263 * @return null
264 */
265 public Node getPreviousSibling ()
266 {
267 return null;
268 }
269
270
271 /***
272 * Get the next sibling node.
273 *
274 * @return null
275 */
276 public Node getNextSibling ()
277 {
278 return null;
279 }
280
281
282 /***
283 * Get the attribute nodes.
284 *
285 * @return null
286 */
287 public NamedNodeMap getAttributes ()
288 {
289 return null;
290 }
291
292
293 /***
294 * Get the owner document.
295 *
296 * @return the owner document <em>of the parent node</em>
297 */
298 public Document getOwnerDocument ()
299 {
300 if (parent == null) return null;
301 return parent.getOwnerDocument();
302 }
303
304
305 /***
306 * Insert a new child node (always fails).
307 *
308 * @param newChild the node to add
309 * @param refChild ignored
310 * @return never
311 * @throws DOMException always
312 * @see Node#insertBefore
313 */
314 public Node insertBefore (Node newChild, Node refChild)
315 throws DOMException
316 {
317 disallowModification();
318 return null;
319 }
320
321
322 /***
323 * Replace a child node (always fails).
324 *
325 * @param newChild the node to add
326 * @param oldChild the child node to replace
327 * @return never
328 * @throws DOMException always
329 * @see Node#replaceChild
330 */
331 public Node replaceChild (Node newChild, Node oldChild) throws DOMException
332 {
333 disallowModification();
334 return null;
335 }
336
337
338 /***
339 * Remove a child node (always fails).
340 *
341 * @param oldChild the child node to remove
342 * @return never
343 * @throws DOMException always
344 * @see Node#removeChild
345 */
346 public Node removeChild(Node oldChild) throws DOMException
347 {
348 disallowModification();
349 return null;
350 }
351
352
353 /***
354 * Append a new child node (always fails).
355 *
356 * @param newChild the node to add
357 * @return never
358 * @throws DOMException always
359 * @see Node#appendChild
360 */
361 public Node appendChild(Node newChild) throws DOMException
362 {
363 disallowModification();
364 return null;
365 }
366
367
368 /***
369 * Test for child nodes.
370 *
371 * @return false
372 */
373 public boolean hasChildNodes()
374 {
375 return false;
376 }
377
378
379 /***
380 * Create a copy of this node.
381 *
382 * @param deep make a deep copy (no effect, since namespace nodes
383 * don't have children).
384 * @return a new copy of this namespace node
385 */
386 public Node cloneNode (boolean deep)
387 {
388 return new NamespaceNode(parent, name, value);
389 }
390
391
392 /***
393 * Normalize the text descendants of this node.
394 *
395 * <p>This method has no effect, since namespace nodes have no
396 * descendants.</p>
397 */
398 public void normalize ()
399 {
400
401 }
402
403
404 /***
405 * Test if a DOM2 feature is supported. (None are.)
406 *
407 * @param feature the feature name
408 * @param version the feature version
409 * @return false
410 */
411 public boolean isSupported(String feature, String version)
412 {
413 return false;
414 }
415
416
417 /***
418 * Get the namespace URI of this node.
419 *
420 * <p>Namespace declarations are not themselves
421 * Namespace-qualified.</p>
422 *
423 * @return null
424 */
425 public String getNamespaceURI()
426 {
427 return null;
428 }
429
430
431 /***
432 * Get the namespace prefix of this node.
433 *
434 * <p>Namespace declarations are not themselves
435 * namespace-qualified.</p>
436 *
437 * @return null
438 * @see #getLocalName
439 */
440 public String getPrefix()
441 {
442 return null;
443 }
444
445
446 /***
447 * Change the namespace prefix of this node (always fails).
448 *
449 * @param prefix the new prefix
450 * @throws DOMException always thrown
451 */
452 public void setPrefix(String prefix)
453 throws DOMException
454 {
455 disallowModification();
456 }
457
458
459 /***
460 * Get the XPath name of the namespace node;; i.e. the
461 * namespace prefix.
462 *
463 * @return the namespace prefix
464 */
465 public String getLocalName ()
466 {
467 return name;
468 }
469
470
471 /***
472 * Test if this node has attributes.
473 *
474 * @return false
475 */
476 public boolean hasAttributes ()
477 {
478 return false;
479 }
480
481
482 /***
483 * Throw a NO_MODIFICATION_ALLOWED_ERR DOMException.
484 *
485 * @throws DOMException always thrown
486 */
487 private void disallowModification () throws DOMException
488 {
489 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
490 "Namespace node may not be modified");
491 }
492
493
494
495
496
497
498
499
500 /***
501 * Generate a hash code for a namespace node.
502 *
503 * @return a hash code for this node
504 */
505 public int hashCode ()
506 {
507 return hashCode(parent) + hashCode(name) + hashCode(value);
508 }
509
510
511 /***
512 * Test for equivalence with another object.
513 *
514 * <p>Two Namespace nodes are considered equivalent if their parents,
515 * names, and values are equal.</p>
516 *
517 * @param o the object to test for equality
518 * @return true if the object is equivalent to this node, false
519 * otherwise
520 */
521 public boolean equals (Object o)
522 {
523 if (o == this) return true;
524 else if (o == null) return false;
525 else if (o instanceof NamespaceNode) {
526 NamespaceNode ns = (NamespaceNode)o;
527 return (equals(parent, ns.getParentNode()) &&
528 equals(name, ns.getNodeName()) &&
529 equals(value, ns.getNodeValue()));
530 } else {
531 return false;
532 }
533 }
534
535
536 /***
537 * Helper method for generating a hash code.
538 *
539 * @param o the object for generating a hash code (possibly null)
540 * @return the object's hash code, or 0 if the object is null
541 * @see java.lang.Object#hashCode
542 */
543 private int hashCode (Object o)
544 {
545 return (o == null ? 0 : o.hashCode());
546 }
547
548
549 /***
550 * Helper method for comparing two objects.
551 *
552 * @param a the first object to compare (possibly null)
553 * @param b the second object to compare (possibly null)
554 * @return true if the objects are equivalent or are both null
555 * @see java.lang.Object#equals
556 */
557 private boolean equals (Object a, Object b)
558 {
559 return ((a == null && b == null) ||
560 (a != null && a.equals(b)));
561 }
562
563
564
565
566
567
568 private Node parent;
569 private String name;
570 private String value;
571
572
573
574
575
576
577
578
579 /***
580 * A node list with no members.
581 *
582 * <p>This class is necessary for the {@link Node#getChildNodes}
583 * method, which must return an empty node list rather than
584 * null when there are no children.</p>
585 */
586 private static class EmptyNodeList implements NodeList
587 {
588
589 /***
590 * @see NodeList#getLength
591 */
592 public int getLength ()
593 {
594 return 0;
595 }
596
597
598 /***
599 * @see NodeList#item
600 */
601 public Node item(int index)
602 {
603 return null;
604 }
605
606 }
607
608
609
610
611
612 /***
613 * Return the base URI of the document containing this node.
614 * This only works in DOM 3.
615 *
616 * @return null
617 */
618 public String getBaseURI() {
619 Class clazz = Node.class;
620 try {
621 Class[] args = new Class[0];
622 Method getBaseURI = clazz.getMethod("getBaseURI", args);
623 String base = (String) getBaseURI.invoke(this.getParentNode(), args);
624 return base;
625 }
626 catch (Exception ex) {
627 return null;
628 }
629 }
630
631
632 /***
633 * Compare relative position of this node to another nbode. (Always fails).
634 * This method is included solely for compatibility with the superclass.
635 *
636 * @param other the node to compare to
637 *
638 * @return never
639 * @throws DOMException NOT_SUPPORTED_ERR
640 */
641 public short compareDocumentPosition(Node other) throws DOMException {
642 DOMException ex = new DOMException(
643 DOMException.NOT_SUPPORTED_ERR,
644 "DOM level 3 interfaces are not fully implemented in Jaxen's NamespaceNode class"
645 );
646 throw ex;
647 }
648
649
650 /***
651 * Return the namespace URI.
652 *
653 * @return the namespace URI
654 * @see #getNodeValue
655 */
656 public String getTextContent() {
657 return value;
658 }
659
660
661 /***
662 * Change the value of this node (always fails).
663 * This method is included solely for compatibility with the superclass.
664 *
665 * @param textContent the new content
666 * @throws DOMException always
667 */
668 public void setTextContent(String textContent) throws DOMException {
669 disallowModification();
670 }
671
672
673 /***
674 * Returns true if and only if this object represents the same XPath namespace node
675 * as the argument; that is, they have the same parent, the same prefix, and the
676 * same URI.
677 *
678 * @param other the node to compare to
679 * @return true if this object represents the same XPath namespace node
680 * as other; false otherwise
681 */
682 public boolean isSameNode(Node other) {
683 boolean a = this.isEqualNode(other);
684
685
686
687
688
689 boolean b;
690 Node thisParent = this.getParentNode();
691 Node thatParent = other.getParentNode();
692 try {
693 Class clazz = Node.class;
694 Class[] args = {clazz};
695 Method isEqual = clazz.getMethod("isEqual", args);
696 Object[] args2 = new Object[1];
697 args2[0] = thatParent;
698 Boolean result = (Boolean) isEqual.invoke(thisParent, args2);
699 b = result.booleanValue();
700 }
701 catch (NoSuchMethodException ex) {
702 b = thisParent.equals(thatParent);
703 }
704 catch (InvocationTargetException ex) {
705 b = thisParent.equals(thatParent);
706 }
707 catch (IllegalAccessException ex) {
708 b = thisParent.equals(thatParent);
709 }
710
711 return a && b;
712
713 }
714
715
716 /***
717 * Return the prefix bound to this namespace URI within the scope
718 * of this node.
719 *
720 * @param namespaceURI the URI to find a prefix binding for
721 *
722 * @return a prefix matching this namespace URI
723 * @throws UnsupportedOperationException in DOM 2
724 */
725 public String lookupPrefix(String namespaceURI) {
726
727
728
729
730 try {
731 Class clazz = Node.class;
732 Class[] argTypes = {String.class};
733 Method lookupPrefix = clazz.getMethod("lookupPrefix", argTypes);
734 String[] args = {namespaceURI};
735 String result = (String) lookupPrefix.invoke(parent, args);
736 return result;
737 }
738 catch (NoSuchMethodException ex) {
739 throw new UnsupportedOperationException("Cannot lookup prefixes in DOM 2");
740 }
741 catch (InvocationTargetException ex) {
742 throw new UnsupportedOperationException("Cannot lookup prefixes in DOM 2");
743 }
744 catch (IllegalAccessException ex) {
745 throw new UnsupportedOperationException("Cannot lookup prefixes in DOM 2");
746 }
747
748 }
749
750
751 /***
752 * Return true if the specified URI is the default namespace in
753 * scope (always fails). This method is included solely for
754 * compatibility with the superclass.
755 *
756 * @param namespaceURI the URI to check
757 *
758 * @return never
759 * @throws UnsupportedOperationException always
760 */
761 public boolean isDefaultNamespace(String namespaceURI) {
762 return namespaceURI.equals(this.lookupNamespaceURI(null));
763 }
764
765
766 /***
767 * Return the namespace URI mapped to the specified
768 * prefix within the scope of this namespace node.
769 *
770 * @param prefix the prefix to search for
771 *
772 * @return the namespace URI mapped to this prefix
773 * @throws UnsupportedOperationException in DOM 2
774 */
775 public String lookupNamespaceURI(String prefix) {
776
777
778
779
780 try {
781 Class clazz = Node.class;
782 Class[] argTypes = {String.class};
783 Method lookupNamespaceURI = clazz.getMethod("lookupNamespaceURI", argTypes);
784 String[] args = {prefix};
785 String result = (String) lookupNamespaceURI.invoke(parent, args);
786 return result;
787 }
788 catch (NoSuchMethodException ex) {
789 throw new UnsupportedOperationException("Cannot lookup namespace URIs in DOM 2");
790 }
791 catch (InvocationTargetException ex) {
792 throw new UnsupportedOperationException("Cannot lookup namespace URIs in DOM 2");
793 }
794 catch (IllegalAccessException ex) {
795 throw new UnsupportedOperationException("Cannot lookup namespace URIs in DOM 2");
796 }
797 }
798
799
800 /***
801 * Returns true if this object binds the same prefix to the same URI.
802 * That is, this object has the same prefix and URI as the argument.
803 *
804 * @param arg the node to compare to
805 * @return true if this object has the same prefix and URI as the argument; false otherwise
806 */
807 public boolean isEqualNode(Node arg) {
808 if (arg.getNodeType() == this.getNodeType()) {
809 NamespaceNode other = (NamespaceNode) arg;
810 if (other.name == null && this.name != null) return false;
811 else if (other.name != null && this.name == null) return false;
812 else if (other.value == null && this.value != null) return false;
813 else if (other.value != null && this.value == null) return false;
814 else if (other.name == null && this.name == null) {
815 return other.value.equals(this.value);
816 }
817
818 return other.name.equals(this.name) && other.value.equals(this.value);
819 }
820 return false;
821 }
822
823
824 /***
825 * Returns the value of the requested feature. Always returns null.
826 *
827 * @return null
828 */
829 public Object getFeature(String feature, String version) {
830 return null;
831 }
832
833
834
835 private HashMap userData = new HashMap();
836
837 /***
838 * Associates an object with a key.
839 *
840 * @param key the key by which the data will be retrieved
841 * @param data the object to store with the key
842 * @param handler ignored since namespace nodes cannot be imported, cloned, or renamed
843 *
844 * @return the value previously associated with this key; or null
845 * if there isn't any such previous value
846 */
847 public Object setUserData(String key, Object data, UserDataHandler handler) {
848 Object oldValue = getUserData(key);
849 userData.put(key, data);
850 return oldValue;
851 }
852
853
854 /***
855 * Returns the user data associated with the given key.
856 *
857 * @param key the lookup key
858 *
859 * @return the object associated with the key; or null if no such object is available
860 */
861 public Object getUserData(String key) {
862 return userData.get(key);
863 }
864
865 }
866
867