001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.jxpath.ri.model.jdom;
018
019 import java.util.List;
020 import java.util.Locale;
021
022 import org.apache.commons.jxpath.JXPathAbstractFactoryException;
023 import org.apache.commons.jxpath.JXPathContext;
024 import org.apache.commons.jxpath.JXPathException;
025 import org.apache.commons.jxpath.ri.Compiler;
026 import org.apache.commons.jxpath.ri.NamespaceResolver;
027 import org.apache.commons.jxpath.ri.QName;
028 import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
029 import org.apache.commons.jxpath.ri.compiler.NodeTest;
030 import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
031 import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
032 import org.apache.commons.jxpath.ri.model.NodeIterator;
033 import org.apache.commons.jxpath.ri.model.NodePointer;
034 import org.apache.commons.jxpath.util.TypeUtils;
035 import org.jdom.Attribute;
036 import org.jdom.CDATA;
037 import org.jdom.Comment;
038 import org.jdom.Document;
039 import org.jdom.Element;
040 import org.jdom.Namespace;
041 import org.jdom.ProcessingInstruction;
042 import org.jdom.Text;
043
044 /**
045 * A Pointer that points to a DOM node.
046 *
047 * @author Dmitri Plotnikov
048 * @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
049 */
050 public class JDOMNodePointer extends NodePointer {
051 private static final long serialVersionUID = -6346532297491082651L;
052
053 private Object node;
054 private String id;
055 private NamespaceResolver localNamespaceResolver;
056
057 /** XML ns uri */
058 public static final String XML_NAMESPACE_URI =
059 "http://www.w3.org/XML/1998/namespace";
060
061 /** XMLNS ns uri */
062 public static final String XMLNS_NAMESPACE_URI =
063 "http://www.w3.org/2000/xmlns/";
064
065 /**
066 * Create a new JDOMNodePointer.
067 * @param node pointed
068 * @param locale Locale
069 */
070 public JDOMNodePointer(Object node, Locale locale) {
071 super(null, locale);
072 this.node = node;
073 }
074
075 /**
076 * Create a new JDOMNodePointer.
077 * @param node pointed
078 * @param locale Locale
079 * @param id String id
080 */
081 public JDOMNodePointer(Object node, Locale locale, String id) {
082 super(null, locale);
083 this.node = node;
084 this.id = id;
085 }
086
087 /**
088 * Create a new JDOMNodePointer.
089 * @param parent NodePointer
090 * @param node pointed
091 */
092 public JDOMNodePointer(NodePointer parent, Object node) {
093 super(parent);
094 this.node = node;
095 }
096
097 public NodeIterator childIterator(
098 NodeTest test,
099 boolean reverse,
100 NodePointer startWith) {
101 return new JDOMNodeIterator(this, test, reverse, startWith);
102 }
103
104 public NodeIterator attributeIterator(QName name) {
105 return new JDOMAttributeIterator(this, name);
106 }
107
108 public NodeIterator namespaceIterator() {
109 return new JDOMNamespaceIterator(this);
110 }
111
112 public NodePointer namespacePointer(String prefix) {
113 return new JDOMNamespacePointer(this, prefix);
114 }
115
116 public String getNamespaceURI() {
117 return getNamespaceURI(node);
118 }
119
120 /**
121 * Get the ns uri of the specified node.
122 * @param node Node to check
123 * @return String
124 */
125 private static String getNamespaceURI(Object node) {
126 if (node instanceof Element) {
127 Element element = (Element) node;
128 String ns = element.getNamespaceURI();
129 if (ns != null && ns.equals("")) {
130 ns = null;
131 }
132 return ns;
133 }
134 return null;
135 }
136
137 public synchronized NamespaceResolver getNamespaceResolver() {
138 if (localNamespaceResolver == null) {
139 localNamespaceResolver = new NamespaceResolver(super.getNamespaceResolver());
140 localNamespaceResolver.setNamespaceContextPointer(this);
141 }
142 return localNamespaceResolver;
143 }
144
145 public String getNamespaceURI(String prefix) {
146 if (prefix.equals("xml")) {
147 return Namespace.XML_NAMESPACE.getURI();
148 }
149 Element element = null;
150 if (node instanceof Document) {
151 element = ((Document) node).getRootElement();
152 }
153 if (node instanceof Element) {
154 element = (Element) node;
155 }
156 if (element == null) {
157 return null;
158 }
159 Namespace ns = element.getNamespace(prefix);
160 return ns == null ? null : ns.getURI();
161 }
162
163 public int compareChildNodePointers(
164 NodePointer pointer1,
165 NodePointer pointer2) {
166 Object node1 = pointer1.getBaseValue();
167 Object node2 = pointer2.getBaseValue();
168 if (node1 == node2) {
169 return 0;
170 }
171
172 if ((node1 instanceof Attribute) && !(node2 instanceof Attribute)) {
173 return -1;
174 }
175 if (
176 !(node1 instanceof Attribute) && (node2 instanceof Attribute)) {
177 return 1;
178 }
179 if (
180 (node1 instanceof Attribute) && (node2 instanceof Attribute)) {
181 List list = ((Element) getNode()).getAttributes();
182 int length = list.size();
183 for (int i = 0; i < length; i++) {
184 Object n = list.get(i);
185 if (n == node1) {
186 return -1;
187 }
188 else if (n == node2) {
189 return 1;
190 }
191 }
192 return 0; // Should not happen
193 }
194
195 if (!(node instanceof Element)) {
196 throw new RuntimeException(
197 "JXPath internal error: "
198 + "compareChildNodes called for "
199 + node);
200 }
201
202 List children = ((Element) node).getContent();
203 int length = children.size();
204 for (int i = 0; i < length; i++) {
205 Object n = children.get(i);
206 if (n == node1) {
207 return -1;
208 }
209 if (n == node2) {
210 return 1;
211 }
212 }
213
214 return 0;
215 }
216
217 public Object getBaseValue() {
218 return node;
219 }
220
221 public boolean isCollection() {
222 return false;
223 }
224
225 public int getLength() {
226 return 1;
227 }
228
229 public boolean isLeaf() {
230 if (node instanceof Element) {
231 return ((Element) node).getContent().size() == 0;
232 }
233 if (node instanceof Document) {
234 return ((Document) node).getContent().size() == 0;
235 }
236 return true;
237 }
238
239 public QName getName() {
240 String ns = null;
241 String ln = null;
242 if (node instanceof Element) {
243 ns = ((Element) node).getNamespacePrefix();
244 if (ns != null && ns.equals("")) {
245 ns = null;
246 }
247 ln = ((Element) node).getName();
248 }
249 else if (node instanceof ProcessingInstruction) {
250 ln = ((ProcessingInstruction) node).getTarget();
251 }
252 return new QName(ns, ln);
253 }
254
255 public Object getImmediateNode() {
256 return node;
257 }
258
259 public Object getValue() {
260 if (node instanceof Element) {
261 StringBuffer buf = new StringBuffer();
262 for (NodeIterator children = childIterator(null, false, null); children.setPosition(children.getPosition() + 1);) {
263 NodePointer ptr = children.getNodePointer();
264 if (ptr.getImmediateNode() instanceof Element || ptr.getImmediateNode() instanceof Text) {
265 buf.append(ptr.getValue());
266 }
267 }
268 return buf.toString();
269 }
270 if (node instanceof Comment) {
271 String text = ((Comment) node).getText();
272 if (text != null) {
273 text = text.trim();
274 }
275 return text;
276 }
277 String result = null;
278 if (node instanceof Text) {
279 result = ((Text) node).getText();
280 }
281 if (node instanceof ProcessingInstruction) {
282 result = ((ProcessingInstruction) node).getData();
283 }
284 boolean trim = !"preserve".equals(findEnclosingAttribute(node, "space", Namespace.XML_NAMESPACE));
285 return result != null && trim ? result.trim() : result;
286 }
287
288 public void setValue(Object value) {
289 if (node instanceof Text) {
290 String string = (String) TypeUtils.convert(value, String.class);
291 if (string != null && !string.equals("")) {
292 ((Text) node).setText(string);
293 }
294 else {
295 nodeParent(node).removeContent((Text) node);
296 }
297 }
298 else {
299 Element element = (Element) node;
300 element.getContent().clear();
301
302 if (value instanceof Element) {
303 Element valueElement = (Element) value;
304 addContent(valueElement.getContent());
305 }
306 else if (value instanceof Document) {
307 Document valueDocument = (Document) value;
308 addContent(valueDocument.getContent());
309 }
310 else if (value instanceof Text || value instanceof CDATA) {
311 String string = ((Text) value).getText();
312 element.addContent(new Text(string));
313 }
314 else if (value instanceof ProcessingInstruction) {
315 ProcessingInstruction pi =
316 (ProcessingInstruction) ((ProcessingInstruction) value)
317 .clone();
318 element.addContent(pi);
319 }
320 else if (value instanceof Comment) {
321 Comment comment = (Comment) ((Comment) value).clone();
322 element.addContent(comment);
323 }
324 else {
325 String string = (String) TypeUtils.convert(value, String.class);
326 if (string != null && !string.equals("")) {
327 element.addContent(new Text(string));
328 }
329 }
330 }
331 }
332
333 /**
334 * Add the specified content to this element.
335 * @param content List
336 */
337 private void addContent(List content) {
338 Element element = (Element) node;
339 int count = content.size();
340
341 for (int i = 0; i < count; i++) {
342 Object child = content.get(i);
343 if (child instanceof Element) {
344 child = ((Element) child).clone();
345 element.addContent((Element) child);
346 }
347 else if (child instanceof Text) {
348 child = ((Text) child).clone();
349 element.addContent((Text) child);
350 }
351 else if (node instanceof CDATA) {
352 child = ((CDATA) child).clone();
353 element.addContent((CDATA) child);
354 }
355 else if (node instanceof ProcessingInstruction) {
356 child = ((ProcessingInstruction) child).clone();
357 element.addContent((ProcessingInstruction) child);
358 }
359 else if (node instanceof Comment) {
360 child = ((Comment) child).clone();
361 element.addContent((Comment) child);
362 }
363 }
364 }
365
366 public boolean testNode(NodeTest test) {
367 return testNode(this, node, test);
368 }
369
370 /**
371 * Execute test against node on behalf of pointer.
372 * @param pointer Pointer
373 * @param node to test
374 * @param test to execute
375 * @return true if node passes test
376 */
377 public static boolean testNode(
378 NodePointer pointer,
379 Object node,
380 NodeTest test) {
381 if (test == null) {
382 return true;
383 }
384 if (test instanceof NodeNameTest) {
385 if (!(node instanceof Element)) {
386 return false;
387 }
388
389 NodeNameTest nodeNameTest = (NodeNameTest) test;
390 QName testName = nodeNameTest.getNodeName();
391 String namespaceURI = nodeNameTest.getNamespaceURI();
392 boolean wildcard = nodeNameTest.isWildcard();
393 String testPrefix = testName.getPrefix();
394 if (wildcard && testPrefix == null) {
395 return true;
396 }
397 if (wildcard
398 || testName.getName()
399 .equals(JDOMNodePointer.getLocalName(node))) {
400 String nodeNS = JDOMNodePointer.getNamespaceURI(node);
401 return equalStrings(namespaceURI, nodeNS) || nodeNS == null
402 && equalStrings(testPrefix, getPrefix(node));
403 }
404 return false;
405 }
406 if (test instanceof NodeTypeTest) {
407 switch (((NodeTypeTest) test).getNodeType()) {
408 case Compiler.NODE_TYPE_NODE :
409 return true;
410 case Compiler.NODE_TYPE_TEXT :
411 return (node instanceof Text) || (node instanceof CDATA);
412 case Compiler.NODE_TYPE_COMMENT :
413 return node instanceof Comment;
414 case Compiler.NODE_TYPE_PI :
415 return node instanceof ProcessingInstruction;
416 default:
417 return false;
418 }
419 }
420 if (test instanceof ProcessingInstructionTest && node instanceof ProcessingInstruction) {
421 String testPI = ((ProcessingInstructionTest) test).getTarget();
422 String nodePI = ((ProcessingInstruction) node).getTarget();
423 return testPI.equals(nodePI);
424 }
425 return false;
426 }
427
428 /**
429 * Learn whether two strings are == or .equals()
430 * @param s1 string 1
431 * @param s2 string 2
432 * @return true if equal
433 */
434 private static boolean equalStrings(String s1, String s2) {
435 if (s1 == s2) {
436 return true;
437 }
438 s1 = s1 == null ? "" : s1.trim();
439 s2 = s2 == null ? "" : s2.trim();
440 return s1.equals(s2);
441 }
442
443 /**
444 * Get the prefix from a given node.
445 * @param node to check
446 * @return String
447 */
448 public static String getPrefix(Object node) {
449 if (node instanceof Element) {
450 String prefix = ((Element) node).getNamespacePrefix();
451 return (prefix == null || prefix.equals("")) ? null : prefix;
452 }
453 if (node instanceof Attribute) {
454 String prefix = ((Attribute) node).getNamespacePrefix();
455 return (prefix == null || prefix.equals("")) ? null : prefix;
456 }
457 return null;
458 }
459
460 /**
461 * Get the local name of the specified node.
462 * @param node to check
463 * @return String local name
464 */
465 public static String getLocalName(Object node) {
466 if (node instanceof Element) {
467 return ((Element) node).getName();
468 }
469 if (node instanceof Attribute) {
470 return ((Attribute) node).getName();
471 }
472 return null;
473 }
474
475 /**
476 * Returns true if the xml:lang attribute for the current node
477 * or its parent has the specified prefix <i>lang</i>.
478 * If no node has this prefix, calls <code>super.isLanguage(lang)</code>.
479 * @param lang to compare
480 * @return true if this element uses the specified language.
481 */
482 public boolean isLanguage(String lang) {
483 String current = getLanguage();
484 return current == null ? super.isLanguage(lang) : current.toUpperCase(
485 Locale.ENGLISH).startsWith(lang.toUpperCase(Locale.ENGLISH));
486 }
487
488 /**
489 * Get the language of this element.
490 * @return String language
491 */
492 protected String getLanguage() {
493 return findEnclosingAttribute(node, "lang", Namespace.XML_NAMESPACE);
494 }
495
496 /**
497 * Find the nearest occurrence of the specified attribute
498 * on the specified and enclosing elements.
499 * @param n current node
500 * @param attrName attribute name
501 * @param ns Namespace
502 * @return attribute value
503 */
504 protected static String findEnclosingAttribute(Object n, String attrName, Namespace ns) {
505 while (n != null) {
506 if (n instanceof Element) {
507 Element e = (Element) n;
508 String attr = e.getAttributeValue(attrName, ns);
509 if (attr != null && !attr.equals("")) {
510 return attr;
511 }
512 }
513 n = nodeParent(n);
514 }
515 return null;
516 }
517
518 /**
519 * Get the parent of the specified node.
520 * @param node to check
521 * @return parent Element
522 */
523 private static Element nodeParent(Object node) {
524 if (node instanceof Element) {
525 Object parent = ((Element) node).getParent();
526 return parent instanceof Element ? (Element) parent : null;
527 }
528 if (node instanceof Text) {
529 return (Element) ((Text) node).getParent();
530 }
531 if (node instanceof CDATA) {
532 return (Element) ((CDATA) node).getParent();
533 }
534 if (node instanceof ProcessingInstruction) {
535 return (Element) ((ProcessingInstruction) node).getParent();
536 }
537 if (node instanceof Comment) {
538 return (Element) ((Comment) node).getParent();
539 }
540 return null;
541 }
542
543 public NodePointer createChild(
544 JXPathContext context,
545 QName name,
546 int index) {
547 if (index == WHOLE_COLLECTION) {
548 index = 0;
549 }
550 boolean success =
551 getAbstractFactory(context).createObject(
552 context,
553 this,
554 node,
555 name.toString(),
556 index);
557 if (success) {
558 NodeTest nodeTest;
559 String prefix = name.getPrefix();
560 String namespaceURI = prefix == null ? null : context
561 .getNamespaceURI(prefix);
562 nodeTest = new NodeNameTest(name, namespaceURI);
563
564 NodeIterator it =
565 childIterator(nodeTest, false, null);
566 if (it != null && it.setPosition(index + 1)) {
567 return it.getNodePointer();
568 }
569 }
570 throw new JXPathAbstractFactoryException("Factory could not create "
571 + "a child node for path: " + asPath() + "/" + name + "["
572 + (index + 1) + "]");
573 }
574
575 public NodePointer createChild(
576 JXPathContext context, QName name, int index, Object value) {
577 NodePointer ptr = createChild(context, name, index);
578 ptr.setValue(value);
579 return ptr;
580 }
581
582 public NodePointer createAttribute(JXPathContext context, QName name) {
583 if (!(node instanceof Element)) {
584 return super.createAttribute(context, name);
585 }
586
587 Element element = (Element) node;
588 String prefix = name.getPrefix();
589 if (prefix != null) {
590 String namespaceUri = getNamespaceResolver().getNamespaceURI(prefix);
591 if (namespaceUri == null) {
592 throw new JXPathException(
593 "Unknown namespace prefix: " + prefix);
594 }
595 Namespace ns = Namespace.getNamespace(prefix, namespaceUri);
596 Attribute attr = element.getAttribute(name.getName(), ns);
597 if (attr == null) {
598 element.setAttribute(name.getName(), "", ns);
599 }
600 }
601 else {
602 Attribute attr = element.getAttribute(name.getName());
603 if (attr == null) {
604 element.setAttribute(name.getName(), "");
605 }
606 }
607 NodeIterator it = attributeIterator(name);
608 it.setPosition(1);
609 return it.getNodePointer();
610 }
611
612 public void remove() {
613 Element parent = nodeParent(node);
614 if (parent == null) {
615 throw new JXPathException("Cannot remove root JDOM node");
616 }
617 parent.getContent().remove(node);
618 }
619
620 public String asPath() {
621 if (id != null) {
622 return "id('" + escape(id) + "')";
623 }
624
625 StringBuffer buffer = new StringBuffer();
626 if (parent != null) {
627 buffer.append(parent.asPath());
628 }
629 if (node instanceof Element) {
630 // If the parent pointer is not a JDOMNodePointer, it is
631 // the parent's responsibility to produce the node test part
632 // of the path
633 if (parent instanceof JDOMNodePointer) {
634 if (buffer.length() == 0
635 || buffer.charAt(buffer.length() - 1) != '/') {
636 buffer.append('/');
637 }
638 String nsURI = getNamespaceURI();
639 String ln = JDOMNodePointer.getLocalName(node);
640
641 if (nsURI == null) {
642 buffer.append(ln);
643 buffer.append('[');
644 buffer.append(getRelativePositionByName()).append(']');
645 }
646 else {
647 String prefix = getNamespaceResolver().getPrefix(nsURI);
648 if (prefix != null) {
649 buffer.append(prefix);
650 buffer.append(':');
651 buffer.append(ln);
652 buffer.append('[');
653 buffer.append(getRelativePositionByName());
654 buffer.append(']');
655 }
656 else {
657 buffer.append("node()");
658 buffer.append('[');
659 buffer.append(getRelativePositionOfElement());
660 buffer.append(']');
661 }
662 }
663
664 }
665 }
666 else if (node instanceof Text || node instanceof CDATA) {
667 buffer.append("/text()");
668 buffer.append('[').append(getRelativePositionOfTextNode()).append(
669 ']');
670 }
671 else if (node instanceof ProcessingInstruction) {
672 buffer.append("/processing-instruction(\'").append(((ProcessingInstruction) node).getTarget()).append(
673 "')");
674 buffer.append('[').append(getRelativePositionOfPI()).append(
675 ']');
676 }
677 return buffer.toString();
678 }
679
680 /**
681 * Get relative position of this among like-named siblings.
682 * @return 1..n
683 */
684 private int getRelativePositionByName() {
685 if (node instanceof Element) {
686 Object parent = ((Element) node).getParent();
687 if (!(parent instanceof Element)) {
688 return 1;
689 }
690
691 List children = ((Element) parent).getContent();
692 int count = 0;
693 String name = ((Element) node).getQualifiedName();
694 for (int i = 0; i < children.size(); i++) {
695 Object child = children.get(i);
696 if ((child instanceof Element)
697 && ((Element) child).getQualifiedName().equals(name)) {
698 count++;
699 }
700 if (child == node) {
701 break;
702 }
703 }
704 return count;
705 }
706 return 1;
707 }
708
709 /**
710 * Get relative position of this among all siblings.
711 * @return 1..n
712 */
713 private int getRelativePositionOfElement() {
714 Object parent = ((Element) node).getParent();
715 if (parent == null) {
716 return 1;
717 }
718 List children;
719 if (parent instanceof Element) {
720 children = ((Element) parent).getContent();
721 }
722 else {
723 children = ((Document) parent).getContent();
724 }
725 int count = 0;
726 for (int i = 0; i < children.size(); i++) {
727 Object child = children.get(i);
728 if (child instanceof Element) {
729 count++;
730 }
731 if (child == node) {
732 break;
733 }
734 }
735 return count;
736 }
737
738 /**
739 * Get the relative position of this among sibling text nodes.
740 * @return 1..n
741 */
742 private int getRelativePositionOfTextNode() {
743 Element parent;
744 if (node instanceof Text) {
745 parent = (Element) ((Text) node).getParent();
746 }
747 else {
748 parent = (Element) ((CDATA) node).getParent();
749 }
750 if (parent == null) {
751 return 1;
752 }
753 List children = parent.getContent();
754 int count = 0;
755 for (int i = 0; i < children.size(); i++) {
756 Object child = children.get(i);
757 if (child instanceof Text || child instanceof CDATA) {
758 count++;
759 }
760 if (child == node) {
761 break;
762 }
763 }
764 return count;
765 }
766
767 /**
768 * Get the relative position of this among same-target processing instruction siblings.
769 * @return 1..n
770 */
771 private int getRelativePositionOfPI() {
772 String target = ((ProcessingInstruction) node).getTarget();
773 Element parent = (Element) ((ProcessingInstruction) node).getParent();
774 if (parent == null) {
775 return 1;
776 }
777 List children = parent.getContent();
778 int count = 0;
779 for (int i = 0; i < children.size(); i++) {
780 Object child = children.get(i);
781 if (child instanceof ProcessingInstruction
782 && (target == null
783 || target.equals(
784 ((ProcessingInstruction) child).getTarget()))) {
785 count++;
786 }
787 if (child == node) {
788 break;
789 }
790 }
791 return count;
792 }
793
794 public int hashCode() {
795 return node.hashCode();
796 }
797
798 public boolean equals(Object object) {
799 if (object == this) {
800 return true;
801 }
802
803 if (!(object instanceof JDOMNodePointer)) {
804 return false;
805 }
806
807 JDOMNodePointer other = (JDOMNodePointer) object;
808 return node == other.node;
809 }
810
811 }