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: XMLWriter.java,v 1.46 2002/02/14 11:55:46 jstrachan Exp $
008 */
009
010package org.dom4j.io;
011
012import java.io.BufferedOutputStream;
013import java.io.BufferedWriter;
014import java.io.ByteArrayOutputStream;
015import java.io.IOException;
016import java.io.OutputStream;
017import java.io.OutputStreamWriter;
018import java.io.StringWriter;
019import java.io.UnsupportedEncodingException;
020import java.io.Writer;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.StringTokenizer;
029   
030import org.dom4j.Attribute;
031import org.dom4j.CDATA;
032import org.dom4j.CharacterData;
033import org.dom4j.Comment;
034import org.dom4j.DocumentType;
035import org.dom4j.Document;
036import org.dom4j.Element;
037import org.dom4j.Entity;
038import org.dom4j.Namespace;
039import org.dom4j.Node;
040import org.dom4j.ProcessingInstruction;
041import org.dom4j.Text;
042
043import org.dom4j.tree.NamespaceStack;
044
045import org.xml.sax.Attributes;
046import org.xml.sax.ContentHandler;
047import org.xml.sax.DTDHandler;
048import org.xml.sax.InputSource;
049import org.xml.sax.Locator;
050import org.xml.sax.SAXException;
051import org.xml.sax.SAXNotRecognizedException;
052import org.xml.sax.SAXNotSupportedException;
053import org.xml.sax.XMLReader;
054import org.xml.sax.ext.LexicalHandler;
055import org.xml.sax.helpers.XMLFilterImpl;
056
057/**<p><code>XMLWriter</code> takes a DOM4J tree and formats it to a
058  * stream as XML.  
059  * It can also take SAX events too so can be used by SAX clients as this object 
060  * implements the {@link ContentHandler} and {@link LexicalHandler} interfaces.
061  * as well. This formatter performs typical document
062  * formatting.  The XML declaration and processing instructions are
063  * always on their own lines. An {@link OutputFormat} object can be
064  * used to define how whitespace is handled when printing and allows various
065  * configuration options, such as to allow suppression of the XML declaration,
066  * the encoding declaration or whether empty documents are collapsed.</p>
067  *
068  * <p> There are <code>write(...)</code> methods to print any of the
069  * standard DOM4J classes, including <code>Document</code> and
070  * <code>Element</code>, to either a <code>Writer</code> or an
071  * <code>OutputStream</code>.  Warning: using your own
072  * <code>Writer</code> may cause the writer's preferred character
073  * encoding to be ignored.  If you use encodings other than UTF8, we
074  * recommend using the method that takes an OutputStream instead.
075  * </p>
076  *
077  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
078  * @author Joseph Bowbeer
079  * @version $Revision: 1.46 $
080  */
081public class XMLWriter extends XMLFilterImpl implements LexicalHandler {
082
083    protected static final String[] LEXICAL_HANDLER_NAMES = {
084        "http://xml.org/sax/properties/lexical-handler",
085        "http://xml.org/sax/handlers/LexicalHandler"
086    };
087    
088    private static final boolean ESCAPE_TEXT = true;
089    private static final boolean SUPPORT_PAD_TEXT = false;
090    
091    protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat();
092
093    /** Stores the last type of node written so algorithms can refer to the 
094      * previous node type */
095    protected int lastOutputNodeType;
096
097    /** The Writer used to output to */
098    protected Writer writer;
099    
100    /** The Stack of namespaceStack written so far */
101    private NamespaceStack namespaceStack = new NamespaceStack();
102    
103    /** The format used by this writer */
104    private OutputFormat format;
105    /** The initial number of indentations (so you can print a whole
106        document indented, if you like) **/
107    private int indentLevel = 0;
108
109    /** buffer used when escaping strings */
110    private StringBuffer buffer = new StringBuffer();
111
112    /** Whether a flush should occur after writing a document */
113    private boolean autoFlush;
114    
115    /** Lexical handler we should delegate to */
116    private LexicalHandler lexicalHandler;
117
118    /** Whether comments should appear inside DTD declarations - defaults to false */
119    private boolean showCommentsInDTDs;
120    
121    /** Is the writer curerntly inside a DTD definition? */
122    private boolean inDTD;
123    
124    
125    public XMLWriter(Writer writer) {
126        this( writer, DEFAULT_FORMAT );
127    }
128    
129    public XMLWriter(Writer writer, OutputFormat format) {
130        this.writer = writer;
131        this.format = format;
132    }
133    
134    public XMLWriter() {
135        this.format = DEFAULT_FORMAT;
136        this.writer = new BufferedWriter( new OutputStreamWriter( System.out ) );
137        this.autoFlush = true;
138    }
139
140    public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
141        this.format = DEFAULT_FORMAT;
142        this.writer = createWriter(out, format.getEncoding());
143        this.autoFlush = true;
144    }
145    
146    public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException {
147        this.format = format;
148        this.writer = createWriter(out, format.getEncoding());
149        this.autoFlush = true;
150    }
151    
152    public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
153        this.format = format;
154        this.writer = createWriter( System.out, format.getEncoding() );
155        this.autoFlush = true;
156    }
157
158    
159    public void setWriter(Writer writer) {
160        this.writer = writer;
161        this.autoFlush = false;
162    }
163    
164    public void setOutputStream(OutputStream out) throws UnsupportedEncodingException {
165        this.writer = createWriter(out, format.getEncoding());
166        this.autoFlush = true;
167    }
168    
169
170    /** Set the initial indentation level.  This can be used to output
171      * a document (or, more likely, an element) starting at a given
172      * indent level, so it's not always flush against the left margin.
173      * Default: 0
174      *
175      * @param indentLevel the number of indents to start with
176      */
177    public void setIndentLevel(int indentLevel) {
178        this.indentLevel = indentLevel;
179    }
180
181    /** Flushes the underlying Writer */
182    public void flush() throws IOException {
183        writer.flush();
184    }
185    
186    /** Closes the underlying Writer */
187    public void close() throws IOException {
188        writer.close();
189    }
190
191    /** Writes the new line text to the underlying Writer */
192    public void println() throws IOException {
193        writer.write( format.getLineSeparator() );
194    }
195
196    /** Writes the given {@link Attribute}.
197      *
198      * @param attribute <code>Attribute</code> to output.
199      */
200    public void write(Attribute attribute) throws IOException {        
201        writeAttribute(attribute);
202        
203        if ( autoFlush ) {
204            flush();
205        }
206    }
207    
208    
209    /** <p>This will print the <code>Document</code> to the current Writer.</p>
210     *
211     * <p> Warning: using your own Writer may cause the writer's
212     * preferred character encoding to be ignored.  If you use
213     * encodings other than UTF8, we recommend using the method that
214     * takes an OutputStream instead.  </p>
215     *
216     * <p>Note: as with all Writers, you may need to flush() yours
217     * after this method returns.</p>
218     *
219     * @param doc <code>Document</code> to format.
220     * @throws <code>IOException</code> - if there's any problem writing.
221     **/
222    public void write(Document doc) throws IOException {
223        writeDeclaration();
224        
225        if (doc.getDocType() != null) {
226            indent();
227            writeDocType(doc.getDocType());
228        }
229
230        for ( int i = 0, size = doc.nodeCount(); i < size; i++ ) {
231            Node node = doc.node(i);
232            writeNode( node );
233        }
234        writePrintln();
235        
236        if ( autoFlush ) {
237            flush();
238        }
239    }
240
241    /** <p>Writes the <code>{@link Element}</code>, including
242      * its <code>{@link Attribute}</code>s, and its value, and all
243      * its content (child nodes) to the current Writer.</p>
244      *
245      * @param element <code>Element</code> to output.
246      */
247    public void write(Element element) throws IOException {
248        writeElement(element);
249        
250        if ( autoFlush ) {
251            flush();
252        }
253    }
254    
255        
256    /** Writes the given {@link CDATA}.
257      *
258      * @param cdata <code>CDATA</code> to output.
259      */
260    public void write(CDATA cdata) throws IOException {
261        writeCDATA( cdata.getText() );
262        
263        if ( autoFlush ) {
264            flush();
265        }
266    }
267    
268    /** Writes the given {@link Comment}.
269      *
270      * @param comment <code>Comment</code> to output.
271      */
272    public void write(Comment comment) throws IOException {        
273        writeComment( comment.getText() );
274        
275        if ( autoFlush ) {
276            flush();
277        }
278    }
279    
280    /** Writes the given {@link DocumentType}.
281      *
282      * @param docType <code>DocumentType</code> to output.
283      */
284    public void write(DocumentType docType) throws IOException {
285        writeDocType(docType);
286        
287        if ( autoFlush ) {
288            flush();
289        }
290    }
291
292    
293    /** Writes the given {@link Entity}.
294      *
295      * @param entity <code>Entity</code> to output.
296      */
297    public void write(Entity entity) throws IOException {
298        writeEntity( entity );
299        
300        if ( autoFlush ) {
301            flush();
302        }
303    }
304    
305
306    /** Writes the given {@link Namespace}.
307      *
308      * @param namespace <code>Namespace</code> to output.
309      */
310    public void write(Namespace namespace) throws IOException {
311        writeNamespace(namespace);
312        
313        if ( autoFlush ) {
314            flush();
315        }
316    }
317    
318    /** Writes the given {@link ProcessingInstruction}.
319      *
320      * @param processingInstruction <code>ProcessingInstruction</code> to output.
321      */
322    public void write(ProcessingInstruction processingInstruction) throws IOException {
323        writeProcessingInstruction(processingInstruction);
324        
325        if ( autoFlush ) {
326            flush();
327        }
328    }
329    
330    /** <p>Print out a {@link String}, Perfoms
331      * the necessary entity escaping and whitespace stripping.</p>
332      *
333      * @param text is the text to output
334      */
335    public void write(String text) throws IOException {
336        writeString(text);
337        
338        if ( autoFlush ) {
339            flush();
340        }
341    }
342
343    /** Writes the given {@link Text}.
344      *
345      * @param text <code>Text</code> to output.
346      */
347    public void write(Text text) throws IOException {
348        writeString(text.getText());
349        
350        if ( autoFlush ) {
351            flush();
352        }
353    }
354    
355    /** Writes the given {@link Node}.
356      *
357      * @param node <code>Node</code> to output.
358      */
359    public void write(Node node) throws IOException {
360        writeNode(node);
361        
362        if ( autoFlush ) {
363            flush();
364        }
365    }
366    
367    /** Writes the given object which should be a String, a Node or a List
368      * of Nodes.
369      *
370      * @param object is the object to output.
371      */
372    public void write(Object object) throws IOException {
373        if (object instanceof Node) {
374            write((Node) object);
375        }
376        else if (object instanceof String) {
377            write((String) object);
378        }
379        else if (object instanceof List) {
380            List list = (List) object;
381            for ( int i = 0, size = list.size(); i < size; i++ ) {
382                write( list.get(i) );
383            }
384        }
385        else if (object != null) {
386            throw new IOException( "Invalid object: " + object );
387        }
388    }
389    
390    
391    /** <p>Writes the opening tag of an {@link Element}, 
392      * including its {@link Attribute}s
393      * but without its content.</p>
394      *
395      * @param element <code>Element</code> to output.
396      */
397    public void writeOpen(Element element) throws IOException {
398        writer.write("<");
399        writer.write( element.getQualifiedName() );
400        writeAttributes(element);
401        writer.write(">");
402    }
403    
404    /** <p>Writes the closing tag of an {@link Element}</p>
405      *
406      * @param element <code>Element</code> to output.
407      */
408    public void writeClose(Element element) throws IOException {
409        writeClose( element.getQualifiedName() );
410    }
411
412    
413    // XMLFilterImpl methods
414    //-------------------------------------------------------------------------
415    public void parse(InputSource source) throws IOException, SAXException {
416        installLexicalHandler();
417        super.parse(source);
418    }
419    
420
421    public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
422        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
423            if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
424                setLexicalHandler((LexicalHandler) value);
425                return;
426            }
427        }
428        super.setProperty(name, value);
429    }
430
431    public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
432        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
433            if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
434                return getLexicalHandler();
435            }
436        }
437        return super.getProperty(name);
438    }
439
440    public void setLexicalHandler (LexicalHandler handler) {
441        if (handler == null) {
442            throw new NullPointerException("Null lexical handler");
443        } 
444        else {
445            this.lexicalHandler = handler;
446        }
447    }
448
449    public LexicalHandler getLexicalHandler(){
450        return lexicalHandler;
451    }
452    
453    
454    // ContentHandler interface
455    //-------------------------------------------------------------------------
456    public void setDocumentLocator(Locator locator) {
457        super.setDocumentLocator(locator);
458    }
459    
460    public void startDocument() throws SAXException {
461        try {
462            writeDeclaration();
463            super.startDocument();
464        }
465        catch (IOException e) {
466            handleException(e);
467        }
468    }
469    
470    public void endDocument() throws SAXException {
471        super.endDocument();
472    }
473    
474    public void startPrefixMapping(String prefix, String uri) throws SAXException {
475        super.startPrefixMapping(prefix, uri);
476    }
477    
478    public void endPrefixMapping(String prefix) throws SAXException {
479        super.endPrefixMapping(prefix);
480    }
481    
482    
483    public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException {
484        try {
485            writePrintln();
486            indent();
487            writer.write("<");
488            writer.write(qName);
489            writeAttributes( attributes );
490            writer.write(">");
491            ++indentLevel;
492            lastOutputNodeType = Node.ELEMENT_NODE;
493            
494            super.startElement( namespaceURI, localName, qName, attributes );
495        }
496        catch (IOException e) {
497            handleException(e);
498        }
499    }
500    
501    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
502        try {
503            --indentLevel;
504            if ( lastOutputNodeType == Node.ELEMENT_NODE ) {
505                writePrintln();
506                indent();
507            }
508            
509            // XXXX: need to determine this using a stack and checking for
510            // content / children
511            boolean hadContent = true;
512            if ( hadContent ) {
513                writeClose(qName);
514            }
515            else {
516                writeEmptyElementClose(qName);
517            }
518            lastOutputNodeType = Node.ELEMENT_NODE;
519    
520            super.endElement( namespaceURI, localName, qName );
521        }
522        catch (IOException e) {
523            handleException(e);
524        }
525    }
526    
527    public void characters(char[] ch, int start, int length) throws SAXException {
528        try {
529            write( new String( ch, start, length ) );
530            
531            super.characters(ch, start, length);
532        }
533        catch (IOException e) {
534            handleException(e);
535        }
536    }
537
538    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
539        super.ignorableWhitespace(ch, start, length);
540    }
541    
542    public void processingInstruction(String target, String data) throws SAXException {
543        try {
544            indent();
545            writer.write("<?");
546            writer.write(target);
547            writer.write(" ");
548            writer.write(data);
549            writer.write("?>");
550            writePrintln();
551            lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
552            
553            super.processingInstruction(target, data);
554        }
555        catch (IOException e) {
556            handleException(e);
557        }
558    }
559    
560    
561
562    // DTDHandler interface
563    //-------------------------------------------------------------------------
564    public void notationDecl(String name, String publicID, String systemID) throws SAXException {
565        super.notationDecl(name, publicID, systemID);
566    }
567    
568    public void unparsedEntityDecl(String name, String publicID, String systemID, String notationName) throws SAXException {        
569        super.unparsedEntityDecl(name, publicID, systemID, notationName);
570    }
571    
572
573    // LexicalHandler interface
574    //-------------------------------------------------------------------------
575    public void startDTD(String name, String publicID, String systemID) throws SAXException {
576        inDTD = true;        
577        try {
578            writeDocType(name, publicID, systemID);
579        }
580        catch (IOException e) {
581            handleException(e);
582        }
583        
584        if (lexicalHandler != null) {
585            lexicalHandler.startDTD(name, publicID, systemID);
586        }
587    }
588    
589    public void endDTD() throws SAXException {               
590        inDTD = false;
591        if (lexicalHandler != null) {
592            lexicalHandler.endDTD();
593        }
594    }
595    
596    public void startCDATA() throws SAXException {
597        try {
598            writer.write( "<![CDATA[" );
599        }
600        catch (IOException e) {
601            handleException(e);
602        }
603                
604        if (lexicalHandler != null) {
605            lexicalHandler.startCDATA();
606        }
607    }
608    
609    public void endCDATA() throws SAXException {
610        try {
611            writer.write( "]]>" );
612        }
613        catch (IOException e) {
614            handleException(e);
615        }
616                
617        if (lexicalHandler != null) {
618            lexicalHandler.endCDATA();
619        }
620    }
621    
622    public void startEntity(String name) throws SAXException {
623        try {
624            writeEntityRef(name);
625        }
626        catch (IOException e) {
627            handleException(e);
628        }
629                
630        if (lexicalHandler != null) {
631            lexicalHandler.startEntity(name);
632        }
633    }
634    
635    public void endEntity(String name) throws SAXException {                            
636        if (lexicalHandler != null) {
637            lexicalHandler.endEntity(name);
638        }
639    }
640    
641    public void comment(char[] ch, int start, int length) throws SAXException {
642        if ( showCommentsInDTDs || ! inDTD ) {
643            try {
644                writeComment( new String(ch, start, length) );
645            }
646            catch (IOException e) {
647                handleException(e);
648            }
649        }
650                
651        if (lexicalHandler != null) {
652            lexicalHandler.comment(ch, start, length);
653        }
654    }
655    
656    
657    
658    // Implementation methods
659    //-------------------------------------------------------------------------    
660    protected void writeElement(Element element) throws IOException {
661        int size = element.nodeCount();
662        String qualifiedName = element.getQualifiedName();
663        
664        writePrintln();
665        indent();
666        
667        writer.write("<");
668        writer.write(qualifiedName);
669        
670        int previouslyDeclaredNamespaces = namespaceStack.size();
671        Namespace ns = element.getNamespace();
672        if (isNamespaceDeclaration( ns ) ) {
673            namespaceStack.push(ns);
674            writeNamespace(ns);
675        }
676
677        // Print out additional namespace declarations
678        boolean textOnly = true;
679        for ( int i = 0; i < size; i++ ) {
680            Node node = element.node(i);
681            if ( node instanceof Namespace ) {
682                Namespace additional = (Namespace) node;
683                if (isNamespaceDeclaration( additional ) ) {
684                    namespaceStack.push(additional);
685                    writeNamespace(additional);
686                }
687            }
688            else if ( node instanceof Element) {
689                textOnly = false;
690            }
691        }
692
693        writeAttributes(element);
694
695        lastOutputNodeType = Node.ELEMENT_NODE;
696        
697        if ( size <= 0 ) {
698            writeEmptyElementClose(qualifiedName);
699        }
700        else {
701            writer.write(">");
702            if ( textOnly ) {
703                // we have at least one text node so lets assume
704                // that its non-empty
705                writeElementContent(element);
706            }
707            else {
708                // we know it's not null or empty from above
709                ++indentLevel;
710                
711                writeElementContent(element);
712                
713                --indentLevel;                
714
715                writePrintln();
716                indent();
717            }
718            writer.write("</");
719            writer.write(qualifiedName);
720            writer.write(">");
721        }
722
723        // remove declared namespaceStack from stack
724        while (namespaceStack.size() > previouslyDeclaredNamespaces) {
725            namespaceStack.pop();
726        }
727        
728        lastOutputNodeType = Node.ELEMENT_NODE;
729    }
730    
731    /** Outputs the content of the given element. If whitespace trimming is
732     * enabled then all adjacent text nodes are appended together before
733     * the whitespace trimming occurs to avoid problems with multiple 
734     * text nodes being created due to text content that spans parser buffers 
735     * in a SAX parser.
736     */
737    protected void writeElementContent(Element element) throws IOException {
738        if (format.isTrimText()) {
739            // concatenate adjacent text nodes together 
740            // so that whitespace trimming works properly
741            Text lastTextNode = null;
742            StringBuffer buffer = null;
743            for ( int i = 0, size = element.nodeCount(); i < size; i++ ) {
744                Node node = element.node(i);
745                if ( node instanceof Text ) {
746                    if ( lastTextNode == null ) {
747                        lastTextNode = (Text) node;
748                    }
749                    else {
750                        buffer = new StringBuffer( lastTextNode.getText() );
751                        buffer.append( ((Text) node).getText() );
752                    }
753                }
754                else {
755                    if ( lastTextNode != null ) { 
756                        if ( buffer != null ) {
757                            writeString( buffer.toString() );
758                            buffer = null;
759                        }
760                        else {
761                            writeString( lastTextNode.getText() );
762                        }
763                        lastTextNode = null;
764                    }
765                    writeNode(node);
766                }
767            }
768            if ( lastTextNode != null ) { 
769                if ( buffer != null ) {
770                    writeString( buffer.toString() );
771                    buffer = null;
772                }
773                else {
774                    writeString( lastTextNode.getText() );
775                }
776                lastTextNode = null;
777            }
778        }
779        else {
780            for ( int i = 0, size = element.nodeCount(); i < size; i++ ) {
781                Node node = element.node(i);
782                writeNode(node);
783            }
784        }
785    }
786    protected void writeCDATA(String text) throws IOException {
787        writer.write( "<![CDATA[" );
788        writer.write( text );
789        writer.write( "]]>" );
790        
791        lastOutputNodeType = Node.CDATA_SECTION_NODE;
792    }
793
794    protected void writeDocType(DocumentType docType) throws IOException {
795        if (docType != null) {
796            docType.write( writer );
797            //writeDocType( docType.getElementName(), docType.getPublicID(), docType.getSystemID() );
798            writePrintln();
799        }
800    }
801
802    
803    protected void writeNamespace(Namespace namespace) throws IOException {
804        if ( namespace != null ) {
805            String prefix = namespace.getPrefix();
806            writer.write(" xmlns");
807            if (prefix != null && prefix.length() > 0) {
808                writer.write(":");
809                writer.write(prefix);
810            }
811            writer.write("=\"");
812            writer.write(namespace.getURI());
813            writer.write("\"");
814        }
815    }
816
817    protected void writeProcessingInstruction(ProcessingInstruction processingInstruction) throws IOException {
818        //indent();
819        writer.write( "<?" );
820        writer.write( processingInstruction.getName() );
821        writer.write( " " );
822        writer.write( processingInstruction.getText() );
823        writer.write( "?>" );
824        writePrintln();
825        
826        lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
827    }
828    
829    protected void writeString(String text) throws IOException {
830        if ( text != null && text.length() > 0 ) {
831            if ( ESCAPE_TEXT ) {
832                text = escapeElementEntities(text);
833            }
834            
835            if ( SUPPORT_PAD_TEXT ) {
836                if (lastOutputNodeType == Node.ELEMENT_NODE) {
837                    String padText = getPadText();
838                    if ( padText != null ) {
839                        writer.write(padText);
840                    }
841                }
842            }
843            
844            if (format.isTrimText()) {
845                boolean first = true;
846                StringTokenizer tokenizer = new StringTokenizer(text);
847                while (tokenizer.hasMoreTokens()) {
848                    String token = tokenizer.nextToken();
849                    if ( first ) {
850                        first = false;
851                        if ( lastOutputNodeType == Node.TEXT_NODE ) { 
852                            writer.write(" ");
853                        }
854                    }
855                    else {
856                        writer.write(" ");
857                    }
858                    writer.write(token);
859                    lastOutputNodeType = Node.TEXT_NODE;
860                }
861            } 
862            else {                    
863                lastOutputNodeType = Node.TEXT_NODE;
864                writer.write(text);
865            }            
866        }
867    }
868
869        
870    protected void writeNode(Node node) throws IOException {
871        int nodeType = node.getNodeType();
872        switch (nodeType) {
873            case Node.ELEMENT_NODE:
874                writeElement((Element) node);
875                break;
876            case Node.ATTRIBUTE_NODE:
877                writeAttribute((Attribute) node);
878                break;
879            case Node.TEXT_NODE:
880                writeString(node.getText());
881                //write((Text) node);
882                break;
883            case Node.CDATA_SECTION_NODE:
884                writeCDATA(node.getText());
885                break;
886            case Node.ENTITY_REFERENCE_NODE:
887                writeEntity((Entity) node);
888                break;
889            case Node.PROCESSING_INSTRUCTION_NODE:
890                writeProcessingInstruction((ProcessingInstruction) node);
891                break;
892            case Node.COMMENT_NODE:
893                writeComment(node.getText());
894                break;
895            case Node.DOCUMENT_NODE:
896                write((Document) node);
897                break;                
898            case Node.DOCUMENT_TYPE_NODE:
899                writeDocType((DocumentType) node);
900                break;
901            case Node.NAMESPACE_NODE:
902                // Will be output with attributes
903                //write((Namespace) node);
904                break;
905            default:
906                throw new IOException( "Invalid node type: " + node );
907        }
908    }
909    
910    
911    
912    
913    protected void installLexicalHandler() {
914        XMLReader parent = getParent();
915        if (parent == null) {
916            throw new NullPointerException("No parent for filter");
917        }
918        // try to register for lexical events
919        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
920            try {
921                parent.setProperty(LEXICAL_HANDLER_NAMES[i], this);
922                break;
923            }
924            catch (SAXNotRecognizedException ex) {
925                // ignore
926            }
927            catch (SAXNotSupportedException ex) {
928                // ignore
929            }
930        }
931    }
932
933    protected void writeDocType(String name, String publicID, String systemID) throws IOException {
934        boolean hasPublic = false;
935
936        writer.write("<!DOCTYPE ");
937        writer.write(name);
938        if ((publicID != null) && (!publicID.equals(""))) {
939            writer.write(" PUBLIC \"");
940            writer.write(publicID);
941            writer.write("\"");
942            hasPublic = true;
943        }
944        if ((systemID != null) && (!systemID.equals(""))) {
945            if (!hasPublic) {
946                writer.write(" SYSTEM");
947            }
948            writer.write(" \"");
949            writer.write(systemID);
950            writer.write("\"");
951        }
952        writer.write(">");
953        writePrintln();
954    }
955
956    protected void writeEntity(Entity entity) throws IOException {
957        writeEntityRef( entity.getName() );
958    }
959    
960    protected void writeEntityRef(String name) throws IOException {
961        writer.write( "&" );
962        writer.write( name );
963        writer.write( ";" );
964        
965        lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
966    }
967    
968    protected void writeComment(String text) throws IOException {        
969        if (format.isNewlines()) {
970            if ( lastOutputNodeType != Node.COMMENT_NODE ) {
971                println();
972            }
973            indent();
974        }
975        writer.write( "<!--" );
976        writer.write( text );
977        writer.write( "-->" );
978        
979        writePrintln();
980
981        lastOutputNodeType = Node.COMMENT_NODE;
982    }
983    
984    /** Writes the attributes of the given element
985      *
986      */
987    protected void writeAttributes( Element element ) throws IOException {
988
989        // I do not yet handle the case where the same prefix maps to
990        // two different URIs. For attributes on the same element
991        // this is illegal; but as yet we don't throw an exception
992        // if someone tries to do this
993        for ( int i = 0, size = element.attributeCount(); i < size; i++ ) {
994            Attribute attribute = element.attribute(i);
995            Namespace ns = attribute.getNamespace();
996            if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) {
997                String prefix = ns.getPrefix();           
998                String uri = namespaceStack.getURI(prefix);
999                if (!ns.getURI().equals(uri)) { // output a new namespace declaration
1000                    writeNamespace(ns);
1001                    namespaceStack.push(ns);
1002                }
1003            }
1004            
1005            writer.write(" ");
1006            writer.write(attribute.getQualifiedName());
1007            writer.write("=\"");            
1008            writeEscapeAttributeEntities(attribute.getValue());            
1009            writer.write("\"");
1010        }
1011    }
1012
1013    protected void writeAttribute(Attribute attribute) throws IOException {        
1014        writer.write(" ");
1015        writer.write(attribute.getQualifiedName());
1016        writer.write("=");
1017
1018        writer.write("\"");
1019        
1020        writeEscapeAttributeEntities(attribute.getValue());
1021        
1022        writer.write("\"");
1023        lastOutputNodeType = Node.ATTRIBUTE_NODE;
1024    }
1025
1026    protected void writeAttributes(Attributes attributes) throws IOException {
1027        for (int i = 0, size = attributes.getLength(); i < size; i++) {
1028            writeAttribute( attributes, i );
1029        }
1030    }
1031
1032    protected void writeAttribute(Attributes attributes, int index) throws IOException {       
1033        writer.write(" ");
1034        writer.write(attributes.getQName(index));
1035        writer.write("=\"");        
1036        writeEscapeAttributeEntities(attributes.getValue(index));
1037        writer.write("\"");
1038    }
1039
1040    
1041    
1042    protected void indent() throws IOException {
1043        String indent = format.getIndent();
1044        if ( indent != null && indent.length() > 0 ) {
1045            for ( int i = 0; i < indentLevel; i++ ) {
1046                writer.write(indent);
1047            }
1048        }
1049    }
1050    
1051    /**
1052     * <p>
1053     * This will print a new line only if the newlines flag was set to true
1054     * </p>
1055     *
1056     * @param out <code>Writer</code> to write to
1057     */
1058    protected void writePrintln() throws IOException  {
1059        if (format.isNewlines()) {
1060            writer.write( format.getLineSeparator() );
1061        }
1062    }
1063
1064    /**
1065     * Get an OutputStreamWriter, use preferred encoding.
1066     */
1067    protected Writer createWriter(OutputStream outStream, String encoding) throws UnsupportedEncodingException {
1068        return new BufferedWriter( 
1069            new OutputStreamWriter( outStream, encoding )
1070        );
1071    }
1072
1073    /**
1074     * <p>
1075     * This will write the declaration to the given Writer.
1076     *   Assumes XML version 1.0 since we don't directly know.
1077     * </p>
1078     */
1079    protected void writeDeclaration() throws IOException {
1080        String encoding = format.getEncoding();
1081        
1082        // Only print of declaration is not suppressed
1083        if (! format.isSuppressDeclaration()) {
1084            // Assume 1.0 version
1085            if (encoding.equals("UTF8")) {
1086                writer.write("<?xml version=\"1.0\"");
1087                if (!format.isOmitEncoding()) {
1088                    writer.write(" encoding=\"UTF-8\"");
1089                }
1090                writer.write("?>");
1091            } else {
1092                writer.write("<?xml version=\"1.0\"");
1093                if (! format.isOmitEncoding()) {
1094                    writer.write(" encoding=\"" + encoding + "\"");
1095                }
1096                writer.write("?>");
1097            }
1098            println();
1099        }        
1100    }    
1101
1102    protected void writeClose(String qualifiedName) throws IOException {
1103        writer.write("</");
1104        writer.write(qualifiedName);
1105        writer.write(">");
1106    }
1107
1108    protected void writeEmptyElementClose(String qualifiedName) throws IOException {
1109        // Simply close up
1110        if (! isExpandEmptyElements()) {
1111            writer.write("/>");
1112        } else {
1113            writer.write("></");
1114            writer.write(qualifiedName);
1115            writer.write(">");
1116        }
1117    }
1118
1119    protected boolean isExpandEmptyElements() {
1120        return format.isExpandEmptyElements();
1121    }
1122
1123
1124    /** This will take the pre-defined entities in XML 1.0 and
1125      * convert their character representation to the appropriate
1126      * entity reference, suitable for XML attributes.
1127      */
1128    protected String escapeElementEntities(String text) {
1129        char[] block = null;
1130        int i, last = 0, size = text.length();
1131        for ( i = 0; i < size; i++ ) {
1132            String entity = null;
1133            switch( text.charAt(i) ) {
1134                case '<' :
1135                    entity = "&lt;";
1136                    break;
1137                case '>' :
1138                    entity = "&gt;";
1139                    break;
1140                case '&' :
1141                    entity = "&amp;";
1142                    break;
1143            }
1144            if (entity != null) {
1145                if ( block == null ) {
1146                    block = text.toCharArray();
1147                }
1148                buffer.append(block, last, i - last);
1149                buffer.append(entity);
1150                last = i + 1;
1151            }
1152        }
1153        if ( last == 0 ) {
1154            return text;
1155        }
1156        if ( last < size ) {
1157            if ( block == null ) {
1158                block = text.toCharArray();
1159            }
1160            buffer.append(block, last, i - last);
1161        }
1162        String answer = buffer.toString();
1163        buffer.setLength(0);
1164        return answer;
1165    }
1166    
1167    protected void writeEscapeAttributeEntities(String text) throws IOException {
1168        if ( text != null ) {
1169            String escapedText = escapeAttributeEntities( text );
1170            writer.write( escapedText );
1171        }
1172    }
1173    /** This will take the pre-defined entities in XML 1.0 and
1174      * convert their character representation to the appropriate
1175      * entity reference, suitable for XML attributes.
1176      */
1177    protected String escapeAttributeEntities(String text) {
1178        char[] block = null;
1179        int i, last = 0, size = text.length();
1180        for ( i = 0; i < size; i++ ) {
1181            String entity = null;
1182            switch( text.charAt(i) ) {
1183                case '<' :
1184                    entity = "&lt;";
1185                    break;
1186                case '>' :
1187                    entity = "&gt;";
1188                    break;
1189                case '\'' :
1190                    entity = "&apos;";
1191                    break;
1192                case '\"' :
1193                    entity = "&quot;";
1194                    break;
1195                case '&' :
1196                    entity = "&amp;";
1197                    break;
1198            }
1199            if (entity != null) {
1200                if ( block == null ) {
1201                    block = text.toCharArray();
1202                }
1203                buffer.append(block, last, i - last);
1204                buffer.append(entity);
1205                last = i + 1;
1206            }
1207        }
1208        if ( last == 0 ) {
1209            return text;
1210        }
1211        if ( last < size ) {
1212            if ( block == null ) {
1213                block = text.toCharArray();
1214            }
1215            buffer.append(block, last, i - last);
1216        }
1217        String answer = buffer.toString();
1218        buffer.setLength(0);
1219        return answer;
1220    }
1221
1222    protected boolean isNamespaceDeclaration( Namespace ns ) {
1223        if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) {
1224            String uri = ns.getURI();
1225            if ( uri != null && uri.length() > 0 ) {
1226                if ( ! namespaceStack.contains( ns ) ) {
1227                    return true;
1228
1229                }
1230            }
1231        }
1232        return false;
1233    }
1234    
1235    protected void handleException(IOException e) throws SAXException {
1236        throw new SAXException(e);
1237    }
1238    
1239    protected String getPadText() {
1240        return null;
1241    }
1242}
1243
1244
1245
1246
1247/*
1248 * Redistribution and use of this software and associated documentation
1249 * ("Software"), with or without modification, are permitted provided
1250 * that the following conditions are met:
1251 *
1252 * 1. Redistributions of source code must retain copyright
1253 *    statements and notices.  Redistributions must also contain a
1254 *    copy of this document.
1255 *
1256 * 2. Redistributions in binary form must reproduce the
1257 *    above copyright notice, this list of conditions and the
1258 *    following disclaimer in the documentation and/or other
1259 *    materials provided with the distribution.
1260 *
1261 * 3. The name "DOM4J" must not be used to endorse or promote
1262 *    products derived from this Software without prior written
1263 *    permission of MetaStuff, Ltd.  For written permission,
1264 *    please contact dom4j-info@metastuff.com.
1265 *
1266 * 4. Products derived from this Software may not be called "DOM4J"
1267 *    nor may "DOM4J" appear in their names without prior written
1268 *    permission of MetaStuff, Ltd. DOM4J is a registered
1269 *    trademark of MetaStuff, Ltd.
1270 *
1271 * 5. Due credit should be given to the DOM4J Project
1272 *    (http://dom4j.org/).
1273 *
1274 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
1275 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
1276 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
1277 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
1278 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
1279 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1280 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
1281 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
1282 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
1283 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1284 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
1285 * OF THE POSSIBILITY OF SUCH DAMAGE.
1286 *
1287 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
1288 *
1289 * $Id: XMLWriter.java,v 1.46 2002/02/14 11:55:46 jstrachan Exp $
1290 */