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