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