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