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    }