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