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