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