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 */
017package org.apache.commons.jxpath.ri.model.dom;
018
019import java.util.HashMap;
020import java.util.Locale;
021import java.util.Map;
022
023import org.apache.commons.jxpath.JXPathAbstractFactoryException;
024import org.apache.commons.jxpath.JXPathContext;
025import org.apache.commons.jxpath.JXPathException;
026import org.apache.commons.jxpath.Pointer;
027import org.apache.commons.jxpath.ri.Compiler;
028import org.apache.commons.jxpath.ri.NamespaceResolver;
029import org.apache.commons.jxpath.ri.QName;
030import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
031import org.apache.commons.jxpath.ri.compiler.NodeTest;
032import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
033import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
034import org.apache.commons.jxpath.ri.model.NodeIterator;
035import org.apache.commons.jxpath.ri.model.NodePointer;
036import org.apache.commons.jxpath.ri.model.beans.NullPointer;
037import org.apache.commons.jxpath.util.TypeUtils;
038import org.w3c.dom.Attr;
039import org.w3c.dom.Comment;
040import org.w3c.dom.Document;
041import org.w3c.dom.Element;
042import org.w3c.dom.NamedNodeMap;
043import org.w3c.dom.Node;
044import org.w3c.dom.NodeList;
045import 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: 1234255 $ $Date: 2012-01-21 04:11:46 +0100 (Sa, 21 Jan 2012) $
053 */
054public 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(getRelativePositionByQName()).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(getRelativePositionByQName());
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 getRelativePositionByQName() {
557        int count = 1;
558        Node n = node.getPreviousSibling();
559        while (n != null) {
560            if (n.getNodeType() == Node.ELEMENT_NODE && matchesQName(n)) {
561                count++;
562            }
563            n = n.getPreviousSibling();
564        }
565        return count;
566    }
567
568    private boolean matchesQName(Node n) {
569        if (getNamespaceURI() != null) {
570            return equalStrings(getNamespaceURI(n), getNamespaceURI())
571                    && equalStrings(node.getLocalName(), n.getLocalName());
572        }
573        return equalStrings(node.getNodeName(), n.getNodeName());
574    }
575
576    /**
577     * Get relative position of this among all siblings.
578     * @return 1..n
579     */
580    private int getRelativePositionOfElement() {
581        int count = 1;
582        Node n = node.getPreviousSibling();
583        while (n != null) {
584            if (n.getNodeType() == Node.ELEMENT_NODE) {
585                count++;
586            }
587            n = n.getPreviousSibling();
588        }
589        return count;
590    }
591
592    /**
593     * Get the relative position of this among sibling text nodes.
594     * @return 1..n
595     */
596    private int getRelativePositionOfTextNode() {
597        int count = 1;
598        Node n = node.getPreviousSibling();
599        while (n != null) {
600            if (n.getNodeType() == Node.TEXT_NODE
601                || n.getNodeType() == Node.CDATA_SECTION_NODE) {
602                count++;
603            }
604            n = n.getPreviousSibling();
605        }
606        return count;
607    }
608
609    /**
610     * Get the relative position of this among same-target processing instruction siblings.
611     * @return 1..n
612     */
613    private int getRelativePositionOfPI() {
614        int count = 1;
615        String target = ((ProcessingInstruction) node).getTarget();
616        Node n = node.getPreviousSibling();
617        while (n != null) {
618            if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
619                && ((ProcessingInstruction) n).getTarget().equals(target)) {
620                count++;
621            }
622            n = n.getPreviousSibling();
623        }
624        return count;
625    }
626
627    public int hashCode() {
628        return node.hashCode();
629    }
630
631    public boolean equals(Object object) {
632        return object == this || object instanceof DOMNodePointer && node == ((DOMNodePointer) object).node;
633    }
634
635    /**
636     * Get any prefix from the specified node.
637     * @param node the node to check
638     * @return String xml prefix
639     */
640    public static String getPrefix(Node node) {
641        String prefix = node.getPrefix();
642        if (prefix != null) {
643            return prefix;
644        }
645
646        String name = node.getNodeName();
647        int index = name.lastIndexOf(':');
648        return index < 0 ? null : name.substring(0, index);
649    }
650
651    /**
652     * Get the local name of the specified node.
653     * @param node node to check
654     * @return String local name
655     */
656    public static String getLocalName(Node node) {
657        String localName = node.getLocalName();
658        if (localName != null) {
659            return localName;
660        }
661
662        String name = node.getNodeName();
663        int index = name.lastIndexOf(':');
664        return index < 0 ? name : name.substring(index + 1);
665    }
666
667    /**
668     * Get the ns uri of the specified node.
669     * @param node Node to check
670     * @return String ns uri
671     */
672    public static String getNamespaceURI(Node node) {
673        if (node instanceof Document) {
674            node = ((Document) node).getDocumentElement();
675        }
676
677        Element element = (Element) node;
678
679        String uri = element.getNamespaceURI();
680        if (uri == null) {
681            String prefix = getPrefix(node);
682            String qname = prefix == null ? "xmlns" : "xmlns:" + prefix;
683
684            Node aNode = node;
685            while (aNode != null) {
686                if (aNode.getNodeType() == Node.ELEMENT_NODE) {
687                    Attr attr = ((Element) aNode).getAttributeNode(qname);
688                    if (attr != null) {
689                        uri = attr.getValue();
690                        break;
691                    }
692                }
693                aNode = aNode.getParentNode();
694            }
695        }
696        return "".equals(uri) ? null : uri;
697    }
698
699    public Object getValue() {
700        if (node.getNodeType() == Node.COMMENT_NODE) {
701            String text = ((Comment) node).getData();
702            return text == null ? "" : text.trim();
703        }
704        return stringValue(node);
705    }
706
707    /**
708     * Get the string value of the specified node.
709     * @param node Node to check
710     * @return String
711     */
712    private String stringValue(Node node) {
713        int nodeType = node.getNodeType();
714        if (nodeType == Node.COMMENT_NODE) {
715            return "";
716        }
717        boolean trim = !"preserve".equals(findEnclosingAttribute(node, "xml:space"));
718        if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
719            String text = node.getNodeValue();
720            return text == null ? "" : trim ? text.trim() : text;
721        }
722        if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
723            String text = ((ProcessingInstruction) node).getData();
724            return text == null ? "" : trim ? text.trim() : text;
725        }
726        NodeList list = node.getChildNodes();
727        StringBuffer buf = new StringBuffer();
728        for (int i = 0; i < list.getLength(); i++) {
729            Node child = list.item(i);
730            buf.append(stringValue(child));
731        }
732        return buf.toString();
733    }
734
735    /**
736     * Locates a node by ID.
737     * @param context starting context
738     * @param id to find
739     * @return Pointer
740     */
741    public Pointer getPointerByID(JXPathContext context, String id) {
742        Document document = node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node
743                : node.getOwnerDocument();
744        Element element = document.getElementById(id);
745        return element == null ? (Pointer) new NullPointer(getLocale(), id)
746                : new DOMNodePointer(element, getLocale(), id);
747    }
748
749    public int compareChildNodePointers(NodePointer pointer1,
750            NodePointer pointer2) {
751        Node node1 = (Node) pointer1.getBaseValue();
752        Node node2 = (Node) pointer2.getBaseValue();
753        if (node1 == node2) {
754            return 0;
755        }
756
757        int t1 = node1.getNodeType();
758        int t2 = node2.getNodeType();
759        if (t1 == Node.ATTRIBUTE_NODE && t2 != Node.ATTRIBUTE_NODE) {
760            return -1;
761        }
762        if (t1 != Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
763            return 1;
764        }
765        if (t1 == Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
766            NamedNodeMap map = ((Node) getNode()).getAttributes();
767            int length = map.getLength();
768            for (int i = 0; i < length; i++) {
769                Node n = map.item(i);
770                if (n == node1) {
771                    return -1;
772                }
773                if (n == node2) {
774                    return 1;
775                }
776            }
777            return 0; // Should not happen
778        }
779
780        Node current = node.getFirstChild();
781        while (current != null) {
782            if (current == node1) {
783                return -1;
784            }
785            if (current == node2) {
786                return 1;
787            }
788            current = current.getNextSibling();
789        }
790        return 0;
791    }
792}