001/* 002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 003 * 004 * This software is open source. 005 * See the bottom of this file for the licence. 006 * 007 * $Id: AbstractElement.java,v 1.63 2002/02/01 13:04:32 jstrachan Exp $ 008 */ 009 010package org.dom4j.tree; 011 012import java.io.IOException; 013import java.io.StringWriter; 014import java.io.Writer; 015import java.util.ArrayList; 016import java.util.Collections; 017import java.util.HashMap; 018import java.util.Iterator; 019import java.util.LinkedList; 020import java.util.List; 021import java.util.Map; 022import java.util.StringTokenizer; 023 024import org.dom4j.Attribute; 025import org.dom4j.CDATA; 026import org.dom4j.CharacterData; 027import org.dom4j.Comment; 028import org.dom4j.DocumentFactory; 029import org.dom4j.Document; 030import org.dom4j.Element; 031import org.dom4j.Entity; 032import org.dom4j.IllegalAddException; 033import org.dom4j.Node; 034import org.dom4j.Namespace; 035import org.dom4j.ProcessingInstruction; 036import org.dom4j.QName; 037import org.dom4j.Text; 038import org.dom4j.Visitor; 039import org.dom4j.io.XMLWriter; 040 041import org.xml.sax.Attributes; 042 043/** <p><code>AbstractElement</code> is an abstract base class for 044 * tree implementors to use for implementation inheritence.</p> 045 * 046 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 047 * @version $Revision: 1.63 $ 048 */ 049public abstract class AbstractElement extends AbstractBranch implements Element { 050 051 /** The <code>DocumentFactory</code> instance used by default */ 052 private static final DocumentFactory DOCUMENT_FACTORY = DocumentFactory.getInstance(); 053 054 protected static final List EMPTY_LIST = Collections.EMPTY_LIST; 055 protected static final Iterator EMPTY_ITERATOR = EMPTY_LIST.iterator(); 056 057 058 protected static final boolean VERBOSE_TOSTRING = false; 059 protected static final boolean USE_STRINGVALUE_SEPARATOR = false; 060 061 062 public AbstractElement() { 063 } 064 065 public short getNodeType() { 066 return ELEMENT_NODE; 067 } 068 069 public boolean isRootElement() { 070 Document document = getDocument(); 071 if ( document != null ) { 072 Element root = document.getRootElement(); 073 if ( root == this ) { 074 return true; 075 } 076 } 077 return false; 078 } 079 080 public void setName(String name) { 081 setQName( getDocumentFactory().createQName( name ) ); 082 } 083 084 public void setNamespace(Namespace namespace) { 085 setQName( getDocumentFactory().createQName( getName(), namespace ) ); 086 } 087 088 /** Returns the XPath expression to match this Elements name 089 * which is getQualifiedName() if there is a namespace prefix defined or 090 * if no namespace is present then it is getName() or if a namespace is defined 091 * with no prefix then the expression is *[name()='X'] where X = getName(). 092 */ 093 public String getXPathNameStep() { 094 String uri = getNamespaceURI(); 095 if ( uri == null || uri.length() == 0 ) { 096 return getName(); 097 } 098 String prefix = getNamespacePrefix(); 099 if ( prefix == null || prefix.length() == 0 ) { 100 return "*[name()='" + getName() + "']"; 101 } 102 return getQualifiedName(); 103 } 104 105 public String getPath(Element context) { 106 Element parent = getParent(); 107 if ( parent == null ) { 108 return "/" + getXPathNameStep(); 109 } 110 else if ( parent == context ) { 111 return getXPathNameStep(); 112 } 113 return parent.getPath( context ) + "/" + getXPathNameStep(); 114 } 115 116 public String getUniquePath(Element context) { 117 Element parent = getParent(); 118 if ( parent == null ) { 119 return "/" + getXPathNameStep(); 120 } 121 StringBuffer buffer = new StringBuffer(); 122 if ( parent != context ) { 123 buffer.append( parent.getUniquePath(context) ); 124 buffer.append( "/" ); 125 } 126 buffer.append( getXPathNameStep() ); 127 List mySiblings = parent.elements( getQName() ); 128 if ( mySiblings.size() > 1 ) { 129 int idx = mySiblings.indexOf( this ); 130 if ( idx >= 0 ) { 131 buffer.append( "[" ); 132 buffer.append( Integer.toString( ++idx ) ); 133 buffer.append( "]" ); 134 } 135 136 } 137 return buffer.toString(); 138 } 139 140 public String asXML() { 141 try { 142 StringWriter out = new StringWriter(); 143 XMLWriter writer = new XMLWriter( out, outputFormat ); 144 writer.write(this); 145 return out.toString(); 146 } 147 catch (IOException e) { 148 throw new RuntimeException("Wierd IOException while generating textual representation: " + e.getMessage()); 149 } 150 } 151 152 public void write(Writer out) throws IOException { 153 XMLWriter writer = new XMLWriter( out, outputFormat ); 154 writer.write(this); 155 } 156 157 /** <p><code>accept</code> method is the <code>Visitor Pattern</code> method. 158 * </p> 159 * 160 * @param visitor <code>Visitor</code> is the visitor. 161 */ 162 public void accept(Visitor visitor) { 163 visitor.visit(this); 164 165 // visit attributes 166 for ( int i = 0, size = attributeCount(); i < size; i++ ) { 167 Attribute attribute = attribute(i); 168 visitor.visit(attribute); 169 } 170 171 // visit content 172 for ( int i = 0, size = nodeCount(); i < size; i++ ) { 173 Node node = node(i); 174 node.accept(visitor); 175 } 176 } 177 178 public String toString() { 179 String uri = getNamespaceURI(); 180 if ( uri != null && uri.length() > 0 ) { 181 if ( VERBOSE_TOSTRING ) { 182 return super.toString() + " [Element: <" + getQualifiedName() 183 + " uri: " + uri 184 + " attributes: " + attributeList() 185 + " content: " + contentList() + " />]"; 186 } 187 else { 188 return super.toString() + " [Element: <" + getQualifiedName() 189 + " uri: " + uri 190 + " attributes: " + attributeList() + "/>]"; 191 } 192 } 193 else { 194 if ( VERBOSE_TOSTRING ) { 195 return super.toString() + " [Element: <" + getQualifiedName() 196 + " attributes: " + attributeList() 197 + " content: " + contentList() + " />]"; 198 } 199 else { 200 return super.toString() + " [Element: <" + getQualifiedName() 201 + " attributes: " + attributeList() + "/>]"; 202 } 203 } 204 } 205 206 207 // QName methods 208 //------------------------------------------------------------------------- 209 210 public Namespace getNamespace() { 211 return getQName().getNamespace(); 212 } 213 214 public String getName() { 215 return getQName().getName(); 216 } 217 218 public String getNamespacePrefix() { 219 return getQName().getNamespacePrefix(); 220 } 221 222 public String getNamespaceURI() { 223 return getQName().getNamespaceURI(); 224 } 225 226 public String getQualifiedName() { 227 return getQName().getQualifiedName(); 228 } 229 230 231 public Object getData() { 232 return getText(); 233 } 234 235 public void setData(Object data) { 236 // ignore this method 237 } 238 239 240 241 242 243 244 // Node methods 245 //------------------------------------------------------------------------- 246 public Node node(int index) { 247 if ( index >= 0 ) { 248 List list = contentList(); 249 if ( index >= list.size() ) { 250 return null; 251 } 252 Object node = list.get(index); 253 if (node != null) { 254 if (node instanceof Node) { 255 return (Node) node; 256 } 257 else { 258 return getDocumentFactory().createText(node.toString()); 259 } 260 } 261 } 262 return null; 263 } 264 265 public int indexOf(Node node) { 266 return contentList().indexOf( node ); 267 } 268 269 public int nodeCount() { 270 return contentList().size(); 271 } 272 273 public Iterator nodeIterator() { 274 return contentList().iterator(); 275 } 276 277 278 279 280 // Element methods 281 //------------------------------------------------------------------------- 282 283 public Element element(String name) { 284 List list = contentList(); 285 int size = list.size(); 286 for ( int i = 0; i < size; i++ ) { 287 Object object = list.get(i); 288 if ( object instanceof Element ) { 289 Element element = (Element) object; 290 if ( name.equals( element.getName() ) ) { 291 return element; 292 } 293 } 294 } 295 return null; 296 } 297 298 public Element element(QName qName) { 299 List list = contentList(); 300 int size = list.size(); 301 for ( int i = 0; i < size; i++ ) { 302 Object object = list.get(i); 303 if ( object instanceof Element ) { 304 Element element = (Element) object; 305 if ( qName.equals( element.getQName() ) ) { 306 return element; 307 } 308 } 309 } 310 return null; 311 } 312 313 public Element element(String name, Namespace namespace) { 314 return element( getDocumentFactory().createQName( name, namespace ) ); 315 } 316 317 318 319 public List elements() { 320 List list = contentList(); 321 BackedList answer = createResultList(); 322 int size = list.size(); 323 for ( int i = 0; i < size; i++ ) { 324 Object object = list.get(i); 325 if ( object instanceof Element ) { 326 answer.addLocal( object ); 327 } 328 } 329 return answer; 330 } 331 332 public List elements(String name) { 333 List list = contentList(); 334 BackedList answer = createResultList(); 335 int size = list.size(); 336 for ( int i = 0; i < size; i++ ) { 337 Object object = list.get(i); 338 if ( object instanceof Element ) { 339 Element element = (Element) object; 340 if ( name.equals( element.getName() ) ) { 341 answer.addLocal( element ); 342 } 343 } 344 } 345 return answer; 346 } 347 348 public List elements(QName qName) { 349 List list = contentList(); 350 BackedList answer = createResultList(); 351 int size = list.size(); 352 for ( int i = 0; i < size; i++ ) { 353 Object object = list.get(i); 354 if ( object instanceof Element ) { 355 Element element = (Element) object; 356 if ( qName.equals( element.getQName() ) ) { 357 answer.addLocal( element ); 358 } 359 } 360 } 361 return answer; 362 } 363 364 public List elements(String name, Namespace namespace) { 365 return elements( getDocumentFactory().createQName(name, namespace ) ); 366 } 367 368 public Iterator elementIterator() { 369 List list = contentList(); 370 return new ElementIterator(list.iterator()); 371 } 372 373 public Iterator elementIterator(String name) { 374 List list = contentList(); 375 return new ElementNameIterator(list.iterator(), name); 376 } 377 378 public Iterator elementIterator(QName qName) { 379 List list = contentList(); 380 return new ElementQNameIterator(list.iterator(), qName); 381 } 382 383 public Iterator elementIterator(String name, Namespace namespace) { 384 return elementIterator( getDocumentFactory().createQName( name, namespace ) ); 385 } 386 387 388 389 390 391 392 // Attribute methods 393 //------------------------------------------------------------------------- 394 395 396 public List attributes() { 397 return new ContentListFacade(this, attributeList()); 398 } 399 400 401 public Iterator attributeIterator() { 402 return attributeList().iterator(); 403 } 404 405 public Attribute attribute(int index) { 406 return (Attribute) attributeList().get(index); 407 } 408 409 public int attributeCount() { 410 return attributeList().size(); 411 } 412 413 public Attribute attribute(String name) { 414 List list = attributeList(); 415 int size = list.size(); 416 for ( int i = 0; i < size; i++ ) { 417 Attribute attribute = (Attribute) list.get(i); 418 if ( name.equals( attribute.getName() ) ) { 419 return attribute; 420 } 421 } 422 return null; 423 } 424 425 public Attribute attribute(QName qName) { 426 List list = attributeList(); 427 int size = list.size(); 428 for ( int i = 0; i < size; i++ ) { 429 Attribute attribute = (Attribute) list.get(i); 430 if ( qName.equals( attribute.getQName() ) ) { 431 return attribute; 432 } 433 } 434 return null; 435 } 436 437 public Attribute attribute(String name, Namespace namespace) { 438 return attribute( getDocumentFactory().createQName( name, namespace ) ); 439 } 440 441 /** This method provides a more optimal way of setting all the attributes 442 * on an Element particularly for use in {@link org.dom4j.io.SAXReader}. 443 */ 444 public void setAttributes(Attributes attributes, NamespaceStack namespaceStack, boolean noNamespaceAttributes) { 445 // now lets add all attribute values 446 int size = attributes.getLength(); 447 if ( size > 0 ) { 448 DocumentFactory factory = getDocumentFactory(); 449 if ( size == 1 ) { 450 // allow lazy construction of the List of Attributes 451 String attributeQualifiedName = attributes.getQName(0); 452 if ( noNamespaceAttributes || ! attributeQualifiedName.startsWith( "xmlns" ) ) { 453 String attributeURI = attributes.getURI(0); 454 String attributeLocalName = attributes.getLocalName(0); 455 String attributeValue = attributes.getValue(0); 456 457 QName attributeQName = namespaceStack.getAttributeQName( 458 attributeURI, attributeLocalName, attributeQualifiedName 459 ); 460 add(factory.createAttribute(this, attributeQName, attributeValue)); 461 } 462 } 463 else { 464 List list = attributeList(size); 465 list.clear(); 466 for ( int i = 0; i < size; i++ ) { 467 // optimised to avoid the call to attribute(QName) to 468 // lookup an attribute for a given QName 469 String attributeQualifiedName = attributes.getQName(i); 470 if ( noNamespaceAttributes || ! attributeQualifiedName.startsWith( "xmlns" ) ) { 471 String attributeURI = attributes.getURI(i); 472 String attributeLocalName = attributes.getLocalName(i); 473 String attributeValue = attributes.getValue(i); 474 475 QName attributeQName = namespaceStack.getAttributeQName( 476 attributeURI, attributeLocalName, attributeQualifiedName 477 ); 478 Attribute attribute = factory.createAttribute( 479 this, attributeQName, attributeValue 480 ); 481 list.add(attribute); 482 childAdded(attribute); 483 } 484 } 485 } 486 } 487 } 488 489 public String attributeValue(String name) { 490 Attribute attrib = attribute(name); 491 if (attrib == null) { 492 return null; 493 } 494 else { 495 return attrib.getValue(); 496 } 497 } 498 499 public String attributeValue(QName qName) { 500 Attribute attrib = attribute(qName); 501 if (attrib == null) { 502 return null; 503 } 504 else { 505 return attrib.getValue(); 506 } 507 } 508 509 public String attributeValue(String name, String defaultValue) { 510 String answer = attributeValue(name); 511 return (answer != null) ? answer : defaultValue; 512 } 513 514 public String attributeValue(QName qName, String defaultValue) { 515 String answer = attributeValue(qName); 516 return (answer != null) ? answer : defaultValue; 517 } 518 519 public void setAttributeValue(String name, String value) { 520 addAttribute(name, value); 521 } 522 523 public void setAttributeValue(QName qName, String value) { 524 addAttribute(qName, value); 525 } 526 527 public void add(Attribute attribute) { 528 if (attribute.getParent() != null) { 529 String message = 530 "The Attribute already has an existing parent \"" 531 + attribute.getParent().getQualifiedName() + "\""; 532 533 throw new IllegalAddException( this, attribute, message ); 534 } 535 attributeList().add(attribute); 536 childAdded(attribute); 537 } 538 539 540 public boolean remove(Attribute attribute) { 541 List list = attributeList(); 542 boolean answer = list.remove(attribute); 543 if ( answer ) { 544 childRemoved(attribute); 545 } 546 else { 547 // we may have a copy of the attribute 548 Attribute copy = attribute( attribute.getQName() ); 549 if ( copy != null ) { 550 list.remove( copy ); 551 answer = true; 552 } 553 } 554 555 return answer; 556 } 557 558 559 560 // Processing instruction API 561 //------------------------------------------------------------------------- 562 563 public List processingInstructions() { 564 List list = contentList(); 565 BackedList answer = createResultList(); 566 int size = list.size(); 567 for ( int i = 0; i < size; i++ ) { 568 Object object = list.get(i); 569 if ( object instanceof ProcessingInstruction ) { 570 answer.addLocal( object ); 571 } 572 } 573 return answer; 574 } 575 576 public List processingInstructions(String target) { 577 List list = contentList(); 578 BackedList answer = createResultList(); 579 int size = list.size(); 580 for ( int i = 0; i < size; i++ ) { 581 Object object = list.get(i); 582 if ( object instanceof ProcessingInstruction ) { 583 ProcessingInstruction pi = (ProcessingInstruction) object; 584 if ( target.equals( pi.getName() ) ) { 585 answer.addLocal( pi ); 586 } 587 } 588 } 589 return answer; 590 } 591 592 public ProcessingInstruction processingInstruction(String target) { 593 List list = contentList(); 594 int size = list.size(); 595 for ( int i = 0; i < size; i++ ) { 596 Object object = list.get(i); 597 if ( object instanceof ProcessingInstruction ) { 598 ProcessingInstruction pi = (ProcessingInstruction) object; 599 if ( target.equals( pi.getName() ) ) { 600 return pi; 601 } 602 } 603 } 604 return null; 605 } 606 607 public boolean removeProcessingInstruction(String target) { 608 List list = contentList(); 609 for ( Iterator iter = list.iterator(); iter.hasNext(); ) { 610 Object object = iter.next(); 611 if ( object instanceof ProcessingInstruction ) { 612 ProcessingInstruction pi = (ProcessingInstruction) object; 613 if ( target.equals( pi.getName() ) ) { 614 iter.remove(); 615 return true; 616 } 617 } 618 } 619 return false; 620 } 621 622 623 624 625 // Content Model methods 626 //------------------------------------------------------------------------- 627 628 public Node getXPathResult(int index) { 629 Node answer = node(index); 630 if (answer != null && !answer.supportsParent()) { 631 return answer.asXPathResult(this); 632 } 633 return answer; 634 } 635 636 637 public Element addAttribute(String name, String value) { 638 Attribute attribute = attribute(name); 639 if (attribute == null ) { 640 add(getDocumentFactory().createAttribute(this, name, value)); 641 } 642 else if (attribute.isReadOnly()) { 643 remove(attribute); 644 add(getDocumentFactory().createAttribute(this, name, value)); 645 } 646 else { 647 attribute.setValue(value); 648 } 649 return this; 650 } 651 652 public Element addAttribute(QName qName, String value) { 653 Attribute attribute = attribute(qName); 654 if (attribute == null ) { 655 add(getDocumentFactory().createAttribute(this, qName, value)); 656 } 657 else if (attribute.isReadOnly()) { 658 remove(attribute); 659 add(getDocumentFactory().createAttribute(this, qName, value)); 660 } 661 else { 662 attribute.setValue(value); 663 } 664 return this; 665 } 666 667 public Element addCDATA(String cdata) { 668 CDATA node = getDocumentFactory().createCDATA(cdata); 669 addNewNode(node); 670 return this; 671 } 672 673 public Element addComment(String comment) { 674 Comment node = getDocumentFactory().createComment( comment ); 675 addNewNode( node ); 676 return this; 677 } 678 679 680 public Element addElement(String name) { 681 DocumentFactory factory = getDocumentFactory(); 682 int index = name.indexOf(":"); 683 String prefix = ""; 684 String localName = name; 685 Namespace namespace = null; 686 if (index > 0) { 687 prefix = name.substring(0, index); 688 localName = name.substring(index+1); 689 namespace = getNamespaceForPrefix( prefix ); 690 if ( namespace == null ) { 691 throw new IllegalAddException( 692 "No such namespace prefix: " + prefix 693 + " is in scope on: " + this 694 + " so cannot add element: " + name 695 ); 696 } 697 } 698 else { 699 namespace = getNamespaceForPrefix( "" ); 700 } 701 702 Element node; 703 if ( namespace != null ) { 704 QName qname = factory.createQName( localName, namespace ); 705 node = factory.createElement( qname ); 706 } 707 else { 708 node = factory.createElement( name ); 709 } 710 addNewNode( node ); 711 return node; 712 } 713 714 public Element addEntity(String name, String text) { 715 Entity node = getDocumentFactory().createEntity(name, text); 716 addNewNode(node); 717 return this; 718 } 719 720 public Element addNamespace(String prefix, String uri) { 721 Namespace node = getDocumentFactory().createNamespace(prefix, uri); 722 addNewNode(node); 723 return this; 724 } 725 726 public Element addProcessingInstruction(String target, String data) { 727 ProcessingInstruction node = getDocumentFactory().createProcessingInstruction( target, data ); 728 addNewNode( node ); 729 return this; 730 } 731 732 public Element addProcessingInstruction(String target, Map data) { 733 ProcessingInstruction node = getDocumentFactory().createProcessingInstruction( target, data ); 734 addNewNode( node ); 735 return this; 736 } 737 738 739 public Element addText(String text) { 740 Text node = getDocumentFactory().createText(text); 741 addNewNode(node); 742 return this; 743 } 744 745 746 // polymorphic node methods 747 public void add(Node node) { 748 switch ( node.getNodeType() ) { 749 case ELEMENT_NODE: 750 add((Element) node); 751 break; 752 case ATTRIBUTE_NODE: 753 add((Attribute) node); 754 break; 755 case TEXT_NODE: 756 add((Text) node); 757 break; 758 case CDATA_SECTION_NODE: 759 add((CDATA) node); 760 break; 761 case ENTITY_REFERENCE_NODE: 762 add((Entity) node); 763 break; 764 case PROCESSING_INSTRUCTION_NODE: 765 add((ProcessingInstruction) node); 766 break; 767 case COMMENT_NODE: 768 add((Comment) node); 769 break; 770/* XXXX: to do! 771 case DOCUMENT_TYPE_NODE: 772 add((DocumentType) node); 773 break; 774*/ 775 case NAMESPACE_NODE: 776 add((Namespace) node); 777 break; 778 default: 779 invalidNodeTypeAddException(node); 780 } 781 } 782 783 public boolean remove(Node node) { 784 switch ( node.getNodeType() ) { 785 case ELEMENT_NODE: 786 return remove((Element) node); 787 case ATTRIBUTE_NODE: 788 return remove((Attribute) node); 789 case TEXT_NODE: 790 return remove((Text) node); 791 case CDATA_SECTION_NODE: 792 return remove((CDATA) node); 793 case ENTITY_REFERENCE_NODE: 794 return remove((Entity) node); 795 case PROCESSING_INSTRUCTION_NODE: 796 return remove((ProcessingInstruction) node); 797 case COMMENT_NODE: 798 return remove((Comment) node); 799/* 800 case DOCUMENT_TYPE_NODE: 801 return remove((DocumentType) node); 802*/ 803 case NAMESPACE_NODE: 804 return remove((Namespace) node); 805 default: 806 return false; 807 } 808 } 809 810 // typesafe versions using node classes 811 public void add(CDATA cdata) { 812 addNode(cdata); 813 } 814 815 public void add(Comment comment) { 816 addNode(comment); 817 } 818 819 public void add(Element element) { 820 addNode(element); 821 } 822 823 public void add(Entity entity) { 824 addNode(entity); 825 } 826 827 public void add(Namespace namespace) { 828 addNode(namespace); 829 } 830 831 public void add(ProcessingInstruction pi) { 832 addNode(pi); 833 } 834 835 public void add(Text text) { 836 addNode(text); 837 } 838 839 840 public boolean remove(CDATA cdata) { 841 return removeNode(cdata); 842 } 843 844 public boolean remove(Comment comment) { 845 return removeNode(comment); 846 } 847 848 public boolean remove(Element element) { 849 return removeNode(element); 850 } 851 852 public boolean remove(Entity entity) { 853 return removeNode(entity); 854 } 855 856 public boolean remove(Namespace namespace) { 857 return removeNode(namespace); 858 } 859 860 public boolean remove(ProcessingInstruction pi) { 861 return removeNode(pi); 862 } 863 864 public boolean remove(Text text) { 865 return removeNode(text); 866 } 867 868 869 870 // Helper methods 871 //------------------------------------------------------------------------- 872 873 public boolean hasMixedContent() { 874 List content = contentList(); 875 if (content == null || content.isEmpty() || content.size() < 2) { 876 return false; 877 } 878 879 Class prevClass = null; 880 for ( Iterator iter = content.iterator(); iter.hasNext(); ) { 881 Object object = iter.next(); 882 Class newClass = object.getClass(); 883 if (newClass != prevClass) { 884 if (prevClass != null) { 885 return true; 886 } 887 prevClass = newClass; 888 } 889 } 890 return false; 891 } 892 893 public boolean isTextOnly() { 894 List content = contentList(); 895 if (content == null || content.isEmpty()) { 896 return true; 897 } 898 for ( Iterator iter = content.iterator(); iter.hasNext(); ) { 899 Object object = iter.next(); 900 if ( ! ( object instanceof CharacterData) && ! ( object instanceof String ) ) { 901 return false; 902 } 903 } 904 return true; 905 } 906 907 public void setText(String text) { 908 clearContent(); 909 addText(text); 910 } 911 912 public String getStringValue() { 913 List list = contentList(); 914 int size = list.size(); 915 if ( size > 0 ) { 916 if ( size == 1 ) { 917 // optimised to avoid StringBuffer creation 918 return getContentAsStringValue( list.get(0) ); 919 } 920 else { 921 StringBuffer buffer = new StringBuffer(); 922 for ( int i = 0; i < size; i++ ) { 923 Object node = list.get(i); 924 String string = getContentAsStringValue( node ); 925 if ( string.length() > 0 ) { 926 if ( USE_STRINGVALUE_SEPARATOR ) { 927 if ( buffer.length() > 0 ) { 928 buffer.append( ' ' ); 929 } 930 } 931 buffer.append( string ); 932 } 933 } 934 return buffer.toString(); 935 } 936 } 937 return ""; 938 } 939 940 941 /** 942 * Puts all <code>Text</code> nodes in the full depth of the sub-tree 943 * underneath this <code>Node</code>, including attribute nodes, into a 944 * "normal" form where only structure (e.g., elements, comments, 945 * processing instructions, CDATA sections, and entity references) 946 * separates <code>Text</code> nodes, i.e., there are neither adjacent 947 * <code>Text</code> nodes nor empty <code>Text</code> nodes. This can 948 * be used to ensure that the DOM view of a document is the same as if 949 * it were saved and re-loaded, and is useful when operations (such as 950 * XPointer lookups) that depend on a particular document tree 951 * structure are to be used.In cases where the document contains 952 * <code>CDATASections</code>, the normalize operation alone may not be 953 * sufficient, since XPointers do not differentiate between 954 * <code>Text</code> nodes and <code>CDATASection</code> nodes. 955 * @version DOM Level 2 956 */ 957 public void normalize() { 958 List content = contentList(); 959 Text previousText = null; 960 int i = 0; 961 while ( i < content.size() ) { 962 Node node = (Node) content.get(i); 963 if ( node instanceof Text ) { 964 Text text = (Text) node; 965 if ( previousText != null ) { 966 previousText.appendText( text.getText() ); 967 remove(text); 968 } 969 else { 970 String value = text.getText(); 971 // only remove empty Text nodes, not whitespace nodes 972 //if ( value == null || value.trim().length() <= 0 ) { 973 if ( value == null || value.length() <= 0 ) { 974 remove(text); 975 } 976 else { 977 previousText = text; 978 i++; 979 } 980 } 981 } 982 else { 983 if ( node instanceof Element ) { 984 Element element = (Element) node; 985 element.normalize(); 986 } 987 previousText = null; 988 i++; 989 } 990 } 991 } 992 993 public String elementText(String name) { 994 Element element = element(name); 995 return (element != null) ? element.getText() : null; 996 } 997 998 public String elementText(QName qName) { 999 Element element = element(qName); 1000 return (element != null) ? element.getText() : null; 1001 } 1002 1003 1004 public String elementTextTrim(String name) { 1005 Element element = element(name); 1006 return (element != null) ? element.getTextTrim() : null; 1007 } 1008 1009 public String elementTextTrim(QName qName) { 1010 Element element = element(qName); 1011 return (element != null) ? element.getTextTrim() : null; 1012 } 1013 1014 1015 // add to me content from another element 1016 // analagous to the addAll(collection) methods in Java 2 collections 1017 public void appendAttributes(Element element) { 1018 for ( int i = 0, size = element.attributeCount(); i < size; i++ ) { 1019 Attribute attribute = element.attribute(i); 1020 if ( attribute.supportsParent() ) { 1021 addAttribute(attribute.getQName(), attribute.getValue()); 1022 } 1023 else { 1024 add(attribute); 1025 } 1026 } 1027 } 1028 1029 1030 1031 /** <p>This returns a deep clone of this element. 1032 * The new element is detached from its parent, and getParent() on the 1033 * clone will return null.</p> 1034 * 1035 * @return the clone of this element 1036 */ 1037/* 1038 public Object clone() { 1039 Element clone = createElement(getQName()); 1040 clone.appendAttributes(this); 1041 clone.appendContent(this); 1042 return clone; 1043 } 1044*/ 1045 public Element createCopy() { 1046 Element clone = createElement(getQName()); 1047 clone.appendAttributes(this); 1048 clone.appendContent(this); 1049 return clone; 1050 } 1051 1052 public Element createCopy(String name) { 1053 Element clone = createElement(name); 1054 clone.appendAttributes(this); 1055 clone.appendContent(this); 1056 return clone; 1057 } 1058 1059 public Element createCopy(QName qName) { 1060 Element clone = createElement(qName); 1061 clone.appendAttributes(this); 1062 clone.appendContent(this); 1063 return clone; 1064 } 1065 1066 1067 1068 public QName getQName(String qualifiedName) { 1069 String prefix = ""; 1070 String localName = qualifiedName; 1071 int index = qualifiedName.indexOf(":"); 1072 if (index > 0) { 1073 prefix = qualifiedName.substring(0, index); 1074 localName = qualifiedName.substring(index+1); 1075 } 1076 Namespace namespace = getNamespaceForPrefix(prefix); 1077 if ( namespace != null ) { 1078 return getDocumentFactory().createQName( localName, namespace ); 1079 } 1080 else { 1081 return getDocumentFactory().createQName( localName ); 1082 } 1083 } 1084 1085 public Namespace getNamespaceForPrefix(String prefix) { 1086 if ( prefix == null ) { 1087 prefix = ""; 1088 } 1089 if ( prefix.equals( getNamespacePrefix() ) ) { 1090 return getNamespace(); 1091 } 1092 else if ( prefix.equals( "xml" ) ) { 1093 return Namespace.XML_NAMESPACE; 1094 } 1095 else { 1096 List list = contentList(); 1097 int size = list.size(); 1098 for ( int i = 0; i < size; i++ ) { 1099 Object object = list.get(i); 1100 if ( object instanceof Namespace ) { 1101 Namespace namespace = (Namespace) object; 1102 if ( prefix.equals( namespace.getPrefix() ) ) { 1103 return namespace; 1104 } 1105 } 1106 } 1107 } 1108 Element parent = getParent(); 1109 if ( parent != null ) { 1110 Namespace answer = parent.getNamespaceForPrefix(prefix); 1111 if ( answer != null ) { 1112 return answer; 1113 } 1114 } 1115 if ( prefix == null || prefix.length() <= 0 ) { 1116 return Namespace.NO_NAMESPACE; 1117 } 1118 return null; 1119 } 1120 1121 public Namespace getNamespaceForURI(String uri) { 1122 if ( uri == null || uri.length() <= 0 ) { 1123 return Namespace.NO_NAMESPACE; 1124 } 1125 else if ( uri.equals( getNamespaceURI() ) ) { 1126 return getNamespace(); 1127 } 1128 else { 1129 List list = contentList(); 1130 int size = list.size(); 1131 for ( int i = 0; i < size; i++ ) { 1132 Object object = list.get(i); 1133 if ( object instanceof Namespace ) { 1134 Namespace namespace = (Namespace) object; 1135 if ( uri.equals( namespace.getURI() ) ) { 1136 return namespace; 1137 } 1138 } 1139 } 1140 return null; 1141 } 1142 } 1143 1144 public List declaredNamespaces() { 1145 BackedList answer = createResultList(); 1146 if ( getNamespaceURI().length() > 0 ) { 1147 answer.addLocal( getNamespace() ); 1148 } 1149 List list = contentList(); 1150 int size = list.size(); 1151 for ( int i = 0; i < size; i++ ) { 1152 Object object = list.get(i); 1153 if ( object instanceof Namespace ) { 1154 answer.addLocal( object ); 1155 } 1156 } 1157 return answer; 1158 } 1159 1160 public List additionalNamespaces() { 1161 List list = contentList(); 1162 int size = list.size(); 1163 BackedList answer = createResultList(); 1164 for ( int i = 0; i < size; i++ ) { 1165 Object object = list.get(i); 1166 if ( object instanceof Namespace ) { 1167 Namespace namespace = (Namespace) object; 1168 answer.addLocal( namespace ); 1169 } 1170 } 1171 return answer; 1172 } 1173 1174 public List additionalNamespaces(String defaultNamespaceURI) { 1175 List list = contentList(); 1176 BackedList answer = createResultList(); 1177 int size = list.size(); 1178 for ( int i = 0; i < size; i++ ) { 1179 Object object = list.get(i); 1180 if ( object instanceof Namespace ) { 1181 Namespace namespace = (Namespace) object; 1182 if ( ! defaultNamespaceURI.equals( namespace.getURI() ) ) { 1183 answer.addLocal( namespace ); 1184 } 1185 } 1186 } 1187 return answer; 1188 } 1189 1190 // Implementation helper methods 1191 //------------------------------------------------------------------------- 1192 1193 /** Ensures that the list of attributes has the given size */ 1194 public void ensureAttributesCapacity(int minCapacity) { 1195 if ( minCapacity > 1 ) { 1196 List list = attributeList(); 1197 if ( list instanceof ArrayList ) { 1198 ArrayList arrayList = (ArrayList) list; 1199 arrayList.ensureCapacity(minCapacity); 1200 } 1201 } 1202 } 1203 1204 // Implementation methods 1205 //------------------------------------------------------------------------- 1206 1207 1208 protected Element createElement(String name) { 1209 return getDocumentFactory().createElement(name); 1210 } 1211 1212 protected Element createElement(QName qName) { 1213 return getDocumentFactory().createElement(qName); 1214 } 1215 1216 1217 protected void addNode(Node node) { 1218 if (node.getParent() != null) { 1219 // XXX: could clone here 1220 String message = "The Node already has an existing parent of \"" 1221 + node.getParent().getQualifiedName() + "\""; 1222 throw new IllegalAddException(this, node, message); 1223 } 1224 addNewNode(node); 1225 } 1226 1227 /** Like addNode() but does not require a parent check */ 1228 protected void addNewNode(Node node) { 1229 contentList().add( node ); 1230 1231 childAdded(node); 1232 } 1233 1234 protected boolean removeNode(Node node) { 1235 boolean answer = contentList().remove(node); 1236 if (answer) { 1237 childRemoved(node); 1238 } 1239 return answer; 1240 } 1241 1242 /** Called when a new child node is added to 1243 * create any parent relationships 1244 */ 1245 protected void childAdded(Node node) { 1246 if (node != null ) { 1247 node.setParent(this); 1248 } 1249 } 1250 1251 protected void childRemoved(Node node) { 1252 if ( node != null ) { 1253 node.setParent(null); 1254 node.setDocument(null); 1255 } 1256 } 1257 1258 /** @return the internal List used to store attributes or 1259 * creates one if one is not available 1260 */ 1261 protected abstract List attributeList(); 1262 1263 /** @return the internal List used to store attributes or 1264 * creates one with the specified size if one is not available 1265 */ 1266 protected abstract List attributeList(int attributeCount); 1267 1268 protected DocumentFactory getDocumentFactory() { 1269 QName qName = getQName(); 1270 // QName might be null as we might not have been constructed yet 1271 if ( qName != null ) { 1272 DocumentFactory factory = qName.getDocumentFactory(); 1273 if ( factory != null ) { 1274 return factory; 1275 } 1276 } 1277 return DOCUMENT_FACTORY; 1278 } 1279 1280 /** A Factory Method pattern which creates 1281 * a List implementation used to store attributes 1282 */ 1283 protected List createAttributeList() { 1284 return createAttributeList( DEFAULT_CONTENT_LIST_SIZE ); 1285 } 1286 1287 /** A Factory Method pattern which creates 1288 * a List implementation used to store attributes 1289 */ 1290 protected List createAttributeList( int size ) { 1291 return new ArrayList( size ); 1292 } 1293 1294 protected Iterator createSingleIterator( Object result ) { 1295 return new SingleIterator( result ); 1296 } 1297} 1298 1299 1300 1301 1302/* 1303 * Redistribution and use of this software and associated documentation 1304 * ("Software"), with or without modification, are permitted provided 1305 * that the following conditions are met: 1306 * 1307 * 1. Redistributions of source code must retain copyright 1308 * statements and notices. Redistributions must also contain a 1309 * copy of this document. 1310 * 1311 * 2. Redistributions in binary form must reproduce the 1312 * above copyright notice, this list of conditions and the 1313 * following disclaimer in the documentation and/or other 1314 * materials provided with the distribution. 1315 * 1316 * 3. The name "DOM4J" must not be used to endorse or promote 1317 * products derived from this Software without prior written 1318 * permission of MetaStuff, Ltd. For written permission, 1319 * please contact dom4j-info@metastuff.com. 1320 * 1321 * 4. Products derived from this Software may not be called "DOM4J" 1322 * nor may "DOM4J" appear in their names without prior written 1323 * permission of MetaStuff, Ltd. DOM4J is a registered 1324 * trademark of MetaStuff, Ltd. 1325 * 1326 * 5. Due credit should be given to the DOM4J Project 1327 * (http://dom4j.org/). 1328 * 1329 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS 1330 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 1331 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 1332 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 1333 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 1334 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 1335 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 1336 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 1337 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 1338 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 1339 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 1340 * OF THE POSSIBILITY OF SUCH DAMAGE. 1341 * 1342 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 1343 * 1344 * $Id: AbstractElement.java,v 1.63 2002/02/01 13:04:32 jstrachan Exp $ 1345 */