View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jxpath.ri.model.dom;
18  
19  import java.util.HashMap;
20  import java.util.Locale;
21  import java.util.Map;
22  
23  import org.apache.commons.jxpath.JXPathAbstractFactoryException;
24  import org.apache.commons.jxpath.JXPathContext;
25  import org.apache.commons.jxpath.JXPathException;
26  import org.apache.commons.jxpath.Pointer;
27  import org.apache.commons.jxpath.ri.Compiler;
28  import org.apache.commons.jxpath.ri.NamespaceResolver;
29  import org.apache.commons.jxpath.ri.QName;
30  import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
31  import org.apache.commons.jxpath.ri.compiler.NodeTest;
32  import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
33  import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
34  import org.apache.commons.jxpath.ri.model.NodeIterator;
35  import org.apache.commons.jxpath.ri.model.NodePointer;
36  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
37  import org.apache.commons.jxpath.util.TypeUtils;
38  import org.w3c.dom.Attr;
39  import org.w3c.dom.Comment;
40  import org.w3c.dom.Document;
41  import org.w3c.dom.Element;
42  import org.w3c.dom.NamedNodeMap;
43  import org.w3c.dom.Node;
44  import org.w3c.dom.NodeList;
45  import org.w3c.dom.ProcessingInstruction;
46  
47  /**
48   * A Pointer that points to a DOM node. Because a DOM Node is not guaranteed Serializable,
49   * a DOMNodePointer instance may likewise not be properly Serializable.
50   *
51   * @author Dmitri Plotnikov
52   * @version $Revision: 1234255 $ $Date: 2012-01-21 04:11:46 +0100 (Sa, 21 Jan 2012) $
53   */
54  public class DOMNodePointer extends NodePointer {
55  
56      private static final long serialVersionUID = -8751046933894857319L;
57  
58      private Node node;
59      private Map namespaces;
60      private String defaultNamespace;
61      private String id;
62      private NamespaceResolver localNamespaceResolver;
63  
64      /** XML namespace URI */
65      public static final String XML_NAMESPACE_URI =
66              "http://www.w3.org/XML/1998/namespace";
67  
68      /** XMLNS namespace URI */
69      public static final String XMLNS_NAMESPACE_URI =
70              "http://www.w3.org/2000/xmlns/";
71  
72      /**
73       * Create a new DOMNodePointer.
74       * @param node pointed at
75       * @param locale Locale
76       */
77      public DOMNodePointer(Node node, Locale locale) {
78          super(null, locale);
79          this.node = node;
80      }
81  
82      /**
83       * Create a new DOMNodePointer.
84       * @param node pointed at
85       * @param locale Locale
86       * @param id string id
87       */
88      public DOMNodePointer(Node node, Locale locale, String id) {
89          super(null, locale);
90          this.node = node;
91          this.id = id;
92      }
93  
94      /**
95       * Create a new DOMNodePointer.
96       * @param parent pointer
97       * @param node pointed
98       */
99      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 }