View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jxpath.ri.model.jdom;
18  
19  import java.util.List;
20  import java.util.Locale;
21  
22  import org.apache.commons.jxpath.JXPathAbstractFactoryException;
23  import org.apache.commons.jxpath.JXPathContext;
24  import org.apache.commons.jxpath.JXPathException;
25  import org.apache.commons.jxpath.ri.Compiler;
26  import org.apache.commons.jxpath.ri.NamespaceResolver;
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.NodeIterator;
33  import org.apache.commons.jxpath.ri.model.NodePointer;
34  import org.apache.commons.jxpath.util.TypeUtils;
35  import org.jdom.Attribute;
36  import org.jdom.CDATA;
37  import org.jdom.Comment;
38  import org.jdom.Document;
39  import org.jdom.Element;
40  import org.jdom.Namespace;
41  import org.jdom.ProcessingInstruction;
42  import org.jdom.Text;
43  
44  /**
45   * A Pointer that points to a DOM node.
46   *
47   * @author Dmitri Plotnikov
48   * @version $Revision: 1234032 $ $Date: 2012-01-20 12:53:31 -0500 (Fri, 20 Jan 2012) $
49   */
50  public class JDOMNodePointer extends NodePointer {
51      private static final long serialVersionUID = -6346532297491082651L;
52  
53      private Object node;
54      private String id;
55      private NamespaceResolver localNamespaceResolver;
56  
57      /** XML ns uri */
58      public static final String XML_NAMESPACE_URI =
59              "http://www.w3.org/XML/1998/namespace";
60  
61      /** XMLNS ns uri */
62      public static final String XMLNS_NAMESPACE_URI =
63              "http://www.w3.org/2000/xmlns/";
64  
65      /**
66       * Create a new JDOMNodePointer.
67       * @param node pointed
68       * @param locale Locale
69       */
70      public JDOMNodePointer(Object node, Locale locale) {
71          super(null, locale);
72          this.node = node;
73      }
74  
75      /**
76       * Create a new JDOMNodePointer.
77       * @param node pointed
78       * @param locale Locale
79       * @param id String id
80       */
81      public JDOMNodePointer(Object node, Locale locale, String id) {
82          super(null, locale);
83          this.node = node;
84          this.id = id;
85      }
86  
87      /**
88       * Create a new JDOMNodePointer.
89       * @param parent NodePointer
90       * @param node pointed
91       */
92      public JDOMNodePointer(NodePointer parent, Object node) {
93          super(parent);
94          this.node = node;
95      }
96  
97      public NodeIterator childIterator(
98          NodeTest test,
99          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 ("".equals(ns)) {
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(getRelativePositionByQName()).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(getRelativePositionByQName());
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 getRelativePositionByQName() {
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             for (int i = 0; i < children.size(); i++) {
694                 Object child = children.get(i);
695                 if (child instanceof Element && matchesQName(((Element) child))) {
696                     count++;
697                 }
698                 if (child == node) {
699                     break;
700                 }
701             }
702             return count;
703         }
704         return 1;
705     }
706 
707     private boolean matchesQName(Element element) {
708         if (getNamespaceURI() != null) {
709             String ns = getNamespaceURI(element);
710             if (ns == null || !ns.equals(getNamespaceURI())) {
711                 return false;
712             }
713         }
714         return element.getName().equals(((Element) node).getName());
715     }
716 
717     /**
718      * Get relative position of this among all siblings.
719      * @return 1..n
720      */
721     private int getRelativePositionOfElement() {
722         Object parent = ((Element) node).getParent();
723         if (parent == null) {
724             return 1;
725         }
726         List children;
727         if (parent instanceof Element) {
728             children = ((Element) parent).getContent();
729         }
730         else {
731             children = ((Document) parent).getContent();
732         }
733         int count = 0;
734         for (int i = 0; i < children.size(); i++) {
735             Object child = children.get(i);
736             if (child instanceof Element) {
737                 count++;
738             }
739             if (child == node) {
740                 break;
741             }
742         }
743         return count;
744     }
745 
746     /**
747      * Get the relative position of this among sibling text nodes.
748      * @return 1..n
749      */
750     private int getRelativePositionOfTextNode() {
751         Element parent;
752         if (node instanceof Text) {
753             parent = (Element) ((Text) node).getParent();
754         }
755         else {
756             parent = (Element) ((CDATA) node).getParent();
757         }
758         if (parent == null) {
759             return 1;
760         }
761         List children = parent.getContent();
762         int count = 0;
763         for (int i = 0; i < children.size(); i++) {
764             Object child = children.get(i);
765             if (child instanceof Text || child instanceof CDATA) {
766                 count++;
767             }
768             if (child == node) {
769                 break;
770             }
771         }
772         return count;
773     }
774 
775     /**
776      * Get the relative position of this among same-target processing instruction siblings.
777      * @return 1..n
778      */
779     private int getRelativePositionOfPI() {
780         String target = ((ProcessingInstruction) node).getTarget();
781         Element parent = (Element) ((ProcessingInstruction) node).getParent();
782         if (parent == null) {
783             return 1;
784         }
785         List children = parent.getContent();
786         int count = 0;
787         for (int i = 0; i < children.size(); i++) {
788             Object child = children.get(i);
789             if (child instanceof ProcessingInstruction
790                 && (target == null
791                     || target.equals(
792                         ((ProcessingInstruction) child).getTarget()))) {
793                 count++;
794             }
795             if (child == node) {
796                 break;
797             }
798         }
799         return count;
800     }
801 
802     public int hashCode() {
803         return node.hashCode();
804     }
805 
806     public boolean equals(Object object) {
807         if (object == this) {
808             return true;
809         }
810 
811         if (!(object instanceof JDOMNodePointer)) {
812             return false;
813         }
814 
815         JDOMNodePointer other = (JDOMNodePointer) object;
816         return node == other.node;
817     }
818 
819 }