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  */