Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/jdom/output/XMLOutputter.java


1   /*--
2   
3    $Id: XMLOutputter.java,v 1.112 2004/09/01 06:08:18 jhunter Exp $
4   
5    Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
6    All rights reserved.
7   
8    Redistribution and use in source and binary forms, with or without
9    modification, are permitted provided that the following conditions
10   are met:
11  
12   1. Redistributions of source code must retain the above copyright
13      notice, this list of conditions, and the following disclaimer.
14  
15   2. Redistributions in binary form must reproduce the above copyright
16      notice, this list of conditions, and the disclaimer that follows
17      these conditions in the documentation and/or other materials
18      provided with the distribution.
19  
20   3. The name "JDOM" must not be used to endorse or promote products
21      derived from this software without prior written permission.  For
22      written permission, please contact <request_AT_jdom_DOT_org>.
23  
24   4. Products derived from this software may not be called "JDOM", nor
25      may "JDOM" appear in their name, without prior written permission
26      from the JDOM Project Management <request_AT_jdom_DOT_org>.
27  
28   In addition, we request (but do not require) that you include in the
29   end-user documentation provided with the redistribution and/or in the
30   software itself an acknowledgement equivalent to the following:
31       "This product includes software developed by the
32        JDOM Project (http://www.jdom.org/)."
33   Alternatively, the acknowledgment may be graphical using the logos
34   available at http://www.jdom.org/images/logos.
35  
36   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39   DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47   SUCH DAMAGE.
48  
49   This software consists of voluntary contributions made by many
50   individuals on behalf of the JDOM Project and was originally
51   created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52   Brett McLaughlin <brett_AT_jdom_DOT_org>.  For more information
53   on the JDOM Project, please see <http://www.jdom.org/>.
54  
55   */
56  
57  package org.jdom.output;
58  
59  import java.io.*;
60  import java.util.*;
61  
62  import javax.xml.transform.Result;
63  
64  import org.jdom.*;
65  
66  /**
67   * Outputs a JDOM document as a stream of bytes. The outputter can manage many
68   * styles of document formatting, from untouched to pretty printed. The default
69   * is to output the document content exactly as created, but this can be changed
70   * by setting a new Format object. For pretty-print output, use
71   * <code>{@link Format#getPrettyFormat()}</code>. For whitespace-normalized
72   * output, use <code>{@link Format#getCompactFormat()}</code>.
73   * <p>
74   * There are <code>{@link #output output(...)}</code> methods to print any of
75   * the standard JDOM classes, including Document and Element, to either a Writer
76   * or an OutputStream. <b>Warning</b>: When outputting to a Writer, make sure
77   * the writer's encoding matches the encoding setting in the Format object. This
78   * ensures the encoding in which the content is written (controlled by the
79   * Writer configuration) matches the encoding placed in the document's XML
80   * declaration (controlled by the XMLOutputter). Because a Writer cannot be
81   * queried for its encoding, the information must be passed to the Format
82   * manually in its constructor or via the
83   * <code>{@link Format#setEncoding}</code> method. The default encoding is
84   * UTF-8.
85   * <p>
86   * The methods <code>{@link #outputString outputString(...)}</code> are for
87   * convenience only; for top performance you should call one of the <code>{@link
88   * #output output(...)}</code> methods and pass in your own Writer or
89   * OutputStream if possible.
90   * <p>
91   * XML declarations are always printed on their own line followed by a line
92   * seperator (this doesn't change the semantics of the document). To omit
93   * printing of the declaration use
94   * <code>{@link Format#setOmitDeclaration}</code>. To omit printing of the
95   * encoding in the declaration use <code>{@link Format#setOmitEncoding}</code>.
96   * Unfortunatly there is currently no way to know the original encoding of the
97   * document.
98   * <p>
99   * Empty elements are by default printed as &lt;empty/&gt;, but this can be
100  * configured with <code>{@link Format#setExpandEmptyElements}</code> to cause
101  * them to be expanded to &lt;empty&gt;&lt;/empty&gt;.
102  *
103  * @version $Revision: 1.112 $, $Date: 2004/09/01 06:08:18 $
104  * @author  Brett McLaughlin
105  * @author  Jason Hunter
106  * @author  Jason Reid
107  * @author  Wolfgang Werner
108  * @author  Elliotte Rusty Harold
109  * @author  David &amp; Will (from Post Tool Design)
110  * @author  Dan Schaffer
111  * @author  Alex Chaffee
112  * @author  Bradley S. Huffman
113  */
114 
115 public class XMLOutputter implements Cloneable {
116 
117     private static final String CVS_ID =
118       "@(#) $RCSfile: XMLOutputter.java,v $ $Revision: 1.112 $ $Date: 2004/09/01 06:08:18 $ $Name: jdom_1_0 $";
119 
120     // For normal output
121     private Format userFormat = Format.getRawFormat();
122 
123     // For xml:space="preserve"
124     protected static final Format preserveFormat = Format.getRawFormat();
125 
126     // What's currently in use
127     protected Format currentFormat = userFormat;
128 
129     /** Whether output escaping is enabled for the being processed
130       * Element - default is <code>true</code> */
131     private boolean escapeOutput = true;
132 
133     // * * * * * * * * * * Constructors * * * * * * * * * *
134     // * * * * * * * * * * Constructors * * * * * * * * * *
135 
136     /**
137      * This will create an <code>XMLOutputter</code> with the default
138      * {@link Format} matching {@link Format#getRawFormat}.
139      */
140     public XMLOutputter() {
141     }
142 
143     /**
144      * This will create an <code>XMLOutputter</code> with the specified
145      * format characteristics.  Note the format object is cloned internally
146      * before use.
147      */
148     public XMLOutputter(Format format) {
149         userFormat = (Format) format.clone();
150         currentFormat = userFormat;
151     }
152 
153     /**
154      * This will create an <code>XMLOutputter</code> with all the
155      * options as set in the given <code>XMLOutputter</code>.  Note
156      * that <code>XMLOutputter two = (XMLOutputter)one.clone();</code>
157      * would work equally well.
158      *
159      * @param that the XMLOutputter to clone
160      */
161     public XMLOutputter(XMLOutputter that) {
162         this.userFormat = (Format) that.userFormat.clone();
163         currentFormat = userFormat;
164     }
165 
166     // * * * * * * * * * * Set parameters methods * * * * * * * * * *
167     // * * * * * * * * * * Set parameters methods * * * * * * * * * *
168 
169     /**
170      * Sets the new format logic for the outputter.  Note the Format
171      * object is cloned internally before use.
172      *
173      * @param newFormat the format to use for output
174      */
175     public void setFormat(Format newFormat) {
176         this.userFormat = (Format) newFormat.clone();
177         this.currentFormat = userFormat;
178     }
179 
180     /**
181      * Returns the current format in use by the outputter.  Note the 
182      * Format object returned is a clone of the one used internally.
183      */
184     public Format getFormat() {
185         return (Format) userFormat.clone();
186     }
187 
188     // * * * * * * * * * * Output to a OutputStream * * * * * * * * * *
189     // * * * * * * * * * * Output to a OutputStream * * * * * * * * * *
190 
191     /**
192      * This will print the <code>Document</code> to the given output stream.
193      * The characters are printed using the encoding specified in the
194      * constructor, or a default of UTF-8.
195      *
196      * @param doc <code>Document</code> to format.
197      * @param out <code>OutputStream</code> to use.
198      * @throws IOException - if there's any problem writing.
199      */
200     public void output(Document doc, OutputStream out)
201                     throws IOException {
202         Writer writer = makeWriter(out);
203         output(doc, writer);  // output() flushes
204     }
205 
206     /**
207      * Print out the <code>{@link DocType}</code>.
208      *
209      * @param doctype <code>DocType</code> to output.
210      * @param out <code>OutputStream</code> to use.
211      */
212     public void output(DocType doctype, OutputStream out) throws IOException {
213         Writer writer = makeWriter(out);
214         output(doctype, writer);  // output() flushes
215     }
216 
217     /**
218      * Print out an <code>{@link Element}</code>, including
219      * its <code>{@link Attribute}</code>s, and all
220      * contained (child) elements, etc.
221      *
222      * @param element <code>Element</code> to output.
223      * @param out <code>Writer</code> to use.
224      */
225     public void output(Element element, OutputStream out) throws IOException {
226         Writer writer = makeWriter(out);
227         output(element, writer);  // output() flushes
228     }
229 
230     /**
231      * This will handle printing out an <code>{@link
232      * Element}</code>'s content only, not including its tag, and
233      * attributes.  This can be useful for printing the content of an
234      * element that contains HTML, like "&lt;description&gt;JDOM is
235      * &lt;b&gt;fun&gt;!&lt;/description&gt;".
236      *
237      * @param element <code>Element</code> to output.
238      * @param out <code>OutputStream</code> to use.
239      */
240     public void outputElementContent(Element element, OutputStream out)
241                     throws IOException {
242         Writer writer = makeWriter(out);
243         outputElementContent(element, writer);  // output() flushes
244     }
245 
246     /**
247      * This will handle printing out a list of nodes.
248      * This can be useful for printing the content of an element that
249      * contains HTML, like "&lt;description&gt;JDOM is
250      * &lt;b&gt;fun&gt;!&lt;/description&gt;".
251      *
252      * @param list <code>List</code> of nodes.
253      * @param out <code>OutputStream</code> to use.
254      */
255     public void output(List list, OutputStream out)
256                     throws IOException {
257         Writer writer = makeWriter(out);
258         output(list, writer);  // output() flushes
259     }
260 
261     /**
262      * Print out a <code>{@link CDATA}</code> node.
263      *
264      * @param cdata <code>CDATA</code> to output.
265      * @param out <code>OutputStream</code> to use.
266      */
267     public void output(CDATA cdata, OutputStream out) throws IOException {
268         Writer writer = makeWriter(out);
269         output(cdata, writer);  // output() flushes
270     }
271 
272     /**
273      * Print out a <code>{@link Text}</code> node.  Perfoms
274      * the necessary entity escaping and whitespace stripping.
275      *
276      * @param text <code>Text</code> to output.
277      * @param out <code>OutputStream</code> to use.
278      */
279     public void output(Text text, OutputStream out) throws IOException {
280         Writer writer = makeWriter(out);
281         output(text, writer);  // output() flushes
282     }
283 
284     /**
285      * Print out a <code>{@link Comment}</code>.
286      *
287      * @param comment <code>Comment</code> to output.
288      * @param out <code>OutputStream</code> to use.
289      */
290     public void output(Comment comment, OutputStream out) throws IOException {
291         Writer writer = makeWriter(out);
292         output(comment, writer);  // output() flushes
293     }
294 
295     /**
296      * Print out a <code>{@link ProcessingInstruction}</code>.
297      *
298      * @param pi <code>ProcessingInstruction</code> to output.
299      * @param out <code>OutputStream</code> to use.
300      */
301     public void output(ProcessingInstruction pi, OutputStream out)
302                                  throws IOException {
303         Writer writer = makeWriter(out);
304         output(pi, writer);  // output() flushes
305     }
306 
307     /**
308      * Print out a <code>{@link EntityRef}</code>.
309      *
310      * @param entity <code>EntityRef</code> to output.
311      * @param out <code>OutputStream</code> to use.
312      */
313     public void output(EntityRef entity, OutputStream out) throws IOException {
314         Writer writer = makeWriter(out);
315         output(entity, writer);  // output() flushes
316     }
317 
318     /**
319      * Get an OutputStreamWriter, using prefered encoding
320      * (see {@link Format#setEncoding}).
321      */
322     private Writer makeWriter(OutputStream out)
323                          throws java.io.UnsupportedEncodingException {
324         return makeWriter(out, userFormat.encoding);
325     }
326 
327     /**
328      * Get an OutputStreamWriter, use specified encoding.
329      */
330     private static Writer makeWriter(OutputStream out, String enc)
331                          throws java.io.UnsupportedEncodingException {
332         // "UTF-8" is not recognized before JDK 1.1.6, so we'll translate
333         // into "UTF8" which works with all JDKs.
334         if ("UTF-8".equals(enc)) {
335             enc = "UTF8";
336         }
337 
338         Writer writer = new BufferedWriter(
339                             (new OutputStreamWriter(
340                                 new BufferedOutputStream(out), enc)
341                             ));
342         return writer;
343     }
344 
345     // * * * * * * * * * * Output to a Writer * * * * * * * * * *
346     // * * * * * * * * * * Output to a Writer * * * * * * * * * *
347 
348     /**
349      * This will print the <code>Document</code> to the given Writer.
350      *
351      * <p>
352      * Warning: using your own Writer may cause the outputter's
353      * preferred character encoding to be ignored.  If you use
354      * encodings other than UTF-8, we recommend using the method that
355      * takes an OutputStream instead.
356      * </p>
357      *
358      * @param doc <code>Document</code> to format.
359      * @param out <code>Writer</code> to use.
360      * @throws IOException - if there's any problem writing.
361      */
362     public void output(Document doc, Writer out) throws IOException {
363 
364         printDeclaration(out, doc, userFormat.encoding);
365 
366         // Print out root element, as well as any root level
367         // comments and processing instructions,
368         // starting with no indentation
369         List content = doc.getContent();
370         int size = content.size();
371         for (int i = 0; i < size; i++) {
372             Object obj = content.get(i);
373 
374             if (obj instanceof Element) {
375                 printElement(out, doc.getRootElement(), 0,
376                              createNamespaceStack());
377             }
378             else if (obj instanceof Comment) {
379                 printComment(out, (Comment) obj);
380             }
381             else if (obj instanceof ProcessingInstruction) {
382                 printProcessingInstruction(out, (ProcessingInstruction) obj);
383             }
384             else if (obj instanceof DocType) {
385                 printDocType(out, doc.getDocType());
386                 // Always print line separator after declaration, helps the
387                 // output look better and is semantically inconsequential
388                 out.write(currentFormat.lineSeparator);
389             }
390             else {
391                 // XXX if we get here then we have a illegal content, for
392                 //     now we'll just ignore it
393             }
394 
395             newline(out);
396             indent(out, 0);
397         }
398 
399         // Output final line separator
400         // We output this no matter what the newline flags say
401         out.write(currentFormat.lineSeparator);
402 
403         out.flush();
404     }
405 
406     /**
407      * Print out the <code>{@link DocType}</code>.
408      *
409      * @param doctype <code>DocType</code> to output.
410      * @param out <code>Writer</code> to use.
411      */
412     public void output(DocType doctype, Writer out) throws IOException {
413         printDocType(out, doctype);
414         out.flush();
415     }
416 
417     /**
418      * Print out an <code>{@link Element}</code>, including
419      * its <code>{@link Attribute}</code>s, and all
420      * contained (child) elements, etc.
421      *
422      * @param element <code>Element</code> to output.
423      * @param out <code>Writer</code> to use.
424      */
425     public void output(Element element, Writer out) throws IOException {
426         // If this is the root element we could pre-initialize the
427         // namespace stack with the namespaces
428         printElement(out, element, 0, createNamespaceStack());
429         out.flush();
430     }
431 
432     /**
433      * This will handle printing out an <code>{@link
434      * Element}</code>'s content only, not including its tag, and
435      * attributes.  This can be useful for printing the content of an
436      * element that contains HTML, like "&lt;description&gt;JDOM is
437      * &lt;b&gt;fun&gt;!&lt;/description&gt;".
438      *
439      * @param element <code>Element</code> to output.
440      * @param out <code>Writer</code> to use.
441      */
442     public void outputElementContent(Element element, Writer out)
443                     throws IOException {
444         List content = element.getContent();
445         printContentRange(out, content, 0, content.size(),
446                           0, createNamespaceStack());
447         out.flush();
448     }
449 
450     /**
451      * This will handle printing out a list of nodes.
452      * This can be useful for printing the content of an element that
453      * contains HTML, like "&lt;description&gt;JDOM is
454      * &lt;b&gt;fun&gt;!&lt;/description&gt;".
455      *
456      * @param list <code>List</code> of nodes.
457      * @param out <code>Writer</code> to use.
458      */
459     public void output(List list, Writer out)
460                     throws IOException {
461         printContentRange(out, list, 0, list.size(),
462                           0, createNamespaceStack());
463         out.flush();
464     }
465 
466     /**
467      * Print out a <code>{@link CDATA}</code> node.
468      *
469      * @param cdata <code>CDATA</code> to output.
470      * @param out <code>Writer</code> to use.
471      */
472     public void output(CDATA cdata, Writer out) throws IOException {
473         printCDATA(out, cdata);
474         out.flush();
475     }
476 
477     /**
478      * Print out a <code>{@link Text}</code> node.  Perfoms
479      * the necessary entity escaping and whitespace stripping.
480      *
481      * @param text <code>Text</code> to output.
482      * @param out <code>Writer</code> to use.
483      */
484     public void output(Text text, Writer out) throws IOException {
485         printText(out, text);
486         out.flush();
487     }
488 
489     /**
490      * Print out a <code>{@link Comment}</code>.
491      *
492      * @param comment <code>Comment</code> to output.
493      * @param out <code>Writer</code> to use.
494      */
495     public void output(Comment comment, Writer out) throws IOException {
496         printComment(out, comment);
497         out.flush();
498     }
499 
500     /**
501      * Print out a <code>{@link ProcessingInstruction}</code>.
502      *
503      * @param pi <code>ProcessingInstruction</code> to output.
504      * @param out <code>Writer</code> to use.
505      */
506     public void output(ProcessingInstruction pi, Writer out)
507                                  throws IOException {
508         boolean currentEscapingPolicy = currentFormat.ignoreTrAXEscapingPIs;
509 
510         // Output PI verbatim, disregarding TrAX escaping PIs.
511         currentFormat.setIgnoreTrAXEscapingPIs(true);
512         printProcessingInstruction(out, pi);
513         currentFormat.setIgnoreTrAXEscapingPIs(currentEscapingPolicy);
514 
515         out.flush();
516     }
517 
518     /**
519      * Print out a <code>{@link EntityRef}</code>.
520      *
521      * @param entity <code>EntityRef</code> to output.
522      * @param out <code>Writer</code> to use.
523      */
524     public void output(EntityRef entity, Writer out) throws IOException {
525         printEntityRef(out, entity);
526         out.flush();
527     }
528 
529     // * * * * * * * * * * Output to a String * * * * * * * * * *
530     // * * * * * * * * * * Output to a String * * * * * * * * * *
531 
532     /**
533      * Return a string representing a document.  Uses an internal
534      * StringWriter. Warning: a String is Unicode, which may not match
535      * the outputter's specified encoding.
536      *
537      * @param doc <code>Document</code> to format.
538      */
539     public String outputString(Document doc) {
540         StringWriter out = new StringWriter();
541         try {
542             output(doc, out);  // output() flushes
543         } catch (IOException e) { }
544         return out.toString();
545     }
546 
547     /**
548      * Return a string representing a DocType. Warning: a String is
549      * Unicode, which may not match the outputter's specified
550      * encoding.
551      *
552      * @param doctype <code>DocType</code> to format.
553      */
554     public String outputString(DocType doctype) {
555         StringWriter out = new StringWriter();
556         try {
557             output(doctype, out);  // output() flushes
558         } catch (IOException e) { }
559         return out.toString();
560     }
561 
562     /**
563      * Return a string representing an element. Warning: a String is
564      * Unicode, which may not match the outputter's specified
565      * encoding.
566      *
567      * @param element <code>Element</code> to format.
568      */
569     public String outputString(Element element) {
570         StringWriter out = new StringWriter();
571         try {
572             output(element, out);  // output() flushes
573         } catch (IOException e) { }
574         return out.toString();
575     }
576 
577    /**
578      * Return a string representing a list of nodes.  The list is
579      * assumed to contain legal JDOM nodes.
580      *
581      * @param list <code>List</code> to format.
582      */
583     public String outputString(List list) {
584         StringWriter out = new StringWriter();
585         try {
586             output(list, out);  // output() flushes
587         } catch (IOException e) { }
588         return out.toString();
589     }
590 
591     /**
592      * Return a string representing a CDATA node. Warning: a String is
593      * Unicode, which may not match the outputter's specified
594      * encoding.
595      *
596      * @param cdata <code>CDATA</code> to format.
597      */
598     public String outputString(CDATA cdata) {
599         StringWriter out = new StringWriter();
600         try {
601             output(cdata, out);  // output() flushes
602         } catch (IOException e) { }
603         return out.toString();
604     }
605 
606     /**
607      * Return a string representing a Text node. Warning: a String is
608      * Unicode, which may not match the outputter's specified
609      * encoding.
610      *
611      * @param text <code>Text</code> to format.
612      */
613     public String outputString(Text text) {
614         StringWriter out = new StringWriter();
615         try {
616             output(text, out);  // output() flushes
617         } catch (IOException e) { }
618         return out.toString();
619     }
620 
621 
622     /**
623      * Return a string representing a comment. Warning: a String is
624      * Unicode, which may not match the outputter's specified
625      * encoding.
626      *
627      * @param comment <code>Comment</code> to format.
628      */
629     public String outputString(Comment comment) {
630         StringWriter out = new StringWriter();
631         try {
632             output(comment, out);  // output() flushes
633         } catch (IOException e) { }
634         return out.toString();
635     }
636 
637     /**
638      * Return a string representing a PI. Warning: a String is
639      * Unicode, which may not match the outputter's specified
640      * encoding.
641      *
642      * @param pi <code>ProcessingInstruction</code> to format.
643      */
644     public String outputString(ProcessingInstruction pi) {
645         StringWriter out = new StringWriter();
646         try {
647             output(pi, out);  // output() flushes
648         } catch (IOException e) { }
649         return out.toString();
650     }
651 
652    /**
653      * Return a string representing an entity. Warning: a String is
654      * Unicode, which may not match the outputter's specified
655      * encoding.
656      *
657      * @param entity <code>EntityRef</code> to format.
658      */
659     public String outputString(EntityRef entity) {
660         StringWriter out = new StringWriter();
661         try {
662             output(entity, out);  // output() flushes
663         } catch (IOException e) { }
664         return out.toString();
665     }
666 
667     // * * * * * * * * * * Internal printing methods * * * * * * * * * *
668     // * * * * * * * * * * Internal printing methods * * * * * * * * * *
669 
670     /**
671      * This will handle printing of the declaration.
672      * Assumes XML version 1.0 since we don't directly know.
673      *
674      * @param doc <code>Document</code> whose declaration to write.
675      * @param out <code>Writer</code> to use.
676      * @param encoding The encoding to add to the declaration
677      */
678     protected void printDeclaration(Writer out, Document doc,
679                                     String encoding) throws IOException {
680 
681         // Only print the declaration if it's not being omitted
682         if (!userFormat.omitDeclaration) {
683             // Assume 1.0 version
684             out.write("<?xml version=\"1.0\"");
685             if (!userFormat.omitEncoding) {
686                 out.write(" encoding=\"" + encoding + "\"");
687             }
688             out.write("?>");
689 
690             // Print new line after decl always, even if no other new lines
691             // Helps the output look better and is semantically
692             // inconsequential
693             out.write(currentFormat.lineSeparator);
694         }
695     }
696 
697     /**
698      * This handle printing the DOCTYPE declaration if one exists.
699      *
700      * @param docType <code>Document</code> whose declaration to write.
701      * @param out <code>Writer</code> to use.
702      */
703     protected void printDocType(Writer out, DocType docType)
704                         throws IOException {
705 
706         String publicID = docType.getPublicID();
707         String systemID = docType.getSystemID();
708         String internalSubset = docType.getInternalSubset();
709         boolean hasPublic = false;
710 
711         out.write("<!DOCTYPE ");
712         out.write(docType.getElementName());
713         if (publicID != null) {
714             out.write(" PUBLIC \"");
715             out.write(publicID);
716             out.write("\"");
717             hasPublic = true;
718         }
719         if (systemID != null) {
720             if (!hasPublic) {
721                 out.write(" SYSTEM");
722             }
723             out.write(" \"");
724             out.write(systemID);
725             out.write("\"");
726         }
727         if ((internalSubset != null) && (!internalSubset.equals(""))) {
728             out.write(" [");
729             out.write(currentFormat.lineSeparator);
730             out.write(docType.getInternalSubset());
731             out.write("]");
732         }
733         out.write(">");
734     }
735 
736     /**
737      * This will handle printing of comments.
738      *
739      * @param comment <code>Comment</code> to write.
740      * @param out <code>Writer</code> to use.
741      */
742     protected void printComment(Writer out, Comment comment)
743                        throws IOException {
744         out.write("<!--");
745         out.write(comment.getText());
746         out.write("-->");
747     }
748 
749     /**
750      * This will handle printing of processing instructions.
751      *
752      * @param pi <code>ProcessingInstruction</code> to write.
753      * @param out <code>Writer</code> to use.
754      */
755     protected void printProcessingInstruction(Writer out, ProcessingInstruction pi
756                                               ) throws IOException {
757         String target = pi.getTarget();
758         boolean piProcessed = false;
759 
760         if (currentFormat.ignoreTrAXEscapingPIs == false) {
761             if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) {
762                 escapeOutput = false;
763                 piProcessed  = true;
764             }
765             else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) {
766                 escapeOutput = true;
767                 piProcessed  = true;
768             }
769         }
770         if (piProcessed == false) {
771             String rawData = pi.getData();
772 
773             // Write <?target data?> or if no data then just <?target?>
774             if (!"".equals(rawData)) {
775                 out.write("<?");
776                 out.write(target);
777                 out.write(" ");
778                 out.write(rawData);
779                 out.write("?>");
780             }
781             else {
782                 out.write("<?");
783                 out.write(target);
784                 out.write("?>");
785             }
786         }
787     }
788 
789     /**
790      * This will handle printing a <code>{@link EntityRef}</code>.
791      * Only the entity reference such as <code>&amp;entity;</code>
792      * will be printed. However, subclasses are free to override
793      * this method to print the contents of the entity instead.
794      *
795      * @param entity <code>EntityRef</code> to output.
796      * @param out <code>Writer</code> to use.  */
797     protected void printEntityRef(Writer out, EntityRef entity)
798                        throws IOException {
799         out.write("&");
800         out.write(entity.getName());
801         out.write(";");
802     }
803 
804     /**
805      * This will handle printing of <code>{@link CDATA}</code> text.
806      *
807      * @param cdata <code>CDATA</code> to output.
808      * @param out <code>Writer</code> to use.
809      */
810     protected void printCDATA(Writer out, CDATA cdata) throws IOException {
811         String str = (currentFormat.mode == Format.TextMode.NORMALIZE)
812                      ? cdata.getTextNormalize()
813                      : ((currentFormat.mode == Format.TextMode.TRIM) ?
814                              cdata.getText().trim() : cdata.getText());
815         out.write("<![CDATA[");
816         out.write(str);
817         out.write("]]>");
818     }
819 
820     /**
821      * This will handle printing of <code>{@link Text}</code> strings.
822      *
823      * @param text <code>Text</code> to write.
824      * @param out <code>Writer</code> to use.
825      */
826     protected void printText(Writer out, Text text) throws IOException {
827         String str = (currentFormat.mode == Format.TextMode.NORMALIZE)
828                      ? text.getTextNormalize()
829                      : ((currentFormat.mode == Format.TextMode.TRIM) ?
830                           text.getText().trim() : text.getText());
831         out.write(escapeElementEntities(str));
832     }
833 
834     /**
835      * This will handle printing a string.  Escapes the element entities,
836      * trims interior whitespace, etc. if necessary.
837      */
838     private void printString(Writer out, String str) throws IOException {
839         if (currentFormat.mode == Format.TextMode.NORMALIZE) {
840             str = Text.normalizeString(str);
841         }
842         else if (currentFormat.mode == Format.TextMode.TRIM) {
843             str = str.trim();
844         }
845         out.write(escapeElementEntities(str));
846     }
847 
848     /**
849      * This will handle printing of a <code>{@link Element}</code>,
850      * its <code>{@link Attribute}</code>s, and all contained (child)
851      * elements, etc.
852      *
853      * @param element <code>Element</code> to output.
854      * @param out <code>Writer</code> to use.
855      * @param level <code>int</code> level of indention.
856      * @param namespaces <code>List</code> stack of Namespaces in scope.
857      */
858     protected void printElement(Writer out, Element element,
859                                 int level, NamespaceStack namespaces)
860                        throws IOException {
861 
862         List attributes = element.getAttributes();
863         List content = element.getContent();
864 
865         // Check for xml:space and adjust format settings
866         String space = null;
867         if (attributes != null) {
868             space = element.getAttributeValue("space",
869                                                Namespace.XML_NAMESPACE);
870         }
871 
872         Format previousFormat = currentFormat;
873 
874         if ("default".equals(space)) {
875             currentFormat = userFormat;
876         }
877         else if ("preserve".equals(space)) {
878             currentFormat = preserveFormat;
879         }
880 
881         // Print the beginning of the tag plus attributes and any
882         // necessary namespace declarations
883         out.write("<");
884         printQualifiedName(out, element);
885 
886         // Mark our namespace starting point
887         int previouslyDeclaredNamespaces = namespaces.size();
888 
889         // Print the element's namespace, if appropriate
890         printElementNamespace(out, element, namespaces);
891 
892         // Print out additional namespace declarations
893         printAdditionalNamespaces(out, element, namespaces);
894 
895         // Print out attributes
896         if (attributes != null)
897             printAttributes(out, attributes, element, namespaces);
898 
899         // Depending on the settings (newlines, textNormalize, etc), we may
900         // or may not want to print all of the content, so determine the
901         // index of the start of the content we're interested
902         // in based on the current settings.
903 
904         int start = skipLeadingWhite(content, 0);
905         int size = content.size();
906         if (start >= size) {
907             // Case content is empty or all insignificant whitespace
908             if (currentFormat.expandEmptyElements) {
909                 out.write("></");
910                 printQualifiedName(out, element);
911                 out.write(">");
912             }
913             else {
914                 out.write(" />");
915             }
916         }
917         else {
918             out.write(">");
919 
920             // For a special case where the content is only CDATA
921             // or Text we don't want to indent after the start or
922             // before the end tag.
923 
924             if (nextNonText(content, start) < size) {
925                 // Case Mixed Content - normal indentation
926                 newline(out);
927                 printContentRange(out, content, start, size,
928                                   level + 1, namespaces);
929                 newline(out);
930                 indent(out, level);
931             }
932             else {
933                 // Case all CDATA or Text - no indentation
934                 printTextRange(out, content, start, size);
935             }
936             out.write("</");
937             printQualifiedName(out, element);
938             out.write(">");
939         }
940 
941         // remove declared namespaces from stack
942         while (namespaces.size() > previouslyDeclaredNamespaces) {
943             namespaces.pop();
944         }
945 
946         // Restore our format settings
947         currentFormat = previousFormat;
948     }
949 
950     /**
951      * This will handle printing of content within a given range.
952      * The range to print is specified in typical Java fashion; the
953      * starting index is inclusive, while the ending index is
954      * exclusive.
955      *
956      * @param content <code>List</code> of content to output
957      * @param start index of first content node (inclusive.
958      * @param end index of last content node (exclusive).
959      * @param out <code>Writer</code> to use.
960      * @param level <code>int</code> level of indentation.
961      * @param namespaces <code>List</code> stack of Namespaces in scope.
962      */
963     private void printContentRange(Writer out, List content,
964                                      int start, int end, int level,
965                                      NamespaceStack namespaces)
966                        throws IOException {
967         boolean firstNode; // Flag for 1st node in content
968         Object next;       // Node we're about to print
969         int first, index;  // Indexes into the list of content
970 
971         index = start;
972         while (index < end) {
973             firstNode = (index == start) ? true : false;
974             next = content.get(index);
975 
976             //
977             // Handle consecutive CDATA, Text, and EntityRef nodes all at once
978             //
979             if ((next instanceof Text) || (next instanceof EntityRef)) {
980                 first = skipLeadingWhite(content, index);
981                 // Set index to next node for loop
982                 index = nextNonText(content, first);
983 
984                 // If it's not all whitespace - print it!
985                 if (first < index) {
986                     if (!firstNode)
987                         newline(out);
988                     indent(out, level);
989                     printTextRange(out, content, first, index);
990                 }
991                 continue;
992             }
993 
994             //
995             // Handle other nodes
996             //
997             if (!firstNode) {
998                 newline(out);
999             }
1000
1001            indent(out, level);
1002
1003            if (next instanceof Comment) {
1004                printComment(out, (Comment)next);
1005            }
1006            else if (next instanceof Element) {
1007                printElement(out, (Element)next, level, namespaces);
1008            }
1009            else if (next instanceof ProcessingInstruction) {
1010                printProcessingInstruction(out, (ProcessingInstruction)next);
1011            }
1012            else {
1013                // XXX if we get here then we have a illegal content, for
1014                //     now we'll just ignore it (probably should throw
1015                //     a exception)
1016            }
1017
1018            index++;
1019        } /* while */
1020    }
1021
1022    /**
1023     * This will handle printing of a sequence of <code>{@link CDATA}</code>
1024     * or <code>{@link Text}</code> nodes.  It is an error to have any other
1025     * pass this method any other type of node.
1026     *
1027     * @param content <code>List</code> of content to output
1028     * @param start index of first content node (inclusive).
1029     * @param end index of last content node (exclusive).
1030     * @param out <code>Writer</code> to use.
1031     */
1032    private void printTextRange(Writer out, List content, int start, int end
1033                                  ) throws IOException {
1034        String previous; // Previous text printed
1035        Object node;     // Next node to print
1036        String next;     // Next text to print
1037
1038        previous = null;
1039
1040        // Remove leading whitespace-only nodes
1041        start = skipLeadingWhite(content, start);
1042
1043        int size = content.size();
1044        if (start < size) {
1045            // And remove trialing whitespace-only nodes
1046            end = skipTrailingWhite(content, end);
1047
1048            for (int i = start; i < end; i++) {
1049                node = content.get(i);
1050
1051                // Get the unmangled version of the text
1052                // we are about to print
1053                if (node instanceof Text) {
1054                    next = ((Text) node).getText();
1055                }
1056                else if (node instanceof EntityRef) {
1057                    next = "&" + ((EntityRef) node).getValue() + ";";
1058                }
1059                else {
1060                    throw new IllegalStateException("Should see only " +
1061                                                   "CDATA, Text, or EntityRef");
1062                }
1063
1064                // This may save a little time
1065                if (next == null || "".equals(next)) {
1066                    continue;
1067                }
1068
1069                // Determine if we need to pad the output (padding is
1070                // only need in trim or normalizing mode)
1071                if (previous != null) { // Not 1st node
1072                    if (currentFormat.mode == Format.TextMode.NORMALIZE ||
1073                        currentFormat.mode == Format.TextMode.TRIM) {
1074                            if ((endsWithWhite(previous)) ||
1075                                (startsWithWhite(next))) {
1076                                    out.write(" ");
1077                            }
1078                    }
1079                }
1080
1081                // Print the node
1082                if (node instanceof CDATA) {
1083                    printCDATA(out, (CDATA) node);
1084                }
1085                else if (node instanceof EntityRef) {
1086                    printEntityRef(out, (EntityRef) node);
1087                }
1088                else {
1089                    printString(out, next);
1090                }
1091
1092                previous = next;
1093            }
1094        }
1095    }
1096
1097    /**
1098     * This will handle printing of any needed <code>{@link Namespace}</code>
1099     * declarations.
1100     *
1101     * @param ns <code>Namespace</code> to print definition of
1102     * @param out <code>Writer</code> to use.
1103     */
1104    private void printNamespace(Writer out, Namespace ns,
1105                                NamespaceStack namespaces)
1106                     throws IOException {
1107        String prefix = ns.getPrefix();
1108        String uri = ns.getURI();
1109
1110        // Already printed namespace decl?
1111        if (uri.equals(namespaces.getURI(prefix))) {
1112            return;
1113        }
1114
1115        out.write(" xmlns");
1116        if (!prefix.equals("")) {
1117            out.write(":");
1118            out.write(prefix);
1119        }
1120        out.write("=\"");
1121        out.write(uri);
1122        out.write("\"");
1123        namespaces.push(ns);
1124    }
1125
1126    /**
1127     * This will handle printing of a <code>{@link Attribute}</code> list.
1128     *
1129     * @param attributes <code>List</code> of Attribute objcts
1130     * @param out <code>Writer</code> to use
1131     */
1132    protected void printAttributes(Writer out, List attributes, Element parent,
1133                                   NamespaceStack namespaces)
1134                       throws IOException {
1135
1136        // I do not yet handle the case where the same prefix maps to
1137        // two different URIs. For attributes on the same element
1138        // this is illegal; but as yet we don't throw an exception
1139        // if someone tries to do this
1140        // Set prefixes = new HashSet();
1141        for (int i = 0; i < attributes.size(); i++) {
1142            Attribute attribute = (Attribute) attributes.get(i);
1143            Namespace ns = attribute.getNamespace();
1144            if ((ns != Namespace.NO_NAMESPACE) &&
1145                (ns != Namespace.XML_NAMESPACE)) {
1146                    printNamespace(out, ns, namespaces);
1147            }
1148
1149            out.write(" ");
1150            printQualifiedName(out, attribute);
1151            out.write("=");
1152
1153            out.write("\"");
1154            out.write(escapeAttributeEntities(attribute.getValue()));
1155            out.write("\"");
1156        }
1157    }
1158
1159    private void printElementNamespace(Writer out, Element element,
1160                                       NamespaceStack namespaces)
1161                             throws IOException {
1162        // Add namespace decl only if it's not the XML namespace and it's
1163        // not the NO_NAMESPACE with the prefix "" not yet mapped
1164        // (we do output xmlns="" if the "" prefix was already used and we
1165        // need to reclaim it for the NO_NAMESPACE)
1166        Namespace ns = element.getNamespace();
1167        if (ns == Namespace.XML_NAMESPACE) {
1168            return;
1169        }
1170        if ( !((ns == Namespace.NO_NAMESPACE) &&
1171               (namespaces.getURI("") == null))) {
1172            printNamespace(out, ns, namespaces);
1173        }
1174    }
1175
1176    private void printAdditionalNamespaces(Writer out, Element element,
1177                                           NamespaceStack namespaces)
1178                                throws IOException {
1179        List list = element.getAdditionalNamespaces();
1180        if (list != null) {
1181            for (int i = 0; i < list.size(); i++) {
1182                Namespace additional = (Namespace)list.get(i);
1183                printNamespace(out, additional, namespaces);
1184            }
1185        }
1186    }
1187
1188    // * * * * * * * * * * Support methods * * * * * * * * * *
1189    // * * * * * * * * * * Support methods * * * * * * * * * *
1190
1191    /**
1192     * This will print a new line only if the newlines flag was set to
1193     * true.
1194     *
1195     * @param out <code>Writer</code> to use
1196     */
1197    private void newline(Writer out) throws IOException {
1198        if (currentFormat.indent != null) {
1199            out.write(currentFormat.lineSeparator);
1200        }
1201    }
1202
1203    /**
1204     * This will print indents (only if the newlines flag was
1205     * set to <code>true</code>, and indent is non-null).
1206     *
1207     * @param out <code>Writer</code> to use
1208     * @param level current indent level (number of tabs)
1209     */
1210    private void indent(Writer out, int level) throws IOException {
1211        if (currentFormat.indent == null ||
1212            currentFormat.indent.equals("")) {
1213            return;
1214        }
1215
1216        for (int i = 0; i < level; i++) {
1217            out.write(currentFormat.indent);
1218        }
1219    }
1220
1221    // Returns the index of the first non-all-whitespace CDATA or Text,
1222    // index = content.size() is returned if content contains
1223    // all whitespace.
1224    // @param start index to begin search (inclusive)
1225    private int skipLeadingWhite(List content, int start) {
1226        if (start < 0) {
1227            start = 0;
1228        }
1229
1230        int index = start;
1231        int size = content.size();
1232        if (currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE
1233                || currentFormat.mode == Format.TextMode.NORMALIZE
1234                || currentFormat.mode == Format.TextMode.TRIM) {
1235            while (index < size) {
1236                if (!isAllWhitespace(content.get(index))) {
1237                    return index;
1238                }
1239                index++;
1240            }
1241        }
1242        return index;
1243    }
1244
1245    // Return the index + 1 of the last non-all-whitespace CDATA or
1246    // Text node,  index < 0 is returned
1247    // if content contains all whitespace.
1248    // @param start index to begin search (exclusive)
1249    private int skipTrailingWhite(List content, int start) {
1250        int size = content.size();
1251        if (start > size) {
1252            start = size;
1253        }
1254
1255        int index = start;
1256        if (currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE
1257                || currentFormat.mode == Format.TextMode.NORMALIZE
1258                || currentFormat.mode == Format.TextMode.TRIM) {
1259            while (index >= 0) {
1260                if (!isAllWhitespace(content.get(index - 1)))
1261                    break;
1262                --index;
1263            }
1264        }
1265        return index;
1266    }
1267
1268    // Return the next non-CDATA, non-Text, or non-EntityRef node,
1269    // index = content.size() is returned if there is no more non-CDATA,
1270    // non-Text, or non-EntiryRef nodes
1271    // @param start index to begin search (inclusive)
1272    private static int nextNonText(List content, int start) {
1273        if (start < 0) {
1274            start = 0;
1275        }
1276
1277        int index = start;
1278        int size = content.size();
1279        while (index < size) {
1280            Object node =  content.get(index);
1281            if (!((node instanceof Text) || (node instanceof EntityRef))) {
1282                return index;
1283            }
1284            index++;
1285        }
1286        return size;
1287    }
1288
1289    // Determine if a Object is all whitespace
1290    private boolean isAllWhitespace(Object obj) {
1291        String str = null;
1292
1293        if (obj instanceof String) {
1294            str = (String) obj;
1295        }
1296        else if (obj instanceof Text) {
1297            str = ((Text) obj).getText();
1298        }
1299        else if (obj instanceof EntityRef) {
1300            return false;
1301        }
1302        else {
1303            return false;
1304        }
1305
1306        for (int i = 0; i < str.length(); i++) {
1307            if (!isWhitespace(str.charAt(i)))
1308                return false;
1309        }
1310        return true;
1311    }
1312
1313    // Determine if a string starts with a XML whitespace.
1314    private boolean startsWithWhite(String str) {
1315        if ((str != null) &&
1316            (str.length() > 0) &&
1317            isWhitespace(str.charAt(0))) {
1318           return true;
1319        }
1320        return false;
1321    }
1322
1323    // Determine if a string ends with a XML whitespace.
1324    private boolean endsWithWhite(String str) {
1325        if ((str != null) &&
1326            (str.length() > 0) &&
1327            isWhitespace(str.charAt(str.length() - 1))) {
1328           return true;
1329        }
1330        return false;
1331    }
1332
1333    // Determine if a character is a XML whitespace.
1334    // XXX should this method be in Verifier
1335    private static boolean isWhitespace(char c) {
1336        if (c==' ' || c=='\n' || c=='\t' || c=='\r' ){
1337            return true;
1338        }
1339        return false;
1340    }
1341
1342    /**
1343     * This will take the pre-defined entities in XML 1.0 and
1344     * convert their character representation to the appropriate
1345     * entity reference, suitable for XML attributes.  It does not convert
1346     * the single quote (') because it's not necessary as the outputter
1347     * writes attributes surrounded by double-quotes.
1348     *
1349     * @param str <code>String</code> input to escape.
1350     * @return <code>String</code> with escaped content.
1351     */
1352    public String escapeAttributeEntities(String str) {
1353        StringBuffer buffer;
1354        char ch;
1355        String entity;
1356        EscapeStrategy strategy = currentFormat.escapeStrategy;
1357
1358        buffer = null;
1359        for (int i = 0; i < str.length(); i++) {
1360            ch = str.charAt(i);
1361            switch(ch) {
1362                case '<' :
1363                    entity = "&lt;";
1364                    break;
1365                case '>' :
1366                    entity = "&gt;";
1367                    break;
1368/*
1369                case '\'' :
1370                    entity = "&apos;";
1371                    break;
1372*/
1373                case '\"' :
1374                    entity = "&quot;";
1375                    break;
1376                case '&' :
1377                    entity = "&amp;";
1378                    break;
1379                case '\r' :
1380                    entity = "&#xD;";
1381                    break;
1382                case '\t' :
1383                    entity = "&#x9;";
1384                    break;
1385                case '\n' :
1386                    entity = "&#xA;";
1387                    break;
1388                default :
1389                    if (strategy.shouldEscape(ch)) {
1390                        entity = "&#x" + Integer.toHexString(ch) + ";";
1391                    }
1392                    else {
1393                        entity = null;
1394                    }
1395                    break;
1396            }
1397            if (buffer == null) {
1398                if (entity != null) {
1399                    // An entity occurred, so we'll have to use StringBuffer
1400                    // (allocate room for it plus a few more entities).
1401                    buffer = new</