View Javadoc

    1   /*
    2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
    3    *
    4    * This software is open source.
    5    * See the bottom of this file for the licence.
    6    */
    7
    8   package org.dom4j.tree;
    9
    10  import java.io.IOException;
    11  import java.io.StringWriter;
    12  import java.io.Writer;
    13  import java.util.ArrayList;
    14  import java.util.Collections;
    15  import java.util.Iterator;
    16  import java.util.List;
    17  import java.util.Map;
    18
    19  import org.dom4j.Attribute;
    20  import org.dom4j.CDATA;
    21  import org.dom4j.CharacterData;
    22  import org.dom4j.Comment;
    23  import org.dom4j.Document;
    24  import org.dom4j.DocumentFactory;
    25  import org.dom4j.Element;
    26  import org.dom4j.Entity;
    27  import org.dom4j.IllegalAddException;
    28  import org.dom4j.Namespace;
    29  import org.dom4j.Node;
    30  import org.dom4j.ProcessingInstruction;
    31  import org.dom4j.QName;
    32  import org.dom4j.Text;
    33  import org.dom4j.Visitor;
    34  import org.dom4j.io.OutputFormat;
    35  import org.dom4j.io.XMLWriter;
    36
    37  import org.xml.sax.Attributes;
    38
    39  /***
    40   * <p>
    41   * <code>AbstractElement</code> is an abstract base class for tree
    42   * implementors to use for implementation inheritence.
    43   * </p>
    44   *
    45   * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
    46   * @version $Revision: 1.80 $
    47   */
    48  public abstract class AbstractElement extends AbstractBranch implements
    49          org.dom4j.Element {
    50      /*** The <code>DocumentFactory</code> instance used by default */
    51      private static final DocumentFactory DOCUMENT_FACTORY = DocumentFactory
    52              .getInstance();
    53
    54      protected static final List EMPTY_LIST = Collections.EMPTY_LIST;
    55
    56      protected static final Iterator EMPTY_ITERATOR = EMPTY_LIST.iterator();
    57
    58      protected static final boolean VERBOSE_TOSTRING = false;
    59
    60      protected static final boolean USE_STRINGVALUE_SEPARATOR = false;
    61
    62      public AbstractElement() {
    63      }
    64
    65      public short getNodeType() {
    66          return ELEMENT_NODE;
    67      }
    68
    69      public boolean isRootElement() {
    70          Document document = getDocument();
    71
    72          if (document != null) {
    73              Element root = document.getRootElement();
    74
    75              if (root == this) {
    76                  return true;
    77              }
    78          }
    79
    80          return false;
    81      }
    82
    83      public void setName(String name) {
    84          setQName(getDocumentFactory().createQName(name));
    85      }
    86
    87      public void setNamespace(Namespace namespace) {
    88          setQName(getDocumentFactory().createQName(getName(), namespace));
    89      }
    90
    91      /***
    92       * Returns the XPath expression to match this Elements name which is
    93       * getQualifiedName() if there is a namespace prefix defined or if no
    94       * namespace is present then it is getName() or if a namespace is defined
    95       * with no prefix then the expression is [name()='X'] where X = getName().
    96       *
    97       * @return DOCUMENT ME!
    98       */
    99      public String getXPathNameStep() {
    100         String uri = getNamespaceURI();
    101
    102         if ((uri == null) || (uri.length() == 0)) {
    103             return getName();
    104         }
    105
    106         String prefix = getNamespacePrefix();
    107
    108         if ((prefix == null) || (prefix.length() == 0)) {
    109             return "*[name()='" + getName() + "']";
    110         }
    111
    112         return getQualifiedName();
    113     }
    114
    115     public String getPath(Element context) {
    116         if (this == context) {
    117             return ".";
    118         }
    119
    120         Element parent = getParent();
    121
    122         if (parent == null) {
    123             return "/" + getXPathNameStep();
    124         } else if (parent == context) {
    125             return getXPathNameStep();
    126         }
    127
    128         return parent.getPath(context) + "/" + getXPathNameStep();
    129     }
    130
    131     public String getUniquePath(Element context) {
    132         Element parent = getParent();
    133
    134         if (parent == null) {
    135             return "/" + getXPathNameStep();
    136         }
    137
    138         StringBuffer buffer = new StringBuffer();
    139
    140         if (parent != context) {
    141             buffer.append(parent.getUniquePath(context));
    142
    143             buffer.append("/");
    144         }
    145
    146         buffer.append(getXPathNameStep());
    147
    148         List mySiblings = parent.elements(getQName());
    149
    150         if (mySiblings.size() > 1) {
    151             int idx = mySiblings.indexOf(this);
    152
    153             if (idx >= 0) {
    154                 buffer.append("[");
    155
    156                 buffer.append(Integer.toString(++idx));
    157
    158                 buffer.append("]");
    159             }
    160         }
    161
    162         return buffer.toString();
    163     }
    164
    165     public String asXML() {
    166         try {
    167             StringWriter out = new StringWriter();
    168             XMLWriter writer = new XMLWriter(out, new OutputFormat());
    169
    170             writer.write(this);
    171             writer.flush();
    172
    173             return out.toString();
    174         } catch (IOException e) {
    175             throw new RuntimeException("IOException while generating "
    176                     + "textual representation: " + e.getMessage());
    177         }
    178     }
    179
    180     public void write(Writer out) throws IOException {
    181         XMLWriter writer = new XMLWriter(out, new OutputFormat());
    182         writer.write(this);
    183     }
    184
    185     /***
    186      * <p>
    187      * <code>accept</code> method is the <code>Visitor Pattern</code>
    188      * method.
    189      * </p>
    190      *
    191      * @param visitor
    192      *            <code>Visitor</code> is the visitor.
    193      */
    194     public void accept(Visitor visitor) {
    195         visitor.visit(this);
    196
    197         // visit attributes
    198         for (int i = 0, size = attributeCount(); i < size; i++) {
    199             Attribute attribute = attribute(i);
    200
    201             visitor.visit(attribute);
    202         }
    203
    204         // visit content
    205         for (int i = 0, size = nodeCount(); i < size; i++) {
    206             Node node = node(i);
    207
    208             node.accept(visitor);
    209         }
    210     }
    211
    212     public String toString() {
    213         String uri = getNamespaceURI();
    214
    215         if ((uri != null) && (uri.length() > 0)) {
    216             if (VERBOSE_TOSTRING) {
    217                 return super.toString() + " [Element: <" + getQualifiedName()
    218                         + " uri: " + uri + " attributes: " + attributeList()
    219                         + " content: " + contentList() + " />]";
    220             } else {
    221                 return super.toString() + " [Element: <" + getQualifiedName()
    222                         + " uri: " + uri + " attributes: " + attributeList()
    223                         + "/>]";
    224             }
    225         } else {
    226             if (VERBOSE_TOSTRING) {
    227                 return super.toString() + " [Element: <" + getQualifiedName()
    228                         + " attributes: " + attributeList() + " content: "
    229                         + contentList() + " />]";
    230             } else {
    231                 return super.toString() + " [Element: <" + getQualifiedName()
    232                         + " attributes: " + attributeList() + "/>]";
    233             }
    234         }
    235     }
    236
    237     // QName methods
    238     // -------------------------------------------------------------------------
    239     public Namespace getNamespace() {
    240         return getQName().getNamespace();
    241     }
    242
    243     public String getName() {
    244         return getQName().getName();
    245     }
    246
    247     public String getNamespacePrefix() {
    248         return getQName().getNamespacePrefix();
    249     }
    250
    251     public String getNamespaceURI() {
    252         return getQName().getNamespaceURI();
    253     }
    254
    255     public String getQualifiedName() {
    256         return getQName().getQualifiedName();
    257     }
    258
    259     public Object getData() {
    260         return getText();
    261     }
    262
    263     public void setData(Object data) {
    264         // ignore this method
    265     }
    266
    267     // Node methods
    268     // -------------------------------------------------------------------------
    269     public Node node(int index) {
    270         if (index >= 0) {
    271             List list = contentList();
    272
    273             if (index >= list.size()) {
    274                 return null;
    275             }
    276
    277             Object node = list.get(index);
    278
    279             if (node != null) {
    280                 if (node instanceof Node) {
    281                     return (Node) node;
    282                 } else {
    283                     return getDocumentFactory().createText(node.toString());
    284                 }
    285             }
    286         }
    287
    288         return null;
    289     }
    290
    291     public int indexOf(Node node) {
    292         return contentList().indexOf(node);
    293     }
    294
    295     public int nodeCount() {
    296         return contentList().size();
    297     }
    298
    299     public Iterator nodeIterator() {
    300         return contentList().iterator();
    301     }
    302
    303     // Element methods
    304     // -------------------------------------------------------------------------
    305     public Element element(String name) {
    306         List list = contentList();
    307
    308         int size = list.size();
    309
    310         for (int i = 0; i < size; i++) {
    311             Object object = list.get(i);
    312
    313             if (object instanceof Element) {
    314                 Element element = (Element) object;
    315
    316                 if (name.equals(element.getName())) {
    317                     return element;
    318                 }
    319             }
    320         }
    321
    322         return null;
    323     }
    324
    325     public Element element(QName qName) {
    326         List list = contentList();
    327
    328         int size = list.size();
    329
    330         for (int i = 0; i < size; i++) {
    331             Object object = list.get(i);
    332
    333             if (object instanceof Element) {
    334                 Element element = (Element) object;
    335
    336                 if (qName.equals(element.getQName())) {
    337                     return element;
    338                 }
    339             }
    340         }
    341
    342         return null;
    343     }
    344
    345     public Element element(String name, Namespace namespace) {
    346         return element(getDocumentFactory().createQName(name, namespace));
    347     }
    348
    349     public List elements() {
    350         List list = contentList();
    351
    352         BackedList answer = createResultList();
    353
    354         int size = list.size();
    355
    356         for (int i = 0; i < size; i++) {
    357             Object object = list.get(i);
    358
    359             if (object instanceof Element) {
    360                 answer.addLocal(object);
    361             }
    362         }
    363
    364         return answer;
    365     }
    366
    367     public List elements(String name) {
    368         List list = contentList();
    369
    370         BackedList answer = createResultList();
    371
    372         int size = list.size();
    373
    374         for (int i = 0; i < size; i++) {
    375             Object object = list.get(i);
    376
    377             if (object instanceof Element) {
    378                 Element element = (Element) object;
    379
    380                 if (name.equals(element.getName())) {
    381                     answer.addLocal(element);
    382                 }
    383             }
    384         }
    385
    386         return answer;
    387     }
    388
    389     public List elements(QName qName) {
    390         List list = contentList();
    391
    392         BackedList answer = createResultList();
    393
    394         int size = list.size();
    395
    396         for (int i = 0; i < size; i++) {
    397             Object object = list.get(i);
    398
    399             if (object instanceof Element) {
    400                 Element element = (Element) object;
    401
    402                 if (qName.equals(element.getQName())) {
    403                     answer.addLocal(element);
    404                 }
    405             }
    406         }
    407
    408         return answer;
    409     }
    410
    411     public List elements(String name, Namespace namespace) {
    412         return elements(getDocumentFactory().createQName(name, namespace));
    413     }
    414
    415     public Iterator elementIterator() {
    416         List list = elements();
    417
    418         return list.iterator();
    419     }
    420
    421     public Iterator elementIterator(String name) {
    422         List list = elements(name);
    423
    424         return list.iterator();
    425     }
    426
    427     public Iterator elementIterator(QName qName) {
    428         List list = elements(qName);
    429
    430         return list.iterator();
    431     }
    432
    433     public Iterator elementIterator(String name, Namespace ns) {
    434         return elementIterator(getDocumentFactory().createQName(name, ns));
    435     }
    436
    437     // Attribute methods
    438     // -------------------------------------------------------------------------
    439     public List attributes() {
    440         return new ContentListFacade(this, attributeList());
    441     }
    442
    443     public Iterator attributeIterator() {
    444         return attributeList().iterator();
    445     }
    446
    447     public Attribute attribute(int index) {
    448         return (Attribute) attributeList().get(index);
    449     }
    450
    451     public int attributeCount() {
    452         return attributeList().size();
    453     }
    454
    455     public Attribute attribute(String name) {
    456         List list = attributeList();
    457
    458         int size = list.size();
    459
    460         for (int i = 0; i < size; i++) {
    461             Attribute attribute = (Attribute) list.get(i);
    462
    463             if (name.equals(attribute.getName())) {
    464                 return attribute;
    465             }
    466         }
    467
    468         return null;
    469     }
    470
    471     public Attribute attribute(QName qName) {
    472         List list = attributeList();
    473
    474         int size = list.size();
    475
    476         for (int i = 0; i < size; i++) {
    477             Attribute attribute = (Attribute) list.get(i);
    478
    479             if (qName.equals(attribute.getQName())) {
    480                 return attribute;
    481             }
    482         }
    483
    484         return null;
    485     }
    486
    487     public Attribute attribute(String name, Namespace namespace) {
    488         return attribute(getDocumentFactory().createQName(name, namespace));
    489     }
    490
    491     /***
    492      * This method provides a more optimal way of setting all the attributes on
    493      * an Element particularly for use in {@link org.dom4j.io.SAXReader}.
    494      *
    495      * @param attributes
    496      *            DOCUMENT ME!
    497      * @param namespaceStack
    498      *            DOCUMENT ME!
    499      * @param noNamespaceAttributes
    500      *            DOCUMENT ME!
    501      */
    502     public void setAttributes(Attributes attributes,
    503             NamespaceStack namespaceStack, boolean noNamespaceAttributes) {
    504         // now lets add all attribute values
    505         int size = attributes.getLength();
    506
    507         if (size > 0) {
    508             DocumentFactory factory = getDocumentFactory();
    509
    510             if (size == 1) {
    511                 // allow lazy construction of the List of Attributes
    512                 String name = attributes.getQName(0);
    513
    514                 if (noNamespaceAttributes || !name.startsWith("xmlns")) {
    515                     String attributeURI = attributes.getURI(0);
    516
    517                     String attributeLocalName = attributes.getLocalName(0);
    518
    519                     String attributeValue = attributes.getValue(0);
    520
    521                     QName attributeQName = namespaceStack.getAttributeQName(
    522                             attributeURI, attributeLocalName, name);
    523
    524                     add(factory.createAttribute(this, attributeQName,
    525                             attributeValue));
    526                 }
    527             } else {
    528                 List list = attributeList(size);
    529
    530                 list.clear();
    531
    532                 for (int i = 0; i < size; i++) {
    533                     // optimised to avoid the call to attribute(QName) to
    534                     // lookup an attribute for a given QName
    535                     String attributeName = attributes.getQName(i);
    536
    537                     if (noNamespaceAttributes
    538                             || !attributeName.startsWith("xmlns")) {
    539                         String attributeURI = attributes.getURI(i);
    540
    541                         String attributeLocalName = attributes.getLocalName(i);
    542
    543                         String attributeValue = attributes.getValue(i);
    544
    545                         QName attributeQName = namespaceStack
    546                                 .getAttributeQName(attributeURI,
    547                                         attributeLocalName, attributeName);
    548
    549                         Attribute attribute = factory.createAttribute(this,
    550                                 attributeQName, attributeValue);
    551
    552                         list.add(attribute);
    553
    554                         childAdded(attribute);
    555                     }
    556                 }
    557             }
    558         }
    559     }
    560
    561     public String attributeValue(String name) {
    562         Attribute attrib = attribute(name);
    563
    564         if (attrib == null) {
    565             return null;
    566         } else {
    567             return attrib.getValue();
    568         }
    569     }
    570
    571     public String attributeValue(QName qName) {
    572         Attribute attrib = attribute(qName);
    573
    574         if (attrib == null) {
    575             return null;
    576         } else {
    577             return attrib.getValue();
    578         }
    579     }
    580
    581     public String attributeValue(String name, String defaultValue) {
    582         String answer = attributeValue(name);
    583
    584         return (answer != null) ? answer : defaultValue;
    585     }
    586
    587     public String attributeValue(QName qName, String defaultValue) {
    588         String answer = attributeValue(qName);
    589
    590         return (answer != null) ? answer : defaultValue;
    591     }
    592
    593     /***
    594      * DOCUMENT ME!
    595      *
    596      * @param name
    597      *            DOCUMENT ME!
    598      * @param value
    599      *            DOCUMENT ME!
    600      *
    601      * @deprecated As of version 0.5. Please use {@link
    602      *             #addAttribute(String,String)} instead. WILL BE REMOVED IN
    603      *             dom4j-1.6 !!
    604      */
    605     public void setAttributeValue(String name, String value) {
    606         addAttribute(name, value);
    607     }
    608
    609     /***
    610      * DOCUMENT ME!
    611      *
    612      * @param qName
    613      *            DOCUMENT ME!
    614      * @param value
    615      *            DOCUMENT ME!
    616      *
    617      * @deprecated As of version 0.5. Please use {@link
    618      *             #addAttribute(String,String)} instead. WILL BE REMOVED IN
    619      *             dom4j-1.6 !!
    620      */
    621     public void setAttributeValue(QName qName, String value) {
    622         addAttribute(qName, value);
    623     }
    624
    625     public void add(Attribute attribute) {
    626         if (attribute.getParent() != null) {
    627             String message = "The Attribute already has an existing parent \""
    628                     + attribute.getParent().getQualifiedName() + "\"";
    629
    630             throw new IllegalAddException(this, attribute, message);
    631         }
    632
    633         if (attribute.getValue() == null) {
    634             // try remove a previous attribute with the same
    635             // name since adding an attribute with a null value
    636             // is equivalent to removing it.
    637             Attribute oldAttribute = attribute(attribute.getQName());
    638
    639             if (oldAttribute != null) {
    640                 remove(oldAttribute);
    641             }
    642         } else {
    643             attributeList().add(attribute);
    644
    645             childAdded(attribute);
    646         }
    647     }
    648
    649     public boolean remove(Attribute attribute) {
    650         List list = attributeList();
    651
    652         boolean answer = list.remove(attribute);
    653
    654         if (answer) {
    655             childRemoved(attribute);
    656         } else {
    657             // we may have a copy of the attribute
    658             Attribute copy = attribute(attribute.getQName());
    659
    660             if (copy != null) {
    661                 list.remove(copy);
    662
    663                 answer = true;
    664             }
    665         }
    666
    667         return answer;
    668     }
    669
    670     // Processing instruction API
    671     // -------------------------------------------------------------------------
    672     public List processingInstructions() {
    673         List list = contentList();
    674
    675         BackedList answer = createResultList();
    676
    677         int size = list.size();
    678
    679         for (int i = 0; i < size; i++) {
    680             Object object = list.get(i);
    681
    682             if (object instanceof ProcessingInstruction) {
    683                 answer.addLocal(object);
    684             }
    685         }
    686
    687         return answer;
    688     }
    689
    690     public List processingInstructions(String target) {
    691         List list = contentList();
    692
    693         BackedList answer = createResultList();
    694
    695         int size = list.size();
    696
    697         for (int i = 0; i < size; i++) {
    698             Object object = list.get(i);
    699
    700             if (object instanceof ProcessingInstruction) {
    701                 ProcessingInstruction pi = (ProcessingInstruction) object;
    702
    703                 if (target.equals(pi.getName())) {
    704                     answer.addLocal(pi);
    705                 }
    706             }
    707         }
    708
    709         return answer;
    710     }
    711
    712     public ProcessingInstruction processingInstruction(String target) {
    713         List list = contentList();
    714
    715         int size = list.size();
    716
    717         for (int i = 0; i < size; i++) {
    718             Object object = list.get(i);
    719
    720             if (object instanceof ProcessingInstruction) {
    721                 ProcessingInstruction pi = (ProcessingInstruction) object;
    722
    723                 if (target.equals(pi.getName())) {
    724                     return pi;
    725                 }
    726             }
    727         }
    728
    729         return null;
    730     }
    731
    732     public boolean removeProcessingInstruction(String target) {
    733         List list = contentList();
    734
    735         for (Iterator iter = list.iterator(); iter.hasNext();) {
    736             Object object = iter.next();
    737
    738             if (object instanceof ProcessingInstruction) {
    739                 ProcessingInstruction pi = (ProcessingInstruction) object;
    740
    741                 if (target.equals(pi.getName())) {
    742                     iter.remove();
    743
    744                     return true;
    745                 }
    746             }
    747         }
    748
    749         return false;
    750     }
    751
    752     // Content Model methods
    753     // -------------------------------------------------------------------------
    754     public Node getXPathResult(int index) {
    755         Node answer = node(index);
    756
    757         if ((answer != null) && !answer.supportsParent()) {
    758             return answer.asXPathResult(this);
    759         }
    760
    761         return answer;
    762     }
    763
    764     public Element addAttribute(String name, String value) {
    765         // adding a null value is equivalent to removing the attribute
    766         Attribute attribute = attribute(name);
    767
    768         if (value != null) {
    769             if (attribute == null) {
    770                 add(getDocumentFactory().createAttribute(this, name, value));
    771             } else if (attribute.isReadOnly()) {
    772                 remove(attribute);
    773
    774                 add(getDocumentFactory().createAttribute(this, name, value));
    775             } else {
    776                 attribute.setValue(value);
    777             }
    778         } else if (attribute != null) {
    779             remove(attribute);
    780         }
    781
    782         return this;
    783     }
    784
    785     public Element addAttribute(QName qName, String value) {
    786         // adding a null value is equivalent to removing the attribute
    787         Attribute attribute = attribute(qName);
    788
    789         if (value != null) {
    790             if (attribute == null) {
    791                 add(getDocumentFactory().createAttribute(this, qName, value));
    792             } else if (attribute.isReadOnly()) {
    793                 remove(attribute);
    794
    795                 add(getDocumentFactory().createAttribute(this, qName, value));
    796             } else {
    797                 attribute.setValue(value);
    798             }
    799         } else if (attribute != null) {
    800             remove(attribute);
    801         }
    802
    803         return this;
    804     }
    805
    806     public Element addCDATA(String cdata) {
    807         CDATA node = getDocumentFactory().createCDATA(cdata);
    808
    809         addNewNode(node);
    810
    811         return this;
    812     }
    813
    814     public Element addComment(String comment) {
    815         Comment node = getDocumentFactory().createComment(comment);
    816
    817         addNewNode(node);
    818
    819         return this;
    820     }
    821
    822     public Element addElement(String name) {
    823         DocumentFactory factory = getDocumentFactory();
    824
    825         int index = name.indexOf(":");
    826
    827         String prefix = "";
    828
    829         String localName = name;
    830
    831         Namespace namespace = null;
    832
    833         if (index > 0) {
    834             prefix = name.substring(0, index);
    835
    836             localName = name.substring(index + 1);
    837
    838             namespace = getNamespaceForPrefix(prefix);
    839
    840             if (namespace == null) {
    841                 throw new IllegalAddException("No such namespace prefix: "
    842                         + prefix + " is in scope on: " + this
    843                         + " so cannot add element: " + name);
    844             }
    845         } else {
    846             namespace = getNamespaceForPrefix("");
    847         }
    848
    849         Element node;
    850
    851         if (namespace != null) {
    852             QName qname = factory.createQName(localName, namespace);
    853
    854             node = factory.createElement(qname);
    855         } else {
    856             node = factory.createElement(name);
    857         }
    858
    859         addNewNode(node);
    860
    861         return node;
    862     }
    863
    864     public Element addEntity(String name, String text) {
    865         Entity node = getDocumentFactory().createEntity(name, text);
    866
    867         addNewNode(node);
    868
    869         return this;
    870     }
    871
    872     public Element addNamespace(String prefix, String uri) {
    873         Namespace node = getDocumentFactory().createNamespace(prefix, uri);
    874
    875         addNewNode(node);
    876
    877         return this;
    878     }
    879
    880     public Element addProcessingInstruction(String target, String data) {
    881         ProcessingInstruction node = getDocumentFactory()
    882                 .createProcessingInstruction(target, data);
    883
    884         addNewNode(node);
    885
    886         return this;
    887     }
    888
    889     public Element addProcessingInstruction(String target, Map data) {
    890         ProcessingInstruction node = getDocumentFactory()
    891                 .createProcessingInstruction(target, data);
    892
    893         addNewNode(node);
    894
    895         return this;
    896     }
    897
    898     public Element addText(String text) {
    899         Text node = getDocumentFactory().createText(text);
    900
    901         addNewNode(node);
    902
    903         return this;
    904     }
    905
    906     // polymorphic node methods
    907     public void add(Node node) {
    908         switch (node.getNodeType()) {
    909             case ELEMENT_NODE:
    910                 add((Element) node);
    911
    912                 break;
    913
    914             case ATTRIBUTE_NODE:
    915                 add((Attribute) node);
    916
    917                 break;
    918
    919             case TEXT_NODE:
    920                 add((Text) node);
    921
    922                 break;
    923
    924             case CDATA_SECTION_NODE:
    925                 add((CDATA) node);
    926
    927                 break;
    928
    929             case ENTITY_REFERENCE_NODE:
    930                 add((Entity) node);
    931
    932                 break;
    933
    934             case PROCESSING_INSTRUCTION_NODE:
    935                 add((ProcessingInstruction) node);
    936
    937                 break;
    938
    939             case COMMENT_NODE:
    940                 add((Comment) node);
    941
    942                 break;
    943
    944             /*
    945              * XXXX: to do! case DOCUMENT_TYPE_NODE: add((DocumentType) node);
    946              * break;
    947              */
    948             case NAMESPACE_NODE:
    949                 add((Namespace) node);
    950
    951                 break;
    952
    953             default:
    954                 invalidNodeTypeAddException(node);
    955         }
    956     }
    957
    958     public boolean remove(Node node) {
    959         switch (node.getNodeType()) {
    960             case ELEMENT_NODE:
    961                 return remove((Element) node);
    962
    963             case ATTRIBUTE_NODE:
    964                 return remove((Attribute) node);
    965
    966             case TEXT_NODE:
    967                 return remove((Text) node);
    968
    969             case CDATA_SECTION_NODE:
    970                 return remove((CDATA) node);
    971
    972             case ENTITY_REFERENCE_NODE:
    973                 return remove((Entity) node);
    974
    975             case PROCESSING_INSTRUCTION_NODE:
    976                 return remove((ProcessingInstruction) node);
    977
    978             case COMMENT_NODE:
    979                 return remove((Comment) node);
    980
    981             /*
    982              * case DOCUMENT_TYPE_NODE: return remove((DocumentType) node);
    983              */
    984             case NAMESPACE_NODE:
    985                 return remove((Namespace) node);
    986
    987             default:
    988                 return false;
    989         }
    990     }
    991
    992     // typesafe versions using node classes
    993     public void add(CDATA cdata) {
    994         addNode(cdata);
    995     }
    996
    997     public void add(Comment comment) {
    998         addNode(comment);
    999     }
    1000
    1001     public void add(Element element) {
    1002         addNode(element);
    1003     }
    1004
    1005     public void add(Entity entity) {
    1006         addNode(entity);
    1007     }
    1008
    1009     public void add(Namespace namespace) {
    1010         addNode(namespace);
    1011     }
    1012
    1013     public void add(ProcessingInstruction pi) {
    1014         addNode(pi);
    1015     }
    1016
    1017     public void add(Text text) {
    1018         addNode(text);
    1019     }
    1020
    1021     public boolean remove(CDATA cdata) {
    1022         return removeNode(cdata);
    1023     }
    1024
    1025     public boolean remove(Comment comment) {
    1026         return removeNode(comment);
    1027     }
    1028
    1029     public boolean remove(Element element) {
    1030         return removeNode(element);
    1031     }
    1032
    1033     public boolean remove(Entity entity) {
    1034         return removeNode(entity);
    1035     }
    1036
    1037     public boolean remove(Namespace namespace) {
    1038         return removeNode(namespace);
    1039     }
    1040
    1041     public boolean remove(ProcessingInstruction pi) {
    1042         return removeNode(pi);
    1043     }
    1044
    1045     public boolean remove(Text text) {
    1046         return removeNode(text);
    1047     }
    1048
    1049     // Helper methods
    1050     // -------------------------------------------------------------------------
    1051     public boolean hasMixedContent() {
    1052         List content = contentList();
    1053
    1054         if ((content == null) || content.isEmpty() || (content.size() < 2)) {
    1055             return false;
    1056         }
    1057
    1058         Class prevClass = null;
    1059
    1060         for (Iterator iter = content.iterator(); iter.hasNext();) {
    1061             Object object = iter.next();
    1062
    1063             Class newClass = object.getClass();
    1064
    1065             if (newClass != prevClass) {
    1066                 if (prevClass != null) {
    1067                     return true;
    1068                 }
    1069
    1070                 prevClass = newClass;
    1071             }
    1072         }
    1073
    1074         return false;
    1075     }
    1076
    1077     public boolean isTextOnly() {
    1078         List content = contentList();
    1079
    1080         if ((content == null) || content.isEmpty()) {
    1081             return true;
    1082         }
    1083
    1084         for (Iterator iter = content.iterator(); iter.hasNext();) {
    1085             Object object = iter.next();
    1086
    1087             if (!(object instanceof CharacterData)
    1088                     && !(object instanceof String)) {
    1089                 return false;
    1090             }
    1091         }
    1092
    1093         return true;
    1094     }
    1095
    1096     public void setText(String text) {
    1097         /* remove all text nodes */
    1098         List allContent = contentList();
    1099
    1100         if (allContent != null) {
    1101             Iterator it = allContent.iterator();
    1102
    1103             while (it.hasNext()) {
    1104                 Node node = (Node) it.next();
    1105
    1106                 switch (node.getNodeType()) {
    1107                     case CDATA_SECTION_NODE:
    1108
    1109                     // case ENTITY_NODE:
    1110                     case ENTITY_REFERENCE_NODE:
    1111                     case TEXT_NODE:
    1112                         it.remove();
    1113
    1114                     default:
    1115                         break;
    1116                 }
    1117             }
    1118         }
    1119
    1120         addText(text);
    1121     }
    1122
    1123     public String getStringValue() {
    1124         List list = contentList();
    1125
    1126         int size = list.size();
    1127
    1128         if (size > 0) {
    1129             if (size == 1) {
    1130                 // optimised to avoid StringBuffer creation
    1131                 return getContentAsStringValue(list.get(0));
    1132             } else {
    1133                 StringBuffer buffer = new StringBuffer();
    1134
    1135                 for (int i = 0; i < size; i++) {
    1136                     Object node = list.get(i);
    1137
    1138                     String string = getContentAsStringValue(node);
    1139
    1140                     if (string.length() > 0) {
    1141                         if (USE_STRINGVALUE_SEPARATOR) {
    1142                             if (buffer.length() > 0) {
    1143                                 buffer.append(' ');
    1144                             }
    1145                         }
    1146
    1147                         buffer.append(string);
    1148                     }
    1149                 }
    1150
    1151                 return buffer.toString();
    1152             }
    1153         }
    1154
    1155         return "";
    1156     }
    1157
    1158     /***
    1159      * Puts all <code>Text</code> nodes in the full depth of the sub-tree
    1160      * underneath this <code>Node</code>, including attribute nodes, into a
    1161      * "normal" form where only structure (e.g., elements, comments, processing
    1162      * instructions, CDATA sections, and entity references) separates
    1163      * <code>Text</code> nodes, i.e., there are neither adjacent
    1164      * <code>Text</code> nodes nor empty <code>Text</code> nodes. This can
    1165      * be used to ensure that the DOM view of a document is the same as if it
    1166      * were saved and re-loaded, and is useful when operations (such as XPointer
    1167      * lookups) that depend on a particular document tree structure are to be
    1168      * used.In cases where the document contains <code>CDATASections</code>,
    1169      * the normalize operation alone may not be sufficient, since XPointers do
    1170      * not differentiate between <code>Text</code> nodes and
    1171      * <code>CDATASection</code> nodes.
    1172      *
    1173      * @since DOM Level 2
    1174      */
    1175     public void normalize() {
    1176         List content = contentList();
    1177
    1178         Text previousText = null;
    1179
    1180         int i = 0;
    1181
    1182         while (i < content.size()) {
    1183             Node node = (Node) content.get(i);
    1184
    1185             if (node instanceof Text) {
    1186                 Text text = (Text) node;
    1187
    1188                 if (previousText != null) {
    1189                     previousText.appendText(text.getText());
    1190
    1191                     remove(text);
    1192                 } else {
    1193                     String value = text.getText();
    1194
    1195                     // only remove empty Text nodes, not whitespace nodes
    1196                     // if ( value == null || value.trim().length() <= 0 ) {
    1197                     if ((value == null) || (value.length() <= 0)) {
    1198                         remove(text);
    1199                     } else {
    1200                         previousText = text;
    1201
    1202                         i++;
    1203                     }
    1204                 }
    1205             } else {
    1206                 if (node instanceof Element) {
    1207                     Element element = (Element) node;
    1208
    1209                     element.normalize();
    1210                 }
    1211
    1212                 previousText = null;
    1213
    1214                 i++;
    1215             }
    1216         }
    1217     }
    1218
    1219     public String elementText(String name) {
    1220         Element element = element(name);
    1221
    1222         return (element != null) ? element.getText() : null;
    1223     }
    1224
    1225     public String elementText(QName qName) {
    1226         Element element = element(qName);
    1227
    1228         return (element != null) ? element.getText() : null;
    1229     }
    1230
    1231     public String elementTextTrim(String name) {
    1232         Element element = element(name);
    1233
    1234         return (element != null) ? element.getTextTrim() : null;
    1235     }
    1236
    1237     public String elementTextTrim(QName qName) {
    1238         Element element = element(qName);
    1239
    1240         return (element != null) ? element.getTextTrim() : null;
    1241     }
    1242
    1243     // add to me content from another element
    1244     // analagous to the addAll(collection) methods in Java 2 collections
    1245     public void appendAttributes(Element element) {
    1246         for (int i = 0, size = element.attributeCount(); i < size; i++) {
    1247             Attribute attribute = element.attribute(i);
    1248
    1249             if (attribute.supportsParent()) {
    1250                 addAttribute(attribute.getQName(), attribute.getValue());
    1251             } else {
    1252                 add(attribute);
    1253             }
    1254         }
    1255     }
    1256
    1257     /***
    1258      * <p>
    1259      * This returns a deep clone of this element. The new element is detached
    1260      * from its parent, and getParent() on the clone will return null.
    1261      * </p>
    1262      *
    1263      * @return the clone of this element
    1264      */
    1265
    1266     /*
    1267      * public Object clone() { Element clone = createElement(getQName());
    1268      * clone.appendAttributes(this); clone.appendContent(this); return clone; }
    1269      */
    1270     public Element createCopy() {
    1271         Element clone = createElement(getQName());
    1272
    1273         clone.appendAttributes(this);
    1274
    1275         clone.appendContent(this);
    1276
    1277         return clone;
    1278     }
    1279
    1280     public Element createCopy(String name) {
    1281         Element clone = createElement(name);
    1282
    1283         clone.appendAttributes(this);
    1284
    1285         clone.appendContent(this);
    1286
    1287         return clone;
    1288     }
    1289
    1290     public Element createCopy(QName qName) {
    1291         Element clone = createElement(qName);
    1292
    1293         clone.appendAttributes(this);
    1294
    1295         clone.appendContent(this);
    1296
    1297         return clone;
    1298     }
    1299
    1300     public QName getQName(String qualifiedName) {
    1301         String prefix = "";
    1302
    1303         String localName = qualifiedName;
    1304
    1305         int index = qualifiedName.indexOf(":");
    1306
    1307         if (index > 0) {
    1308             prefix = qualifiedName.substring(0, index);
    1309
    1310             localName = qualifiedName.substring(index + 1);
    1311         }
    1312
    1313         Namespace namespace = getNamespaceForPrefix(prefix);
    1314
    1315         if (namespace != null) {
    1316             return getDocumentFactory().createQName(localName, namespace);
    1317         } else {
    1318             return getDocumentFactory().createQName(localName);
    1319         }
    1320     }
    1321
    1322     public Namespace getNamespaceForPrefix(String prefix) {
    1323         if (prefix == null) {
    1324             prefix = "";
    1325         }
    1326
    1327         if (prefix.equals(getNamespacePrefix())) {
    1328             return getNamespace();
    1329         } else if (prefix.equals("xml")) {
    1330             return Namespace.XML_NAMESPACE;
    1331         } else {
    1332             List list = contentList();
    1333
    1334             int size = list.size();
    1335
    1336             for (int i = 0; i < size; i++) {
    1337                 Object object = list.get(i);
    1338
    1339                 if (object instanceof Namespace) {
    1340                     Namespace namespace = (Namespace) object;
    1341
    1342                     if (prefix.equals(namespace.getPrefix())) {
    1343                         return namespace;
    1344                     }
    1345                 }
    1346             }
    1347         }
    1348
    1349         Element parent = getParent();
    1350
    1351         if (parent != null) {
    1352             Namespace answer = parent.getNamespaceForPrefix(prefix);
    1353
    1354             if (answer != null) {
    1355                 return answer;
    1356             }
    1357         }
    1358
    1359         if ((prefix == null) || (prefix.length() <= 0)) {
    1360             return Namespace.NO_NAMESPACE;
    1361         }
    1362
    1363         return null;
    1364     }
    1365
    1366     public Namespace getNamespaceForURI(String uri) {
    1367         if ((uri == null) || (uri.length() <= 0)) {
    1368             return Namespace.NO_NAMESPACE;
    1369         } else if (uri.equals(getNamespaceURI())) {
    1370             return getNamespace();
    1371         } else {
    1372             List list = contentList();
    1373
    1374             int size = list.size();
    1375
    1376             for (int i = 0; i < size; i++) {
    1377                 Object object = list.get(i);
    1378
    1379                 if (object instanceof Namespace) {
    1380                     Namespace namespace = (Namespace) object;
    1381
    1382                     if (uri.equals(namespace.getURI())) {
    1383                         return namespace;
    1384                     }
    1385                 }
    1386             }
    1387
    1388             return null;
    1389         }
    1390     }
    1391
    1392     public List getNamespacesForURI(String uri) {
    1393         BackedList answer = createResultList();
    1394
    1395         // if (getNamespaceURI().equals(uri)) {
    1396         //
    1397         // answer.addLocal(getNamespace());
    1398         //
    1399         // }
    1400         List list = contentList();
    1401
    1402         int size = list.size();
    1403
    1404         for (int i = 0; i < size; i++) {
    1405             Object object = list.get(i);
    1406
    1407             if ((object instanceof Namespace)
    1408                     && ((Namespace) object).getURI().equals(uri)) {
    1409                 answer.addLocal(object);
    1410             }
    1411         }
    1412
    1413         return answer;
    1414     }
    1415
    1416     public List declaredNamespaces() {
    1417         BackedList answer = createResultList();
    1418
    1419         // if (getNamespaceURI().length() > 0) {
    1420         //
    1421         // answer.addLocal(getNamespace());
    1422         //
    1423         // }
    1424         //
    1425         List list = contentList();
    1426
    1427         int size = list.size();
    1428
    1429         for (int i = 0; i < size; i++) {
    1430             Object object = list.get(i);
    1431
    1432             if (object instanceof Namespace) {
    1433                 answer.addLocal(object);
    1434             }
    1435         }
    1436
    1437         return answer;
    1438     }
    1439
    1440     public List additionalNamespaces() {
    1441         List list = contentList();
    1442
    1443         int size = list.size();
    1444
    1445         BackedList answer = createResultList();
    1446
    1447         for (int i = 0; i < size; i++) {
    1448             Object object = list.get(i);
    1449
    1450             if (object instanceof Namespace) {
    1451                 Namespace namespace = (Namespace) object;
    1452
    1453                 if (!namespace.equals(getNamespace())) {
    1454                     answer.addLocal(namespace);
    1455                 }
    1456             }
    1457         }
    1458
    1459         return answer;
    1460     }
    1461
    1462     public List additionalNamespaces(String defaultNamespaceURI) {
    1463         List list = contentList();
    1464
    1465         BackedList answer = createResultList();
    1466
    1467         int size = list.size();
    1468
    1469         for (int i = 0; i < size; i++) {
    1470             Object object = list.get(i);
    1471
    1472             if (object instanceof Namespace) {
    1473                 Namespace namespace = (Namespace) object;
    1474
    1475                 if (!defaultNamespaceURI.equals(namespace.getURI())) {
    1476                     answer.addLocal(namespace);
    1477                 }
    1478             }
    1479         }
    1480
    1481         return answer;
    1482     }
    1483
    1484     // Implementation helper methods
    1485     // -------------------------------------------------------------------------
    1486
    1487     /***
    1488      * Ensures that the list of attributes has the given size
    1489      *
    1490      * @param minCapacity
    1491      *            DOCUMENT ME!
    1492      */
    1493     public void ensureAttributesCapacity(int minCapacity) {
    1494         if (minCapacity > 1) {
    1495             List list = attributeList();
    1496
    1497             if (list instanceof ArrayList) {
    1498                 ArrayList arrayList = (ArrayList) list;
    1499
    1500                 arrayList.ensureCapacity(minCapacity);
    1501             }
    1502         }
    1503     }
    1504
    1505     // Implementation methods
    1506     // -------------------------------------------------------------------------
    1507     protected Element createElement(String name) {
    1508         return getDocumentFactory().createElement(name);
    1509     }
    1510
    1511     protected Element createElement(QName qName) {
    1512         return getDocumentFactory().createElement(qName);
    1513     }
    1514
    1515     protected void addNode(Node node) {
    1516         if (node.getParent() != null) {
    1517             // XXX: could clone here
    1518             String message = "The Node already has an existing parent of \""
    1519                     + node.getParent().getQualifiedName() + "\"";
    1520
    1521             throw new IllegalAddException(this, node, message);
    1522         }
    1523
    1524         addNewNode(node);
    1525     }
    1526
    1527     protected void addNode(int index, Node node) {
    1528         if (node.getParent() != null) {
    1529             // XXX: could clone here
    1530             String message = "The Node already has an existing parent of \""
    1531                     + node.getParent().getQualifiedName() + "\"";
    1532
    1533             throw new IllegalAddException(this, node, message);
    1534         }
    1535
    1536         addNewNode(index, node);
    1537     }
    1538
    1539     /***
    1540      * Like addNode() but does not require a parent check
    1541      *
    1542      * @param node
    1543      *            DOCUMENT ME!
    1544      */
    1545     protected void addNewNode(Node node) {
    1546         contentList().add(node);
    1547
    1548         childAdded(node);
    1549     }
    1550
    1551     protected void addNewNode(int index, Node node) {
    1552         contentList().add(index, node);
    1553
    1554         childAdded(node);
    1555     }
    1556
    1557     protected boolean removeNode(Node node) {
    1558         boolean answer = contentList().remove(node);
    1559
    1560         if (answer) {
    1561             childRemoved(node);
    1562         }
    1563
    1564         return answer;
    1565     }
    1566
    1567     /***
    1568      * Called when a new child node is added to create any parent relationships
    1569      *
    1570      * @param node
    1571      *            DOCUMENT ME!
    1572      */
    1573     protected void childAdded(Node node) {
    1574         if (node != null) {
    1575             node.setParent(this);
    1576         }
    1577     }
    1578
    1579     protected void childRemoved(Node node) {
    1580         if (node != null) {
    1581             node.setParent(null);
    1582
    1583             node.setDocument(null);
    1584         }
    1585     }
    1586
    1587     /***
    1588      * DOCUMENT ME!
    1589      *
    1590      * @return the internal List used to store attributes or creates one if one
    1591      *         is not available
    1592      */
    1593     protected abstract List attributeList();
    1594
    1595     /***
    1596      * DOCUMENT ME!
    1597      *
    1598      * @param attributeCount
    1599      *            DOCUMENT ME!
    1600      *
    1601      * @return the internal List used to store attributes or creates one with
    1602      *         the specified size if one is not available
    1603      */
    1604     protected abstract List attributeList(int attributeCount);
    1605
    1606     protected DocumentFactory getDocumentFactory() {
    1607         QName qName = getQName();
    1608
    1609         // QName might be null as we might not have been constructed yet
    1610         if (qName != null) {
    1611             DocumentFactory factory = qName.getDocumentFactory();
    1612
    1613             if (factory != null) {
    1614                 return factory;
    1615             }
    1616         }
    1617
    1618         return DOCUMENT_FACTORY;
    1619     }
    1620
    1621     /***
    1622      * A Factory Method pattern which creates a List implementation used to
    1623      * store attributes
    1624      *
    1625      * @return DOCUMENT ME!
    1626      */
    1627     protected List createAttributeList() {
    1628         return createAttributeList(DEFAULT_CONTENT_LIST_SIZE);
    1629     }
    1630
    1631     /***
    1632      * A Factory Method pattern which creates a List implementation used to
    1633      * store attributes
    1634      *
    1635      * @param size
    1636      *            DOCUMENT ME!
    1637      *
    1638      * @return DOCUMENT ME!
    1639      */
    1640     protected List createAttributeList(int size) {
    1641         return new ArrayList(size);
    1642     }
    1643
    1644     protected Iterator createSingleIterator(Object result) {
    1645         return new SingleIterator(result);
    1646     }
    1647 }
    1648
    1649 /*
    1650  * Redistribution and use of this software and associated documentation
    1651  * ("Software"), with or without modification, are permitted provided that the
    1652  * following conditions are met:
    1653  *
    1654  * 1. Redistributions of source code must retain copyright statements and
    1655  * notices. Redistributions must also contain a copy of this document.
    1656  *
    1657  * 2. Redistributions in binary form must reproduce the above copyright notice,
    1658  * this list of conditions and the following disclaimer in the documentation
    1659  * and/or other materials provided with the distribution.
    1660  *
    1661  * 3. The name "DOM4J" must not be used to endorse or promote products derived
    1662  * from this Software without prior written permission of MetaStuff, Ltd. For
    1663  * written permission, please contact dom4j-info@metastuff.com.
    1664  *
    1665  * 4. Products derived from this Software may not be called "DOM4J" nor may
    1666  * "DOM4J" appear in their names without prior written permission of MetaStuff,
    1667  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
    1668  *
    1669  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
    1670  *
    1671  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
    1672  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    1673  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    1674  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
    1675  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    1676  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    1677  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    1678  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    1679  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    1680  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    1681  * POSSIBILITY OF SUCH DAMAGE.
    1682  *
    1683  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
    1684  */