KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > util > XMLWriter


1 package org.jivesoftware.util;
2
3 import org.dom4j.*;
4 import org.dom4j.io.OutputFormat;
5 import org.dom4j.tree.NamespaceStack;
6 import org.xml.sax.*;
7 import org.xml.sax.ext.LexicalHandler JavaDoc;
8 import org.xml.sax.helpers.XMLFilterImpl JavaDoc;
9
10 import java.io.*;
11 import java.util.*;
12
13 /**
14  * Replacement class of the original XMLWriter.java (version: 1.77) since the original is still
15  * using StringBuffer which is not fast.
16  */

17 public class XMLWriter extends XMLFilterImpl JavaDoc implements LexicalHandler JavaDoc {
18
19     private static final String JavaDoc PAD_TEXT = " ";
20
21     protected static final String JavaDoc[] LEXICAL_HANDLER_NAMES = {
22         "http://xml.org/sax/properties/lexical-handler",
23         "http://xml.org/sax/handlers/LexicalHandler"
24     };
25
26     protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat();
27
28     /** Should entityRefs by resolved when writing ? */
29     private boolean resolveEntityRefs = true;
30
31     /** Stores the last type of node written so algorithms can refer to the
32       * previous node type */

33     protected int lastOutputNodeType;
34
35     /** Stores the xml:space attribute value of preserve for whitespace flag */
36     protected boolean preserve=false;
37
38     /** The Writer used to output to */
39     protected Writer writer;
40
41     /** The Stack of namespaceStack written so far */
42     private NamespaceStack namespaceStack = new NamespaceStack();
43
44     /** The format used by this writer */
45     private OutputFormat format;
46
47     /** whether we should escape text */
48     private boolean escapeText = true;
49     /** The initial number of indentations (so you can print a whole
50         document indented, if you like) **/

51     private int indentLevel = 0;
52
53     /** buffer used when escaping strings */
54     private StringBuilder JavaDoc buffer = new StringBuilder JavaDoc();
55
56     /** whether we have added characters before from the same chunk of characters */
57     private boolean charactersAdded = false;
58     private char lastChar;
59
60     /** Whether a flush should occur after writing a document */
61     private boolean autoFlush;
62
63     /** Lexical handler we should delegate to */
64     private LexicalHandler JavaDoc lexicalHandler;
65
66     /** Whether comments should appear inside DTD declarations - defaults to false */
67     private boolean showCommentsInDTDs;
68
69     /** Is the writer curerntly inside a DTD definition? */
70     private boolean inDTD;
71
72     /** The namespaces used for the current element when consuming SAX events */
73     private Map namespacesMap;
74
75     /**
76      * what is the maximum allowed character code
77      * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit)
78      * or -1 to not escape any characters (other than the special XML characters like < > &)
79      */

80     private int maximumAllowedCharacter;
81
82     public XMLWriter(Writer writer) {
83         this( writer, DEFAULT_FORMAT );
84     }
85
86     public XMLWriter(Writer writer, OutputFormat format) {
87         this.writer = writer;
88         this.format = format;
89         namespaceStack.push(Namespace.NO_NAMESPACE);
90     }
91
92     public XMLWriter() {
93         this.format = DEFAULT_FORMAT;
94         this.writer = new BufferedWriter( new OutputStreamWriter( System.out ) );
95         this.autoFlush = true;
96         namespaceStack.push(Namespace.NO_NAMESPACE);
97     }
98
99     public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
100         this.format = DEFAULT_FORMAT;
101         this.writer = createWriter(out, format.getEncoding());
102         this.autoFlush = true;
103         namespaceStack.push(Namespace.NO_NAMESPACE);
104     }
105
106     public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException {
107         this.format = format;
108         this.writer = createWriter(out, format.getEncoding());
109         this.autoFlush = true;
110         namespaceStack.push(Namespace.NO_NAMESPACE);
111     }
112
113     public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
114         this.format = format;
115         this.writer = createWriter( System.out, format.getEncoding() );
116         this.autoFlush = true;
117         namespaceStack.push(Namespace.NO_NAMESPACE);
118     }
119
120     public void setWriter(Writer writer) {
121         this.writer = writer;
122         this.autoFlush = false;
123     }
124
125     public void setOutputStream(OutputStream out) throws UnsupportedEncodingException {
126         this.writer = createWriter(out, format.getEncoding());
127         this.autoFlush = true;
128     }
129
130     /**
131      * @return true if text thats output should be escaped.
132      * This is enabled by default. It could be disabled if
133      * the output format is textual, like in XSLT where we can have
134      * xml, html or text output.
135      */

136     public boolean isEscapeText() {
137         return escapeText;
138     }
139
140     /**
141      * Sets whether text output should be escaped or not.
142      * This is enabled by default. It could be disabled if
143      * the output format is textual, like in XSLT where we can have
144      * xml, html or text output.
145      */

146     public void setEscapeText(boolean escapeText) {
147         this.escapeText = escapeText;
148     }
149
150
151     /** Set the initial indentation level. This can be used to output
152       * a document (or, more likely, an element) starting at a given
153       * indent level, so it's not always flush against the left margin.
154       * Default: 0
155       *
156       * @param indentLevel the number of indents to start with
157       */

158     public void setIndentLevel(int indentLevel) {
159         this.indentLevel = indentLevel;
160     }
161
162     /**
163      * Returns the maximum allowed character code that should be allowed
164      * unescaped which defaults to 127 in US-ASCII (7 bit) or
165      * 255 in ISO-* (8 bit).
166      */

167     public int getMaximumAllowedCharacter() {
168         if (maximumAllowedCharacter == 0) {
169             maximumAllowedCharacter = defaultMaximumAllowedCharacter();
170         }
171         return maximumAllowedCharacter;
172     }
173
174     /**
175      * Sets the maximum allowed character code that should be allowed
176      * unescaped
177      * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit)
178      * or -1 to not escape any characters (other than the special XML characters like < > &)
179      *
180      * If this is not explicitly set then it is defaulted from the encoding.
181      *
182      * @param maximumAllowedCharacter The maximumAllowedCharacter to set
183      */

184     public void setMaximumAllowedCharacter(int maximumAllowedCharacter) {
185         this.maximumAllowedCharacter = maximumAllowedCharacter;
186     }
187
188     /** Flushes the underlying Writer */
189     public void flush() throws IOException {
190         writer.flush();
191     }
192
193     /** Closes the underlying Writer */
194     public void close() throws IOException {
195         writer.close();
196     }
197
198     /** Writes the new line text to the underlying Writer */
199     public void println() throws IOException {
200         writer.write( format.getLineSeparator() );
201     }
202
203     /** Writes the given {@link org.dom4j.Attribute}.
204       *
205       * @param attribute <code>Attribute</code> to output.
206       */

207     public void write(Attribute attribute) throws IOException {
208         writeAttribute(attribute);
209
210         if ( autoFlush ) {
211             flush();
212         }
213     }
214
215
216     /** <p>This will print the <code>Document</code> to the current Writer.</p>
217      *
218      * <p> Warning: using your own Writer may cause the writer's
219      * preferred character encoding to be ignored. If you use
220      * encodings other than UTF8, we recommend using the method that
221      * takes an OutputStream instead. </p>
222      *
223      * <p>Note: as with all Writers, you may need to flush() yours
224      * after this method returns.</p>
225      *
226      * @param doc <code>Document</code> to format.
227      * @throws IOException - if there's any problem writing.
228      **/

229     public void write(Document doc) throws IOException {
230         writeDeclaration();
231
232         if (doc.getDocType() != null) {
233             indent();
234             writeDocType(doc.getDocType());
235         }
236
237         for ( int i = 0, size = doc.nodeCount(); i < size; i++ ) {
238             Node node = doc.node(i);
239             writeNode( node );
240         }
241         writePrintln();
242
243         if ( autoFlush ) {
244             flush();
245         }
246     }
247
248     /** <p>Writes the <code>{@link org.dom4j.Element}</code>, including
249       * its <code>{@link Attribute}</code>s, and its value, and all
250       * its content (child nodes) to the current Writer.</p>
251       *
252       * @param element <code>Element</code> to output.
253       */

254     public void write(Element element) throws IOException {
255         writeElement(element);
256
257         if ( autoFlush ) {
258             flush();
259         }
260     }
261
262
263     /** Writes the given {@link CDATA}.
264       *
265       * @param cdata <code>CDATA</code> to output.
266       */

267     public void write(CDATA cdata) throws IOException {
268         writeCDATA( cdata.getText() );
269
270         if ( autoFlush ) {
271             flush();
272         }
273     }
274
275     /** Writes the given {@link Comment}.
276       *
277       * @param comment <code>Comment</code> to output.
278       */

279     public void write(Comment comment) throws IOException {
280         writeComment( comment.getText() );
281
282         if ( autoFlush ) {
283             flush();
284         }
285     }
286
287     /** Writes the given {@link DocumentType}.
288       *
289       * @param docType <code>DocumentType</code> to output.
290       */

291     public void write(DocumentType docType) throws IOException {
292         writeDocType(docType);
293
294         if ( autoFlush ) {
295             flush();
296         }
297     }
298
299
300     /** Writes the given {@link Entity}.
301       *
302       * @param entity <code>Entity</code> to output.
303       */

304     public void write(Entity entity) throws IOException {
305         writeEntity( entity );
306
307         if ( autoFlush ) {
308             flush();
309         }
310     }
311
312
313     /** Writes the given {@link Namespace}.
314       *
315       * @param namespace <code>Namespace</code> to output.
316       */

317     public void write(Namespace namespace) throws IOException {
318         writeNamespace(namespace);
319
320         if ( autoFlush ) {
321             flush();
322         }
323     }
324
325     /** Writes the given {@link ProcessingInstruction}.
326       *
327       * @param processingInstruction <code>ProcessingInstruction</code> to output.
328       */

329     public void write(ProcessingInstruction processingInstruction) throws IOException {
330         writeProcessingInstruction(processingInstruction);
331
332         if ( autoFlush ) {
333             flush();
334         }
335     }
336
337     /** <p>Print out a {@link String}, Perfoms
338       * the necessary entity escaping and whitespace stripping.</p>
339       *
340       * @param text is the text to output
341       */

342     public void write(String JavaDoc text) throws IOException {
343         writeString(text);
344
345         if ( autoFlush ) {
346             flush();
347         }
348     }
349
350     /** Writes the given {@link Text}.
351       *
352       * @param text <code>Text</code> to output.
353       */

354     public void write(Text text) throws IOException {
355         writeString(text.getText());
356
357         if ( autoFlush ) {
358             flush();
359         }
360     }
361
362     /** Writes the given {@link Node}.
363       *
364       * @param node <code>Node</code> to output.
365       */

366     public void write(Node node) throws IOException {
367         writeNode(node);
368
369         if ( autoFlush ) {
370             flush();
371         }
372     }
373
374     /** Writes the given object which should be a String, a Node or a List
375       * of Nodes.
376       *
377       * @param object is the object to output.
378       */

379     public void write(Object JavaDoc object) throws IOException {
380         if (object instanceof Node) {
381             write((Node) object);
382         }
383         else if (object instanceof String JavaDoc) {
384             write((String JavaDoc) object);
385         }
386         else if (object instanceof List) {
387             List list = (List) object;
388             for ( int i = 0, size = list.size(); i < size; i++ ) {
389                 write( list.get(i) );
390             }
391         }
392         else if (object != null) {
393             throw new IOException( "Invalid object: " + object );
394         }
395     }
396
397
398     /** <p>Writes the opening tag of an {@link Element},
399       * including its {@link Attribute}s
400       * but without its content.</p>
401       *
402       * @param element <code>Element</code> to output.
403       */

404     public void writeOpen(Element element) throws IOException {
405         writer.write("<");
406         writer.write( element.getQualifiedName() );
407         writeAttributes(element);
408         writer.write(">");
409     }
410
411     /** <p>Writes the closing tag of an {@link Element}</p>
412       *
413       * @param element <code>Element</code> to output.
414       */

415     public void writeClose(Element element) throws IOException {
416         writeClose( element.getQualifiedName() );
417     }
418
419
420     // XMLFilterImpl methods
421
//-------------------------------------------------------------------------
422
public void parse(InputSource source) throws IOException, SAXException {
423         installLexicalHandler();
424         super.parse(source);
425     }
426
427
428     public void setProperty(String JavaDoc name, Object JavaDoc value) throws SAXNotRecognizedException, SAXNotSupportedException {
429         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
430             if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
431                 setLexicalHandler((LexicalHandler JavaDoc) value);
432                 return;
433             }
434         }
435         super.setProperty(name, value);
436     }
437
438     public Object JavaDoc getProperty(String JavaDoc name) throws SAXNotRecognizedException, SAXNotSupportedException {
439         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
440             if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
441                 return getLexicalHandler();
442             }
443         }
444         return super.getProperty(name);
445     }
446
447     public void setLexicalHandler (LexicalHandler JavaDoc handler) {
448         if (handler == null) {
449             throw new NullPointerException JavaDoc("Null lexical handler");
450         }
451         else {
452             this.lexicalHandler = handler;
453         }
454     }
455
456     public LexicalHandler JavaDoc getLexicalHandler(){
457         return lexicalHandler;
458     }
459
460
461     // ContentHandler interface
462
//-------------------------------------------------------------------------
463
public void setDocumentLocator(Locator locator) {
464         super.setDocumentLocator(locator);
465     }
466
467     public void startDocument() throws SAXException {
468         try {
469             writeDeclaration();
470             super.startDocument();
471         }
472         catch (IOException e) {
473             handleException(e);
474         }
475     }
476
477     public void endDocument() throws SAXException {
478         super.endDocument();
479
480         if ( autoFlush ) {
481             try {
482                 flush();
483             } catch ( IOException e) {}
484         }
485     }
486
487     public void startPrefixMapping(String JavaDoc prefix, String JavaDoc uri) throws SAXException {
488         if ( namespacesMap == null ) {
489             namespacesMap = new HashMap();
490         }
491         namespacesMap.put(prefix, uri);
492         super.startPrefixMapping(prefix, uri);
493     }
494
495     public void endPrefixMapping(String JavaDoc prefix) throws SAXException {
496         super.endPrefixMapping(prefix);
497     }
498
499     public void startElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName, Attributes attributes) throws SAXException {
500         try {
501             charactersAdded = false;
502
503             writePrintln();
504             indent();
505             writer.write("<");
506             writer.write(qName);
507             writeNamespaces();
508             writeAttributes( attributes );
509             writer.write(">");
510             ++indentLevel;
511             lastOutputNodeType = Node.ELEMENT_NODE;
512
513             super.startElement( namespaceURI, localName, qName, attributes );
514         }
515         catch (IOException e) {
516             handleException(e);
517         }
518     }
519
520     public void endElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName) throws SAXException {
521         try {
522             charactersAdded = false;
523             --indentLevel;
524             if ( lastOutputNodeType == Node.ELEMENT_NODE ) {
525                 writePrintln();
526                 indent();
527             }
528
529             // XXXX: need to determine this using a stack and checking for
530
// content / children
531
boolean hadContent = true;
532             if ( hadContent ) {
533                 writeClose(qName);
534             }
535             else {
536                 writeEmptyElementClose(qName);
537             }
538             lastOutputNodeType = Node.ELEMENT_NODE;
539
540             super.endElement( namespaceURI, localName, qName );
541         }
542         catch (IOException e) {
543             handleException(e);
544         }
545     }
546
547     public void characters(char[] ch, int start, int length) throws SAXException {
548         if (ch == null || ch.length == 0 || length <= 0) {
549             return;
550         }
551
552         try {
553             /*
554              * we can't use the writeString method here because it's possible
555              * we don't receive all characters at once and calling writeString
556              * would cause unwanted spaces to be added in between these chunks
557              * of character arrays.
558              */

559             String JavaDoc string = new String JavaDoc(ch, start, length);
560
561             if (escapeText) {
562                 string = escapeElementEntities(string);
563             }
564
565             if (format.isTrimText()) {
566                 if ((lastOutputNodeType == Node.TEXT_NODE) && !charactersAdded) {
567                     writer.write(" ");
568                 } else if (charactersAdded && Character.isWhitespace(lastChar)) {
569                     writer.write(lastChar);
570                 }
571
572                 String JavaDoc delim = "";
573                 StringTokenizer tokens = new StringTokenizer(string);
574                 while (tokens.hasMoreTokens()) {
575                     writer.write(delim);
576                     writer.write(tokens.nextToken());
577                     delim = " ";
578                 }
579             } else {
580                 writer.write(string);
581             }
582
583             charactersAdded = true;
584             lastChar = ch[start + length - 1];
585             lastOutputNodeType = Node.TEXT_NODE;
586
587             super.characters(ch, start, length);
588         }
589         catch (IOException e) {
590             handleException(e);
591         }
592     }
593
594     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
595         super.ignorableWhitespace(ch, start, length);
596     }
597
598     public void processingInstruction(String JavaDoc target, String JavaDoc data) throws SAXException {
599         try {
600             indent();
601             writer.write("<?");
602             writer.write(target);
603             writer.write(" ");
604             writer.write(data);
605             writer.write("?>");
606             writePrintln();
607             lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
608
609             super.processingInstruction(target, data);
610         }
611         catch (IOException e) {
612             handleException(e);
613         }
614     }
615
616
617
618     // DTDHandler interface
619
//-------------------------------------------------------------------------
620
public void notationDecl(String JavaDoc name, String JavaDoc publicID, String JavaDoc systemID) throws SAXException {
621         super.notationDecl(name, publicID, systemID);
622     }
623
624     public void unparsedEntityDecl(String JavaDoc name, String JavaDoc publicID, String JavaDoc systemID, String JavaDoc notationName) throws SAXException {
625         super.unparsedEntityDecl(name, publicID, systemID, notationName);
626     }
627
628
629     // LexicalHandler interface
630
//-------------------------------------------------------------------------
631
public void startDTD(String JavaDoc name, String JavaDoc publicID, String JavaDoc systemID) throws SAXException {
632         inDTD = true;
633         try {
634             writeDocType(name, publicID, systemID);
635         }
636         catch (IOException e) {
637             handleException(e);
638         }
639
640         if (lexicalHandler != null) {
641             lexicalHandler.startDTD(name, publicID, systemID);
642         }
643     }
644
645     public void endDTD() throws SAXException {
646         inDTD = false;
647         if (lexicalHandler != null) {
648             lexicalHandler.endDTD();
649         }
650     }
651
652     public void startCDATA() throws SAXException {
653         try {
654             writer.write( "<![CDATA[" );
655         }
656         catch (IOException e) {
657             handleException(e);
658         }
659
660         if (lexicalHandler != null) {
661             lexicalHandler.startCDATA();
662         }
663     }
664
665     public void endCDATA() throws SAXException {
666         try {
667             writer.write( "]]>" );
668         }
669         catch (IOException e) {
670             handleException(e);
671         }
672
673         if (lexicalHandler != null) {
674             lexicalHandler.endCDATA();
675         }
676     }
677
678     public void startEntity(String JavaDoc name) throws SAXException {
679         try {
680             writeEntityRef(name);
681         }
682         catch (IOException e) {
683             handleException(e);
684         }
685
686         if (lexicalHandler != null) {
687             lexicalHandler.startEntity(name);
688         }
689     }
690
691     public void endEntity(String JavaDoc name) throws SAXException {
692         if (lexicalHandler != null) {
693             lexicalHandler.endEntity(name);
694         }
695     }
696
697     public void comment(char[] ch, int start, int length) throws SAXException {
698         if ( showCommentsInDTDs || ! inDTD ) {
699             try {
700                 charactersAdded = false;
701                 writeComment( new String JavaDoc(ch, start, length) );
702             }
703             catch (IOException e) {
704                 handleException(e);
705             }
706         }
707
708         if (lexicalHandler != null) {
709             lexicalHandler.comment(ch, start, length);
710         }
711     }
712
713
714
715     // Implementation methods
716
//-------------------------------------------------------------------------
717
protected void writeElement(Element element) throws IOException {
718         int size = element.nodeCount();
719         String JavaDoc qualifiedName = element.getQualifiedName();
720
721         writePrintln();
722         indent();
723
724         writer.write("<");
725         writer.write(qualifiedName);
726
727         int previouslyDeclaredNamespaces = namespaceStack.size();
728         Namespace ns = element.getNamespace();
729         if (isNamespaceDeclaration( ns ) ) {
730             namespaceStack.push(ns);
731             writeNamespace(ns);
732         }
733
734         // Print out additional namespace declarations
735
boolean textOnly = true;
736         for ( int i = 0; i < size; i++ ) {
737             Node node = element.node(i);
738             if ( node instanceof Namespace ) {
739                 Namespace additional = (Namespace) node;
740                 if (isNamespaceDeclaration( additional ) ) {
741                     namespaceStack.push(additional);
742                     writeNamespace(additional);
743                 }
744             }
745             else if ( node instanceof Element) {
746                 textOnly = false;
747             }
748             else if ( node instanceof Comment) {
749                 textOnly = false;
750             }
751         }
752
753         writeAttributes(element);
754
755         lastOutputNodeType = Node.ELEMENT_NODE;
756
757         if ( size <= 0 ) {
758             writeEmptyElementClose(qualifiedName);
759         }
760         else {
761             writer.write(">");
762             if ( textOnly ) {
763                 // we have at least one text node so lets assume
764
// that its non-empty
765
writeElementContent(element);
766             }
767             else {
768                 // we know it's not null or empty from above
769
++indentLevel;
770
771                 writeElementContent(element);
772
773                 --indentLevel;
774
775                 writePrintln();
776                 indent();
777             }
778             writer.write("</");
779             writer.write(qualifiedName);
780             writer.write(">");
781         }
782
783         // remove declared namespaceStack from stack
784
while (namespaceStack.size() > previouslyDeclaredNamespaces) {
785             namespaceStack.pop();
786         }
787
788         lastOutputNodeType = Node.ELEMENT_NODE;
789     }
790
791     /**
792      * Determines if element is a special case of XML elements
793      * where it contains an xml:space attribute of "preserve".
794      * If it does, then retain whitespace.
795      */

796     protected final boolean isElementSpacePreserved(Element element) {
797       final Attribute attr = (Attribute)element.attribute("space");
798       boolean preserveFound=preserve; //default to global state
799
if (attr!=null) {
800         if ("xml".equals(attr.getNamespacePrefix()) &&
801             "preserve".equals(attr.getText())) {
802           preserveFound = true;
803         }
804         else {
805           preserveFound = false;
806         }
807       }
808       return preserveFound;
809     }
810     /** Outputs the content of the given element. If whitespace trimming is
811      * enabled then all adjacent text nodes are appended together before
812      * the whitespace trimming occurs to avoid problems with multiple
813      * text nodes being created due to text content that spans parser buffers
814      * in a SAX parser.
815      */

816     protected void writeElementContent(Element element) throws IOException {
817         boolean trim = format.isTrimText();
818         boolean oldPreserve=preserve;
819         if (trim) { //verify we have to before more expensive test
820
preserve=isElementSpacePreserved(element);
821           trim = !preserve;
822         }
823         if (trim) {
824             // concatenate adjacent text nodes together
825
// so that whitespace trimming works properly
826
Text lastTextNode = null;
827             StringBuilder JavaDoc buffer = null;
828             boolean textOnly = true;
829             for ( int i = 0, size = element.nodeCount(); i < size; i++ ) {
830                 Node node = element.node(i);
831                 if ( node instanceof Text ) {
832                     if ( lastTextNode == null ) {
833                         lastTextNode = (Text) node;
834                     }
835                     else {
836                         if (buffer == null) {
837                             buffer = new StringBuilder JavaDoc( lastTextNode.getText() );
838                         }
839                       buffer.append( ((Text) node).getText() );
840                     }
841                 }
842                 else {
843                     if (!textOnly && format.isPadText()) {
844                         writer.write(PAD_TEXT);
845                     }
846
847                     textOnly = false;
848
849                     if ( lastTextNode != null ) {
850                         if ( buffer != null ) {
851                             writeString( buffer.toString() );
852                             buffer = null;
853                         }
854                         else {
855                             writeString( lastTextNode.getText() );
856                         }
857                         lastTextNode = null;
858
859                         if (format.isPadText()) {
860                             writer.write(PAD_TEXT);
861                         }
862                     }
863                     writeNode(node);
864                 }
865             }
866             if ( lastTextNode != null ) {
867                 if (!textOnly && format.isPadText()) {
868                     writer.write(PAD_TEXT);
869                 }
870                 if ( buffer != null ) {
871                     writeString( buffer.toString() );
872                     buffer = null;
873                 }
874                 else {
875                     writeString( lastTextNode.getText() );
876                 }
877                 lastTextNode = null;
878             }
879         }
880         else {
881             Node lastTextNode = null;
882             for ( int i = 0, size = element.nodeCount(); i < size; i++ ) {
883                 Node node = element.node(i);
884                 if (node instanceof Text) {
885                     writeNode(node);
886                     lastTextNode = node;
887                 } else {
888                     if ((lastTextNode != null) && format.isPadText()) {
889                         writer.write(PAD_TEXT);
890                     }
891                     writeNode(node);
892                     if ((lastTextNode != null) && format.isPadText()) {
893                         writer.write(PAD_TEXT);
894                     }
895                     lastTextNode = null;
896                 }
897             }
898         }
899         preserve=oldPreserve;
900     }
901     protected void writeCDATA(String JavaDoc text) throws IOException {
902         writer.write( "<![CDATA[" );
903         if (text != null) {
904             writer.write( text );
905         }
906         writer.write( "]]>" );
907
908         lastOutputNodeType = Node.CDATA_SECTION_NODE;
909     }
910
911     protected void writeDocType(DocumentType docType) throws IOException {
912         if (docType != null) {
913             docType.write( writer );
914             //writeDocType( docType.getElementName(), docType.getPublicID(), docType.getSystemID() );
915
writePrintln();
916         }
917     }
918
919
920     protected void writeNamespace(Namesp