1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.jxpath.ri.model.dom;
17
18 import java.util.HashMap;
19 import java.util.Locale;
20 import java.util.Map;
21
22 import org.apache.commons.jxpath.AbstractFactory;
23 import org.apache.commons.jxpath.JXPathContext;
24 import org.apache.commons.jxpath.JXPathException;
25 import org.apache.commons.jxpath.Pointer;
26 import org.apache.commons.jxpath.ri.Compiler;
27 import org.apache.commons.jxpath.ri.QName;
28 import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
29 import org.apache.commons.jxpath.ri.compiler.NodeTest;
30 import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
31 import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
32 import org.apache.commons.jxpath.ri.model.beans.NullPointer;
33 import org.apache.commons.jxpath.ri.model.NodeIterator;
34 import org.apache.commons.jxpath.ri.model.NodePointer;
35 import org.apache.commons.jxpath.util.TypeUtils;
36 import org.w3c.dom.Attr;
37 import org.w3c.dom.Comment;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Element;
40 import org.w3c.dom.NamedNodeMap;
41 import org.w3c.dom.Node;
42 import org.w3c.dom.NodeList;
43 import org.w3c.dom.ProcessingInstruction;
44
45 /***
46 * A Pointer that points to a DOM node.
47 *
48 * @author Dmitri Plotnikov
49 * @version $Revision: 1.24 $ $Date: 2004/06/29 22:58:17 $
50 */
51 public class DOMNodePointer extends NodePointer {
52 private Node node;
53 private Map namespaces;
54 private String defaultNamespace;
55 private String id;
56
57 public static final String XML_NAMESPACE_URI =
58 "http://www.w3.org/XML/1998/namespace";
59 public static final String XMLNS_NAMESPACE_URI =
60 "http://www.w3.org/2000/xmlns/";
61
62 public DOMNodePointer(Node node, Locale locale) {
63 super(null, locale);
64 this.node = node;
65 }
66
67 public DOMNodePointer(Node node, Locale locale, String id) {
68 super(null, locale);
69 this.node = node;
70 this.id = id;
71 }
72
73 public DOMNodePointer(NodePointer parent, Node node) {
74 super(parent);
75 this.node = node;
76 }
77
78 public boolean testNode(NodeTest test) {
79 return testNode(node, test);
80 }
81
82 public static boolean testNode(Node node, NodeTest test) {
83 if (test == null) {
84 return true;
85 }
86 else if (test instanceof NodeNameTest) {
87 if (node.getNodeType() != Node.ELEMENT_NODE) {
88 return false;
89 }
90
91 NodeNameTest nodeNameTest = (NodeNameTest) test;
92 QName testName = nodeNameTest.getNodeName();
93 String namespaceURI = nodeNameTest.getNamespaceURI();
94 boolean wildcard = nodeNameTest.isWildcard();
95 String testPrefix = testName.getPrefix();
96 if (wildcard && testPrefix == null) {
97 return true;
98 }
99
100 if (wildcard
101 || testName.getName()
102 .equals(DOMNodePointer.getLocalName(node))) {
103 String nodeNS = DOMNodePointer.getNamespaceURI(node);
104 return equalStrings(namespaceURI, nodeNS);
105 }
106 }
107 else if (test instanceof NodeTypeTest) {
108 int nodeType = node.getNodeType();
109 switch (((NodeTypeTest) test).getNodeType()) {
110 case Compiler.NODE_TYPE_NODE :
111 return nodeType == Node.ELEMENT_NODE;
112 case Compiler.NODE_TYPE_TEXT :
113 return nodeType == Node.CDATA_SECTION_NODE
114 || nodeType == Node.TEXT_NODE;
115 case Compiler.NODE_TYPE_COMMENT :
116 return nodeType == Node.COMMENT_NODE;
117 case Compiler.NODE_TYPE_PI :
118 return nodeType == Node.PROCESSING_INSTRUCTION_NODE;
119 }
120 return false;
121 }
122 else if (test instanceof ProcessingInstructionTest) {
123 if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
124 String testPI = ((ProcessingInstructionTest) test).getTarget();
125 String nodePI = ((ProcessingInstruction) node).getTarget();
126 return testPI.equals(nodePI);
127 }
128 }
129 return false;
130 }
131
132 private static boolean equalStrings(String s1, String s2) {
133 if (s1 == null && s2 != null) {
134 return false;
135 }
136 if (s1 != null && s2 == null) {
137 return false;
138 }
139
140 if (s1 != null && !s1.trim().equals(s2.trim())) {
141 return false;
142 }
143
144 return true;
145 }
146
147 public QName getName() {
148 String ln = null;
149 String ns = null;
150 int type = node.getNodeType();
151 if (type == Node.ELEMENT_NODE) {
152 ns = DOMNodePointer.getPrefix(node);
153 ln = DOMNodePointer.getLocalName(node);
154 }
155 else if (type == Node.PROCESSING_INSTRUCTION_NODE) {
156 ln = ((ProcessingInstruction) node).getTarget();
157 }
158 return new QName(ns, ln);
159 }
160
161 public String getNamespaceURI() {
162 return getNamespaceURI(node);
163 }
164
165 public NodeIterator childIterator(
166 NodeTest test,
167 boolean reverse,
168 NodePointer startWith)
169 {
170 return new DOMNodeIterator(this, test, reverse, startWith);
171 }
172
173 public NodeIterator attributeIterator(QName name) {
174 return new DOMAttributeIterator(this, name);
175 }
176
177 public NodePointer namespacePointer(String prefix) {
178 return new NamespacePointer(this, prefix);
179 }
180
181 public NodeIterator namespaceIterator() {
182 return new DOMNamespaceIterator(this);
183 }
184
185 public String getNamespaceURI(String prefix) {
186 if (prefix == null || prefix.equals("")) {
187 return getDefaultNamespaceURI();
188 }
189
190 if (prefix.equals("xml")) {
191 return XML_NAMESPACE_URI;
192 }
193
194 if (prefix.equals("xmlns")) {
195 return XMLNS_NAMESPACE_URI;
196 }
197
198 String namespace = null;
199 if (namespaces == null) {
200 namespaces = new HashMap();
201 }
202 else {
203 namespace = (String) namespaces.get(prefix);
204 }
205
206 if (namespace == null) {
207 String qname = "xmlns:" + prefix;
208 Node aNode = node;
209 if (aNode instanceof Document) {
210 aNode = ((Document)aNode).getDocumentElement();
211 }
212 while (aNode != null) {
213 if (aNode.getNodeType() == Node.ELEMENT_NODE) {
214 Attr attr = ((Element) aNode).getAttributeNode(qname);
215 if (attr != null) {
216 namespace = attr.getValue();
217 break;
218 }
219 }
220 aNode = aNode.getParentNode();
221 }
222 if (namespace == null || namespace.equals("")) {
223 namespace = NodePointer.UNKNOWN_NAMESPACE;
224 }
225 }
226
227 namespaces.put(prefix, namespace);
228
229 return namespace;
230 }
231
232 private String getNamespaceURI(String prefix, String namespace) {
233 String qname = "xmlns:" + prefix;
234 Node aNode = node;
235 if (aNode instanceof Document) {
236 aNode = ((Document)aNode).getDocumentElement();
237 }
238 while (aNode != null) {
239 if (aNode.getNodeType() == Node.ELEMENT_NODE) {
240 Attr attr = ((Element) aNode).getAttributeNode(qname);
241 if (attr != null) {
242 namespace = attr.getValue();
243 break;
244 }
245 }
246 aNode = aNode.getParentNode();
247 }
248 return namespace;
249 }
250
251 public String getDefaultNamespaceURI() {
252 if (defaultNamespace == null) {
253 Node aNode = node;
254 while (aNode != null) {
255 if (aNode.getNodeType() == Node.ELEMENT_NODE) {
256 Attr attr = ((Element) aNode).getAttributeNode("xmlns");
257 if (attr != null) {
258 defaultNamespace = attr.getValue();
259 break;
260 }
261 }
262 aNode = aNode.getParentNode();
263 }
264 }
265 if (defaultNamespace == null) {
266 defaultNamespace = "";
267 }
268
269 return defaultNamespace.equals("") ? null : defaultNamespace;
270 }
271
272 public Object getBaseValue() {
273 return node;
274 }
275
276 public Object getImmediateNode() {
277 return node;
278 }
279
280 public boolean isActual() {
281 return true;
282 }
283
284 public boolean isCollection() {
285 return false;
286 }
287
288 public int getLength() {
289 return 1;
290 }
291
292 public boolean isLeaf() {
293 return !node.hasChildNodes();
294 }
295
296 /***
297 * Returns true if the xml:lang attribute for the current node
298 * or its parent has the specified prefix <i>lang</i>.
299 * If no node has this prefix, calls <code>super.isLanguage(lang)</code>.
300 */
301 public boolean isLanguage(String lang) {
302 String current = getLanguage();
303 if (current == null) {
304 return super.isLanguage(lang);
305 }
306 return current.toUpperCase().startsWith(lang.toUpperCase());
307 }
308
309 protected String getLanguage() {
310 Node n = node;
311 while (n != null) {
312 if (n.getNodeType() == Node.ELEMENT_NODE) {
313 Element e = (Element) n;
314 String attr = e.getAttribute("xml:lang");
315 if (attr != null && !attr.equals("")) {
316 return attr;
317 }
318 }
319 n = n.getParentNode();
320 }
321 return null;
322 }
323
324 /***
325 * Sets contents of the node to the specified value. If the value is
326 * a String, the contents of the node are replaced with this text.
327 * If the value is an Element or Document, the children of the
328 * node are replaced with the children of the passed node.
329 */
330 public void setValue(Object value) {
331 if (node.getNodeType() == Node.TEXT_NODE
332 || node.getNodeType() == Node.CDATA_SECTION_NODE) {
333 String string = (String) TypeUtils.convert(value, String.class);
334 if (string != null && !string.equals("")) {
335 node.setNodeValue(string);
336 }
337 else {
338 node.getParentNode().removeChild(node);
339 }
340 }
341 else {
342 NodeList children = node.getChildNodes();
343 int count = children.getLength();
344 for (int i = count; --i >= 0;) {
345 Node child = children.item(i);
346 node.removeChild(child);
347 }
348
349 if (value instanceof Node) {
350 Node valueNode = (Node) value;
351 if (valueNode instanceof Element
352 || valueNode instanceof Document) {
353 children = valueNode.getChildNodes();
354 for (int i = 0; i < children.getLength(); i++) {
355 Node child = children.item(i);
356 node.appendChild(child.cloneNode(true));
357 }
358 }
359 else {
360 node.appendChild(valueNode.cloneNode(true));
361 }
362 }
363 else {
364 String string = (String) TypeUtils.convert(value, String.class);
365 if (string != null && !string.equals("")) {
366 Node textNode =
367 node.getOwnerDocument().createTextNode(string);
368 node.appendChild(textNode);
369 }
370 }
371 }
372 }
373
374 public NodePointer createChild(
375 JXPathContext context,
376 QName name,
377 int index)
378 {
379 if (index == WHOLE_COLLECTION) {
380 index = 0;
381 }
382 boolean success =
383 getAbstractFactory(context).createObject(
384 context,
385 this,
386 node,
387 name.toString(),
388 index);
389 if (success) {
390 NodeTest nodeTest;
391 String prefix = name.getPrefix();
392 if (prefix != null) {
393 String namespaceURI = context.getNamespaceURI(prefix);
394 nodeTest = new NodeNameTest(name, namespaceURI);
395 }
396 else {
397 nodeTest = new NodeNameTest(name);
398 }
399
400 NodeIterator it = childIterator(nodeTest, false, null);
401 if (it != null && it.setPosition(index + 1)) {
402 return it.getNodePointer();
403 }
404 }
405 throw new JXPathException(
406 "Factory could not create a child node for path: "
407 + asPath()
408 + "/"
409 + name
410 + "["
411 + (index + 1)
412 + "]");
413 }
414
415 public NodePointer createChild(JXPathContext context,
416 QName name, int index, Object value)
417 {
418 NodePointer ptr = createChild(context, name, index);
419 ptr.setValue(value);
420 return ptr;
421 }
422
423 public NodePointer createAttribute(JXPathContext context, QName name) {
424 if (!(node instanceof Element)) {
425 return super.createAttribute(context, name);
426 }
427 Element element = (Element) node;
428 String prefix = name.getPrefix();
429 if (prefix != null) {
430 String ns = getNamespaceURI(prefix);
431 if (ns == null) {
432 throw new JXPathException(
433 "Unknown namespace prefix: " + prefix);
434 }
435 element.setAttributeNS(ns, name.toString(), "");
436 }
437 else {
438 if (!element.hasAttribute(name.getName())) {
439 element.setAttribute(name.getName(), "");
440 }
441 }
442 NodeIterator it = attributeIterator(name);
443 it.setPosition(1);
444 return it.getNodePointer();
445 }
446
447 public void remove() {
448 Node parent = node.getParentNode();
449 if (parent == null) {
450 throw new JXPathException("Cannot remove root DOM node");
451 }
452 parent.removeChild(node);
453 }
454
455 public String asPath() {
456 if (id != null) {
457 return "id('" + escape(id) + "')";
458 }
459
460 StringBuffer buffer = new StringBuffer();
461 if (parent != null) {
462 buffer.append(parent.asPath());
463 }
464 switch (node.getNodeType()) {
465 case Node.ELEMENT_NODE :
466
467
468
469 if (parent instanceof DOMNodePointer) {
470 if (buffer.length() == 0
471 || buffer.charAt(buffer.length() - 1) != '/') {
472 buffer.append('/');
473 }
474 String nsURI = getNamespaceURI();
475 String ln = DOMNodePointer.getLocalName(node);
476
477 if (nsURI == null) {
478 buffer.append(ln);
479 buffer.append('[');
480 buffer.append(getRelativePositionByName()).append(']');
481 }
482 else {
483 String prefix = getNamespaceResolver().getPrefix(nsURI);
484 if (prefix != null) {
485 buffer.append(prefix);
486 buffer.append(':');
487 buffer.append(ln);
488 buffer.append('[');
489 buffer.append(getRelativePositionByName());
490 buffer.append(']');
491 }
492 else {
493 buffer.append("node()");
494 buffer.append('[');
495 buffer.append(getRelativePositionOfElement());
496 buffer.append(']');
497 }
498 }
499 }
500 break;
501 case Node.TEXT_NODE :
502 case Node.CDATA_SECTION_NODE :
503 buffer.append("/text()");
504 buffer.append('[');
505 buffer.append(getRelativePositionOfTextNode()).append(']');
506 break;
507 case Node.PROCESSING_INSTRUCTION_NODE :
508 String target = ((ProcessingInstruction) node).getTarget();
509 buffer.append("/processing-instruction(\'");
510 buffer.append(target).append("')");
511 buffer.append('[');
512 buffer.append(getRelativePositionOfPI(target)).append(']');
513 break;
514 case Node.DOCUMENT_NODE :
515
516 }
517 return buffer.toString();
518 }
519
520 private String escape(String string) {
521 int index = string.indexOf('\'');
522 while (index != -1) {
523 string =
524 string.substring(0, index)
525 + "'"
526 + string.substring(index + 1);
527 index = string.indexOf('\'');
528 }
529 index = string.indexOf('\"');
530 while (index != -1) {
531 string =
532 string.substring(0, index)
533 + """
534 + string.substring(index + 1);
535 index = string.indexOf('\"');
536 }
537 return string;
538 }
539
540 private int getRelativePositionByName() {
541 int count = 1;
542 Node n = node.getPreviousSibling();
543 while (n != null) {
544 if (n.getNodeType() == Node.ELEMENT_NODE) {
545 String nm = n.getNodeName();
546 if (nm.equals(node.getNodeName())) {
547 count++;
548 }
549 }
550 n = n.getPreviousSibling();
551 }
552 return count;
553 }
554
555 private int getRelativePositionOfElement() {
556 int count = 1;
557 Node n = node.getPreviousSibling();
558 while (n != null) {
559 if (n.getNodeType() == Node.ELEMENT_NODE) {
560 count++;
561 }
562 n = n.getPreviousSibling();
563 }
564 return count;
565 }
566
567 private int getRelativePositionOfTextNode() {
568 int count = 1;
569 Node n = node.getPreviousSibling();
570 while (n != null) {
571 if (n.getNodeType() == Node.TEXT_NODE
572 || n.getNodeType() == Node.CDATA_SECTION_NODE) {
573 count++;
574 }
575 n = n.getPreviousSibling();
576 }
577 return count;
578 }
579
580 private int getRelativePositionOfPI(String target) {
581 int count = 1;
582 Node n = node.getPreviousSibling();
583 while (n != null) {
584 if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
585 && ((ProcessingInstruction) n).getTarget().equals(target)) {
586 count++;
587 }
588 n = n.getPreviousSibling();
589 }
590 return count;
591 }
592
593 public int hashCode() {
594 return System.identityHashCode(node);
595 }
596
597 public boolean equals(Object object) {
598 if (object == this) {
599 return true;
600 }
601
602 if (!(object instanceof DOMNodePointer)) {
603 return false;
604 }
605
606 DOMNodePointer other = (DOMNodePointer) object;
607 return node == other.node;
608 }
609
610 public static String getPrefix(Node node) {
611 String prefix = node.getPrefix();
612 if (prefix != null) {
613 return prefix;
614 }
615
616 String name = node.getNodeName();
617 int index = name.lastIndexOf(':');
618 if (index == -1) {
619 return null;
620 }
621
622 return name.substring(0, index);
623 }
624
625 public static String getLocalName(Node node) {
626 String localName = node.getLocalName();
627 if (localName != null) {
628 return localName;
629 }
630
631 String name = node.getNodeName();
632 int index = name.lastIndexOf(':');
633 if (index == -1) {
634 return name;
635 }
636
637 return name.substring(index + 1);
638 }
639
640 public static String getNamespaceURI(Node node) {
641 if (node instanceof Document) {
642 node = ((Document) node).getDocumentElement();
643 }
644
645 Element element = (Element) node;
646
647 String uri = element.getNamespaceURI();
648 if (uri != null) {
649 return uri;
650 }
651
652 String qname;
653 String prefix = getPrefix(node);
654 if (prefix == null) {
655 qname = "xmlns";
656 }
657 else {
658 qname = "xmlns:" + prefix;
659 }
660
661 Node aNode = node;
662 while (aNode != null) {
663 if (aNode.getNodeType() == Node.ELEMENT_NODE) {
664 Attr attr = ((Element) aNode).getAttributeNode(qname);
665 if (attr != null) {
666 return attr.getValue();
667 }
668 }
669 aNode = aNode.getParentNode();
670 }
671 return null;
672 }
673
674 public Object getValue() {
675 return stringValue(node);
676 }
677
678 private String stringValue(Node node) {
679 int nodeType = node.getNodeType();
680 if (nodeType == Node.COMMENT_NODE) {
681 String text = ((Comment) node).getData();
682 return text == null ? "" : text.trim();
683 }
684 else if (
685 nodeType == Node.TEXT_NODE
686 || nodeType == Node.CDATA_SECTION_NODE) {
687 String text = node.getNodeValue();
688 return text == null ? "" : text.trim();
689 }
690 else if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
691 String text = ((ProcessingInstruction) node).getData();
692 return text == null ? "" : text.trim();
693 }
694 else {
695 NodeList list = node.getChildNodes();
696 StringBuffer buf = new StringBuffer(16);
697 for (int i = 0; i < list.getLength(); i++) {
698 Node child = list.item(i);
699 if (child.getNodeType() == Node.TEXT_NODE) {
700 buf.append(child.getNodeValue());
701 }
702 else {
703 buf.append(stringValue(child));
704 }
705 }
706 return buf.toString().trim();
707 }
708 }
709
710 /***
711 * Locates a node by ID.
712 */
713 public Pointer getPointerByID(JXPathContext context, String id) {
714 Document document;
715 if (node.getNodeType() == Node.DOCUMENT_NODE) {
716 document = (Document) node;
717 }
718 else {
719 document = node.getOwnerDocument();
720 }
721 Element element = document.getElementById(id);
722 if (element != null) {
723 return new DOMNodePointer(element, getLocale(), id);
724 }
725 else {
726 return new NullPointer(getLocale(), id);
727 }
728 }
729
730 private AbstractFactory getAbstractFactory(JXPathContext context) {
731 AbstractFactory factory = context.getFactory();
732 if (factory == null) {
733 throw new JXPathException(
734 "Factory is not set on the JXPathContext - "
735 + "cannot create path: "
736 + asPath());
737 }
738 return factory;
739 }
740
741 public int compareChildNodePointers(
742 NodePointer pointer1, NodePointer pointer2)
743 {
744 Node node1 = (Node) pointer1.getBaseValue();
745 Node node2 = (Node) pointer2.getBaseValue();
746 if (node1 == node2) {
747 return 0;
748 }
749
750 int t1 = node1.getNodeType();
751 int t2 = node2.getNodeType();
752 if (t1 == Node.ATTRIBUTE_NODE && t2 != Node.ATTRIBUTE_NODE) {
753 return -1;
754 }
755 else if (t1 != Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
756 return 1;
757 }
758 else if (t1 == Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
759 NamedNodeMap map = ((Node) getNode()).getAttributes();
760 int length = map.getLength();
761 for (int i = 0; i < length; i++) {
762 Node n = map.item(i);
763 if (n == node1) {
764 return -1;
765 }
766 else if (n == node2) {
767 return 1;
768 }
769 }
770 return 0;
771 }
772
773 Node current = node.getFirstChild();
774 while (current != null) {
775 if (current == node1) {
776 return -1;
777 }
778 else if (current == node2) {
779 return 1;
780 }
781 current = current.getNextSibling();
782 }
783
784 return 0;
785 }
786 }