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}