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    }