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