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