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.jdom.Attribute;
57  import org.jdom.CDATA;
58  import org.jdom.Comment;
59  import org.jdom.DocType;
60  import org.jdom.Document;
61  import org.jdom.Element;
62  import org.jdom.EntityRef;
63  import org.jdom.Namespace;
64  import org.jdom.ProcessingInstruction;
65  import org.jdom.Text;
66  import org.jdom.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 and
80   * comments parts.</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  * seperator (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  * Unfortunatly 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         // "UTF-8" is not recognized before JDK 1.1.6, so we'll translate
368         // into "UTF8" which works with all JDKs.
369         if ( "UTF-8".equals( enc ) )
370         {
371             enc = "UTF8";
372         }
373 
374         Writer writer = new BufferedWriter( ( new OutputStreamWriter( new BufferedOutputStream( out ), enc ) ) );
375         return writer;
376     }
377 
378     // * * * * * * * * * * Output to a Writer * * * * * * * * * *
379     // * * * * * * * * * * Output to a Writer * * * * * * * * * *
380 
381     /**
382      * <p>This will print the <code>Document</code> to the given Writer.</p>
383      * 
384      * <p>Warning: using your own Writer may cause the outputter's
385      * preferred character encoding to be ignored.  If you use
386      * encodings other than UTF-8, we recommend using the method that
387      * takes an OutputStream instead.</p>
388      *
389      * @param doc <code>Document</code> to format.
390      * @param out <code>Writer</code> to use.
391      * @throws IOException - if there's any problem writing.
392      */
393     public void output( Document doc, Writer out )
394         throws IOException
395     {
396 
397         printDeclaration( out, doc, userFormat.encoding );
398 
399         // Print out root element, as well as any root level
400         // comments and processing instructions,
401         // starting with no indentation
402         List<?> content = doc.getContent();
403         int size = content.size();
404         for ( int i = 0; i < size; i++ )
405         {
406             Object obj = content.get( i );
407 
408             if ( obj instanceof Element )
409             {
410                 printElement( out, doc.getRootElement(), 0, createNamespaceStack() );
411             }
412             else if ( obj instanceof Comment )
413             {
414                 printComment( out, (Comment) obj );
415             }
416             else if ( obj instanceof ProcessingInstruction )
417             {
418                 printProcessingInstruction( out, (ProcessingInstruction) obj );
419             }
420             else if ( obj instanceof DocType )
421             {
422                 printDocType( out, doc.getDocType() );
423                 // Always print line separator after declaration, helps the
424                 // output look better and is semantically inconsequential
425                 out.write( currentFormat.lineSeparator );
426             }
427             else
428             {
429                 // XXX if we get here then we have a illegal content, for
430                 //     now we'll just ignore it
431             }
432 
433             newline( out );
434             indent( out, 0 );
435         }
436 
437         // Output final line separator
438         // We output this no matter what the newline flags say
439         out.write( currentFormat.lineSeparator );
440     }
441 
442     /**
443      * Print out the <code>{@link DocType}</code>.
444      *
445      * @param doctype <code>DocType</code> to output.
446      * @param out     <code>Writer</code> to use.
447      */
448     public void output( DocType doctype, Writer out )
449         throws IOException
450     {
451         printDocType( out, doctype );
452     }
453 
454     /**
455      * Print out an <code>{@link Element}</code>, including
456      * its <code>{@link Attribute}</code>s, and all
457      * contained (child) elements, etc.
458      *
459      * @param element <code>Element</code> to output.
460      * @param out     <code>Writer</code> to use.
461      */
462     public void output( Element element, Writer out )
463         throws IOException
464     {
465         // If this is the root element we could pre-initialize the
466         // namespace stack with the namespaces
467         printElement( out, element, 0, createNamespaceStack() );
468     }
469 
470     /**
471      * This will handle printing out an <code>{@link
472      * Element}</code>'s content only, not including its tag, and
473      * attributes.  This can be useful for printing the content of an
474      * element that contains HTML, like "&lt;description&gt;JDOM is
475      * &lt;b&gt;fun&gt;!&lt;/description&gt;".
476      *
477      * @param element <code>Element</code> to output.
478      * @param out     <code>Writer</code> to use.
479      */
480     public void outputElementContent( Element element, Writer out )
481         throws IOException
482     {
483         List<?> content = element.getContent();
484         printContentRange( out, content, 0, content.size(),
485             0, createNamespaceStack() );
486     }
487 
488     /**
489      * This will handle printing out a list of nodes.
490      * This can be useful for printing the content of an element that
491      * contains HTML, like "&lt;description&gt;JDOM is
492      * &lt;b&gt;fun&gt;!&lt;/description&gt;".
493      *
494      * @param list <code>List</code> of nodes.
495      * @param out  <code>Writer</code> to use.
496      */
497     public void output( List<?> list, Writer out )
498         throws IOException
499     {
500         printContentRange( out, list, 0, list.size(), 0, createNamespaceStack() );
501     }
502 
503     /**
504      * Print out a <code>{@link CDATA}</code> node.
505      *
506      * @param cdata <code>CDATA</code> to output.
507      * @param out   <code>Writer</code> to use.
508      */
509     public void output( CDATA cdata, Writer out )
510         throws IOException
511     {
512         printCDATA( out, cdata );
513     }
514 
515     /**
516      * Print out a <code>{@link Text}</code> node.  Perfoms
517      * the necessary entity escaping and whitespace stripping.
518      *
519      * @param text <code>Text</code> to output.
520      * @param out  <code>Writer</code> to use.
521      */
522     public void output( Text text, Writer out )
523         throws IOException
524     {
525         printText( out, text );
526     }
527 
528     /**
529      * Print out a <code>{@link Comment}</code>.
530      *
531      * @param comment <code>Comment</code> to output.
532      * @param out     <code>Writer</code> to use.
533      */
534     public void output( Comment comment, Writer out )
535         throws IOException
536     {
537         printComment( out, comment );
538     }
539 
540     /**
541      * Print out a <code>{@link ProcessingInstruction}</code>.
542      *
543      * @param pi  <code>ProcessingInstruction</code> to output.
544      * @param out <code>Writer</code> to use.
545      */
546     public void output( ProcessingInstruction pi, Writer out )
547         throws IOException
548     {
549         boolean currentEscapingPolicy = currentFormat.ignoreTrAXEscapingPIs;
550 
551         // Output PI verbatim, disregarding TrAX escaping PIs.
552         currentFormat.setIgnoreTrAXEscapingPIs( true );
553         printProcessingInstruction( out, pi );
554         currentFormat.setIgnoreTrAXEscapingPIs( currentEscapingPolicy );
555     }
556 
557     /**
558      * Print out a <code>{@link EntityRef}</code>.
559      *
560      * @param entity <code>EntityRef</code> to output.
561      * @param out    <code>Writer</code> to use.
562      */
563     public void output( EntityRef entity, Writer out )
564         throws IOException
565     {
566         printEntityRef( out, entity );
567     }
568 
569     // * * * * * * * * * * Output to a String * * * * * * * * * *
570     // * * * * * * * * * * Output to a String * * * * * * * * * *
571 
572     /**
573      * Return a string representing a document.  Uses an internal
574      * StringWriter. Warning: a String is Unicode, which may not match
575      * the outputter's specified encoding.
576      *
577      * @param doc <code>Document</code> to format.
578      */
579     public String outputString( Document doc )
580     {
581         StringWriter out = new StringWriter();
582         try
583         {
584             output( doc, out );  // output() flushes
585         }
586         catch ( IOException e )
587         {
588         }
589         return out.toString();
590     }
591 
592     /**
593      * Return a string representing a DocType. Warning: a String is
594      * Unicode, which may not match the outputter's specified
595      * encoding.
596      *
597      * @param doctype <code>DocType</code> to format.
598      */
599     public String outputString( DocType doctype )
600     {
601         StringWriter out = new StringWriter();
602         try
603         {
604             output( doctype, out );  // output() flushes
605         }
606         catch ( IOException e )
607         {
608         }
609         return out.toString();
610     }
611 
612     /**
613      * Return a string representing an element. Warning: a String is
614      * Unicode, which may not match the outputter's specified
615      * encoding.
616      *
617      * @param element <code>Element</code> to format.
618      */
619     public String outputString( Element element )
620     {
621         StringWriter out = new StringWriter();
622         try
623         {
624             output( element, out );  // output() flushes
625         }
626         catch ( IOException e )
627         {
628         }
629         return out.toString();
630     }
631 
632     /**
633      * Return a string representing a list of nodes.  The list is
634      * assumed to contain legal JDOM nodes.
635      *
636      * @param list <code>List</code> to format.
637      */
638     public String outputString( List<?> list )
639     {
640         StringWriter out = new StringWriter();
641         try
642         {
643             output( list, out );  // output() flushes
644         }
645         catch ( IOException e )
646         {
647         }
648         return out.toString();
649     }
650 
651     /**
652      * Return a string representing a CDATA node. Warning: a String is
653      * Unicode, which may not match the outputter's specified
654      * encoding.
655      *
656      * @param cdata <code>CDATA</code> to format.
657      */
658     public String outputString( CDATA cdata )
659     {
660         StringWriter out = new StringWriter();
661         try
662         {
663             output( cdata, out );  // output() flushes
664         }
665         catch ( IOException e )
666         {
667         }
668         return out.toString();
669     }
670 
671     /**
672      * Return a string representing a Text node. Warning: a String is
673      * Unicode, which may not match the outputter's specified
674      * encoding.
675      *
676      * @param text <code>Text</code> to format.
677      */
678     public String outputString( Text text )
679     {
680         StringWriter out = new StringWriter();
681         try
682         {
683             output( text, out );  // output() flushes
684         }
685         catch ( IOException e )
686         {
687         }
688         return out.toString();
689     }
690 
691 
692     /**
693      * Return a string representing a comment. Warning: a String is
694      * Unicode, which may not match the outputter's specified
695      * encoding.
696      *
697      * @param comment <code>Comment</code> to format.
698      */
699     public String outputString( Comment comment )
700     {
701         StringWriter out = new StringWriter();
702         try
703         {
704             output( comment, out );  // output() flushes
705         }
706         catch ( IOException e )
707         {
708         }
709         return out.toString();
710     }
711 
712     /**
713      * Return a string representing a PI. Warning: a String is
714      * Unicode, which may not match the outputter's specified
715      * encoding.
716      *
717      * @param pi <code>ProcessingInstruction</code> to format.
718      */
719     public String outputString( ProcessingInstruction pi )
720     {
721         StringWriter out = new StringWriter();
722         try
723         {
724             output( pi, out );  // output() flushes
725         }
726         catch ( IOException e )
727         {
728         }
729         return out.toString();
730     }
731 
732     /**
733      * Return a string representing an entity. Warning: a String is
734      * Unicode, which may not match the outputter's specified
735      * encoding.
736      *
737      * @param entity <code>EntityRef</code> to format.
738      */
739     public String outputString( EntityRef entity )
740     {
741         StringWriter out = new StringWriter();
742         try
743         {
744             output( entity, out );  // output() flushes
745         }
746         catch ( IOException e )
747         {
748         }
749         return out.toString();
750     }
751 
752     // * * * * * * * * * * Internal printing methods * * * * * * * * * *
753     // * * * * * * * * * * Internal printing methods * * * * * * * * * *
754 
755     /**
756      * This will handle printing of the declaration.
757      * Assumes XML version 1.0 since we don't directly know.
758      *
759      * @param doc      <code>Document</code> whose declaration to write.
760      * @param out      <code>Writer</code> to use.
761      * @param encoding The encoding to add to the declaration
762      */
763     protected void printDeclaration( Writer out, Document doc, String encoding )
764         throws IOException
765     {
766 
767         // Only print the declaration if it's not being omitted
768         if ( !userFormat.omitDeclaration )
769         {
770             // Assume 1.0 version
771             out.write( "<?xml version=\"1.0\"" );
772             if ( !userFormat.omitEncoding )
773             {
774                 out.write( " encoding=\"" + encoding + "\"" );
775             }
776             out.write( "?>" );
777 
778             // Print new line after decl always, even if no other new lines
779             // Helps the output look better and is semantically
780             // inconsequential
781             out.write( currentFormat.lineSeparator );
782         }
783     }
784 
785     /**
786      * This handle printing the DOCTYPE declaration if one exists.
787      *
788      * @param docType <code>Document</code> whose declaration to write.
789      * @param out     <code>Writer</code> to use.
790      */
791     protected void printDocType( Writer out, DocType docType )
792         throws IOException
793     {
794 
795         String publicID = docType.getPublicID();
796         String systemID = docType.getSystemID();
797         String internalSubset = docType.getInternalSubset();
798         boolean hasPublic = false;
799 
800         out.write( "<!DOCTYPE " );
801         out.write( docType.getElementName() );
802         if ( publicID != null )
803         {
804             out.write( " PUBLIC \"" );
805             out.write( publicID );
806             out.write( "\"" );
807             hasPublic = true;
808         }
809         if ( systemID != null )
810         {
811             if ( !hasPublic )
812             {
813                 out.write( " SYSTEM" );
814             }
815             out.write( " \"" );
816             out.write( systemID );
817             out.write( "\"" );
818         }
819         if ( ( internalSubset != null ) && ( !internalSubset.equals( "" ) ) )
820         {
821             out.write( " [" );
822             out.write( currentFormat.lineSeparator );
823             out.write( docType.getInternalSubset() );
824             out.write( "]" );
825         }
826         out.write( ">" );
827     }
828 
829     /**
830      * This will handle printing of comments.
831      *
832      * @param comment <code>Comment</code> to write.
833      * @param out     <code>Writer</code> to use.
834      */
835     protected void printComment( Writer out, Comment comment )
836         throws IOException
837     {
838         out.write( "<!--" );
839         out.write( StringUtils.unifyLineSeparators( comment.getText(), currentFormat.lineSeparator ) );
840         out.write( "-->" );
841     }
842 
843     /**
844      * This will handle printing of processing instructions.
845      *
846      * @param pi  <code>ProcessingInstruction</code> to write.
847      * @param out <code>Writer</code> to use.
848      */
849     protected void printProcessingInstruction( Writer out, ProcessingInstruction pi )
850         throws IOException
851     {
852         String target = pi.getTarget();
853         boolean piProcessed = false;
854 
855         if ( currentFormat.ignoreTrAXEscapingPIs == false )
856         {
857             if ( target.equals( Result.PI_DISABLE_OUTPUT_ESCAPING ) )
858             {
859                 escapeOutput = false;
860                 piProcessed = true;
861             }
862             else if ( target.equals( Result.PI_ENABLE_OUTPUT_ESCAPING ) )
863             {
864                 escapeOutput = true;
865                 piProcessed = true;
866             }
867         }
868         if ( piProcessed == false )
869         {
870             String rawData = pi.getData();
871 
872             // Write <?target data?> or if no data then just <?target?>
873             if ( !"".equals( rawData ) )
874             {
875                 out.write( "<?" );
876                 out.write( target );
877                 out.write( " " );
878                 out.write( rawData );
879                 out.write( "?>" );
880             }
881             else
882             {
883                 out.write( "<?" );
884                 out.write( target );
885                 out.write( "?>" );
886             }
887         }
888     }
889 
890     /**
891      * This will handle printing a <code>{@link EntityRef}</code>.
892      * Only the entity reference such as <code>&amp;entity;</code>
893      * will be printed. However, subclasses are free to override
894      * this method to print the contents of the entity instead.
895      *
896      * @param entity <code>EntityRef</code> to output.
897      * @param out    <code>Writer</code> to use.
898      */
899     protected void printEntityRef( Writer out, EntityRef entity )
900         throws IOException
901     {
902         out.write( "&" );
903         out.write( entity.getName() );
904         out.write( ";" );
905     }
906 
907     /**
908      * This will handle printing of <code>{@link CDATA}</code> text.
909      *
910      * @param cdata <code>CDATA</code> to output.
911      * @param out   <code>Writer</code> to use.
912      */
913     protected void printCDATA( Writer out, CDATA cdata )
914         throws IOException
915     {
916         String str =
917             ( currentFormat.mode == Format.TextMode.NORMALIZE ) ? cdata.getTextNormalize()
918                             : ( ( currentFormat.mode == Format.TextMode.TRIM ) ? cdata.getText().trim()
919                                             : cdata.getText() );
920         out.write( "<![CDATA[" );
921         out.write( str );
922         out.write( "]]>" );
923     }
924 
925     /**
926      * This will handle printing of <code>{@link Text}</code> strings.
927      *
928      * @param text <code>Text</code> to write.
929      * @param out  <code>Writer</code> to use.
930      */
931     protected void printText( Writer out, Text text )
932         throws IOException
933     {
934         String str =
935             ( currentFormat.mode == Format.TextMode.NORMALIZE ) ? text.getTextNormalize()
936                             : ( ( currentFormat.mode == Format.TextMode.TRIM ) ? text.getText().trim() : text.getText() );
937         out.write( escapeElementEntities( str ) );
938     }
939 
940     /**
941      * This will handle printing a string.  Escapes the element entities,
942      * trims interior whitespace, etc. if necessary.
943      */
944     private void printString( Writer out, String str )
945         throws IOException
946     {
947         if ( currentFormat.mode == Format.TextMode.NORMALIZE )
948         {
949             str = Text.normalizeString( str );
950         }
951         else if ( currentFormat.mode == Format.TextMode.TRIM )
952         {
953             str = str.trim();
954         }
955         out.write( escapeElementEntities( str ) );
956     }
957 
958     /**
959      * This will handle printing of a <code>{@link Element}</code>,
960      * its <code>{@link Attribute}</code>s, and all contained (child)
961      * elements, etc.
962      *
963      * @param element    <code>Element</code> to output.
964      * @param out        <code>Writer</code> to use.
965      * @param level      <code>int</code> level of indention.
966      * @param namespaces <code>List</code> stack of Namespaces in scope.
967      */
968     protected void printElement( Writer out, Element element, int level, NamespaceStack namespaces )
969         throws IOException
970     {
971 
972         List<?> attributes = element.getAttributes();
973         List<?> content = element.getContent();
974 
975         // Check for xml:space and adjust format settings
976         String space = null;
977         if ( attributes != null )
978         {
979             space = element.getAttributeValue( "space", Namespace.XML_NAMESPACE );
980         }
981 
982         Format previousFormat = currentFormat;
983 
984         if ( "default".equals( space ) )
985         {
986             currentFormat = userFormat;
987         }
988         else if ( "preserve".equals( space ) )
989         {
990             currentFormat = preserveFormat;
991         }
992 
993         // Print the beginning of the tag plus attributes and any
994         // necessary namespace declarations
995         out.write( "<" );
996         printQualifiedName( out, element );
997 
998         // Mark our namespace starting point
999         int previouslyDeclaredNamespaces = namespaces.size();
1000 
1001         // Print the element's namespace, if appropriate
1002         printElementNamespace( out, element, namespaces );
1003 
1004         // Print out additional namespace declarations
1005         printAdditionalNamespaces( out, element, namespaces );
1006 
1007         // Print out attributes
1008         if ( attributes != null )
1009         {
1010             printAttributes( out, attributes, element, namespaces );
1011         }
1012 
1013         // Depending on the settings (newlines, textNormalize, etc), we may
1014         // or may not want to print all of the content, so determine the
1015         // index of the start of the content we're interested
1016         // in based on the current settings.
1017 
1018         int start = skipLeadingWhite( content, 0 );
1019         int size = content.size();
1020         if ( start >= size )
1021         {
1022             // Case content is empty or all insignificant whitespace
1023             if ( currentFormat.expandEmptyElements )
1024             {
1025                 out.write( "></" );
1026                 printQualifiedName( out, element );
1027                 out.write( ">" );
1028             }
1029             else
1030             {
1031                 out.write( " />" );
1032             }
1033         }
1034         else
1035         {
1036             out.write( ">" );
1037 
1038             // For a special case where the content is only CDATA
1039             // or Text we don't want to indent after the start or
1040             // before the end tag.
1041 
1042             if ( nextNonText( content, start ) < size )
1043             {
1044                 // Case Mixed Content - normal indentation
1045                 newline( out );
1046                 printContentRange( out, content, start, size,
1047                     level + 1, namespaces );
1048                 newline( out );
1049                 indent( out, level );
1050             }
1051             else
1052             {
1053                 // Case all CDATA or Text - no indentation
1054                 printTextRange( out, content, start, size );
1055             }
1056             out.write( "</" );
1057             printQualifiedName( out, element );
1058             out.write( ">" );
1059         }
1060 
1061         // remove declared namespaces from stack
1062         while ( namespaces.size() > previouslyDeclaredNamespaces )
1063         {
1064             namespaces.pop();
1065         }
1066 
1067         // Restore our format settings
1068         currentFormat = previousFormat;
1069     }
1070 
1071     /**
1072      * This will handle printing of content within a given range.
1073      * The range to print is specified in typical Java fashion; the
1074      * starting index is inclusive, while the ending index is
1075      * exclusive.
1076      *
1077      * @param content    <code>List</code> of content to output
1078      * @param start      index of first content node (inclusive.
1079      * @param end        index of last content node (exclusive).
1080      * @param out        <code>Writer</code> to use.
1081      * @param level      <code>int</code> level of indentation.
1082      * @param namespaces <code>List</code> stack of Namespaces in scope.
1083      */
1084     private void printContentRange( Writer out, List<?> content, int start, int end, int level, NamespaceStack namespaces )
1085         throws IOException
1086     {
1087         boolean firstNode; // Flag for 1st node in content
1088         Object next;       // Node we're about to print
1089         int first, index;  // Indexes into the list of content
1090 
1091         index = start;
1092         while ( index < end )
1093         {
1094             firstNode = ( index == start ) ? true : false;
1095             next = content.get( index );
1096 
1097             //
1098             // Handle consecutive CDATA, Text, and EntityRef nodes all at once
1099             //
1100             if ( ( next instanceof Text ) || ( next instanceof EntityRef ) )
1101             {
1102                 first = skipLeadingWhite( content, index );
1103                 // Set index to next node for loop
1104                 index = nextNonText( content, first );
1105 
1106                 // If it's not all whitespace - print it!
1107                 if ( first < index )
1108                 {
1109                     if ( !firstNode )
1110                     {
1111                         newline( out );
1112                     }
1113                     indent( out, level );
1114                     printTextRange( out, content, first, index );
1115                 }
1116                 continue;
1117             }
1118 
1119             //
1120             // Handle other nodes
1121             //
1122             if ( !firstNode )
1123             {
1124                 newline( out );
1125             }
1126 
1127             indent( out, level );
1128 
1129             if ( next instanceof Comment )
1130             {
1131                 printComment( out, (Comment) next );
1132             }
1133             else if ( next instanceof Element )
1134             {
1135                 printElement( out, (Element) next, level, namespaces );
1136             }
1137             else if ( next instanceof ProcessingInstruction )
1138             {
1139                 printProcessingInstruction( out, (ProcessingInstruction) next );
1140             }
1141             else
1142             {
1143                 // XXX if we get here then we have a illegal content, for
1144                 //     now we'll just ignore it (probably should throw
1145                 //     a exception)
1146             }
1147 
1148             index++;
1149         } /* while */
1150     }
1151 
1152     /**
1153      * This will handle printing of a sequence of <code>{@link CDATA}</code>
1154      * or <code>{@link Text}</code> nodes.  It is an error to have any other
1155      * pass this method any other type of node.
1156      *
1157      * @param content <code>List</code> of content to output
1158      * @param start   index of first content node (inclusive).
1159      * @param end     index of last content node (exclusive).
1160      * @param out     <code>Writer</code> to use.
1161      */
1162     private void printTextRange( Writer out, List<?> content, int start, int end )
1163         throws IOException
1164     {
1165         String previous; // Previous text printed
1166         Object node;     // Next node to print
1167         String next;     // Next text to print
1168 
1169         previous = null;
1170 
1171         // Remove leading whitespace-only nodes
1172         start = skipLeadingWhite( content, start );
1173 
1174         int size = content.size();
1175         if ( start < size )
1176         {
1177             // And remove trialing whitespace-only nodes
1178             end = skipTrailingWhite( content, end );
1179 
1180             for ( int i = start; i < end; i++ )
1181             {
1182                 node = content.get( i );
1183 
1184                 // Get the unmangled version of the text
1185                 // we are about to print
1186                 if ( node instanceof CDATA )
1187                 {
1188                     next = "<![CDATA[" + ( (CDATA) node ).getValue() + "]]>";
1189                 }
1190                 else if ( node instanceof Text )
1191                 {
1192                     next = ( (Text) node ).getText();
1193                 }
1194                 else if ( node instanceof EntityRef )
1195                 {
1196                     next = "&" + ( (EntityRef) node ).getValue() + ";";
1197                 }
1198                 else
1199                 {
1200                     throw new IllegalStateException( "Should see only CDATA, Text, or EntityRef" );
1201                 }
1202 
1203                 // This may save a little time
1204                 if ( next == null || "".equals( next ) )
1205                 {
1206                     continue;
1207                 }
1208 
1209                 // Determine if we need to pad the output (padding is
1210                 // only need in trim or normalizing mode)
1211                 if ( previous != null )
1212                 { // Not 1st node
1213                     if ( currentFormat.mode == Format.TextMode.NORMALIZE
1214                         || currentFormat.mode == Format.TextMode.TRIM )
1215                     {
1216                         if ( ( endsWithWhite( previous ) )
1217                             || ( startsWithWhite( next ) ) )
1218                         {
1219                             out.write( " " );
1220                         }
1221                     }
1222                 }
1223 
1224                 // Print the node
1225                 if ( node instanceof CDATA )
1226                 {
1227                     printCDATA( out, (CDATA) node );
1228                 }
1229                 else if ( node instanceof EntityRef )
1230                 {
1231                     printEntityRef( out, (EntityRef) node );
1232                 }
1233                 else
1234                 {
1235                     printString( out, next );
1236                 }
1237 
1238                 previous = next;
1239             }
1240         }
1241     }
1242 
1243     /**
1244      * This will handle printing of any needed <code>{@link Namespace}</code>
1245      * declarations.
1246      *
1247      * @param ns  <code>Namespace</code> to print definition of
1248      * @param out <code>Writer</code> to use.
1249      */
1250     private void printNamespace( Writer out, Namespace ns, NamespaceStack namespaces )
1251         throws IOException
1252     {
1253         String prefix = ns.getPrefix();
1254         String uri = ns.getURI();
1255 
1256         // Already printed namespace decl?
1257         if ( uri.equals( namespaces.getURI( prefix ) ) )
1258         {
1259             return;
1260         }
1261 
1262         out.write( " xmlns" );
1263         if ( !prefix.equals( "" ) )
1264         {
1265             out.write( ":" );
1266             out.write( prefix );
1267         }
1268         out.write( "=\"" );
1269         out.write( uri );
1270         out.write( "\"" );
1271         namespaces.push( ns );
1272     }
1273 
1274     /**
1275      * This will handle printing of a <code>{@link Attribute}</code> list.
1276      *
1277      * @param attributes <code>List</code> of Attribute objcts
1278      * @param out        <code>Writer</code> to use
1279      */
1280     protected void printAttributes( Writer out, List<?> attributes, Element parent, NamespaceStack namespaces )
1281         throws IOException
1282     {
1283 
1284         // I do not yet handle the case where the same prefix maps to
1285         // two different URIs. For attributes on the same element
1286         // this is illegal; but as yet we don't throw an exception
1287         // if someone tries to do this
1288         // Set prefixes = new HashSet();
1289         for ( int i = 0; i < attributes.size(); i++ )
1290         {
1291             Attribute attribute = (Attribute) attributes.get( i );
1292             Namespace ns = attribute.getNamespace();
1293             if ( ( ns != Namespace.NO_NAMESPACE ) && ( ns != Namespace.XML_NAMESPACE ) )
1294             {
1295                 printNamespace( out, ns, namespaces );
1296             }
1297 
1298             out.write( " " );
1299             printQualifiedName( out, attribute );
1300             out.write( "=" );
1301 
1302             out.write( "\"" );
1303             out.write( escapeAttributeEntities( attribute.getValue() ) );
1304             out.write( "\"" );
1305         }
1306     }
1307 
1308     private void printElementNamespace( Writer out, Element element, NamespaceStack namespaces )
1309         throws IOException
1310     {
1311         // Add namespace decl only if it's not the XML namespace and it's
1312         // not the NO_NAMESPACE with the prefix "" not yet mapped
1313         // (we do output xmlns="" if the "" prefix was already used and we
1314         // need to reclaim it for the NO_NAMESPACE)
1315         Namespace ns = element.getNamespace();
1316         if ( ns == Namespace.XML_NAMESPACE )
1317         {
1318             return;
1319         }
1320         if ( !( ( ns == Namespace.NO_NAMESPACE ) && ( namespaces.getURI( "" ) == null ) ) )
1321         {
1322             printNamespace( out, ns, namespaces );
1323         }
1324     }
1325 
1326     private void printAdditionalNamespaces( Writer out, Element element, NamespaceStack namespaces )
1327         throws IOException
1328     {
1329         List<?> list = element.getAdditionalNamespaces();
1330         if ( list != null )
1331         {
1332             for ( int i = 0; i < list.size(); i++ )
1333             {
1334                 Namespace additional = (Namespace) list.get( i );
1335                 printNamespace( out, additional, namespaces );
1336             }
1337         }
1338     }
1339 
1340     // * * * * * * * * * * Support methods * * * * * * * * * *
1341     // * * * * * * * * * * Support methods * * * * * * * * * *
1342 
1343     /**
1344      * This will print a new line only if the newlines flag was set to
1345      * true.
1346      *
1347      * @param out <code>Writer</code> to use
1348      */
1349     private void newline( Writer out )
1350         throws IOException
1351     {
1352         if ( currentFormat.indent != null )
1353         {
1354             out.write( currentFormat.lineSeparator );
1355         }
1356     }
1357 
1358     /**
1359      * This will print indents (only if the newlines flag was
1360      * set to <code>true</code>, and indent is non-null).
1361      *
1362      * @param out   <code>Writer</code> to use
1363      * @param level current indent level (number of tabs)
1364      */
1365     private void indent( Writer out, int level )
1366         throws IOException
1367     {
1368         if ( currentFormat.indent == null || currentFormat.indent.equals( "" ) )
1369         {
1370             return;
1371         }
1372 
1373         for ( int i = 0; i < level; i++ )
1374         {
1375             out.write( currentFormat.indent );
1376         }
1377     }
1378 
1379     // Returns the index of the first non-all-whitespace CDATA or Text,
1380     // index = content.size() is returned if content contains
1381     // all whitespace.
1382     // @param start index to begin search (inclusive)
1383     private int skipLeadingWhite( List<?> content, int start )
1384     {
1385         if ( start < 0 )
1386         {
1387             start = 0;
1388         }
1389 
1390         int index = start;
1391         int size = content.size();
1392         if ( currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE
1393             || currentFormat.mode == Format.TextMode.NORMALIZE
1394             || currentFormat.mode == Format.TextMode.TRIM )
1395         {
1396             while ( index < size )
1397             {
1398                 if ( !isAllWhitespace( content.get( index ) ) )
1399                 {
1400                     return index;
1401                 }
1402                 index++;
1403             }
1404         }
1405         return index;
1406     }
1407 
1408     // Return the index + 1 of the last non-all-whitespace CDATA or
1409     // Text node,  index < 0 is returned
1410     // if content contains all whitespace.
1411     // @param start index to begin search (exclusive)
1412     private int skipTrailingWhite( List<?> content, int start )
1413     {
1414         int size = content.size();
1415         if ( start > size )
1416         {
1417             start = size;
1418         }
1419 
1420         int index = start;
1421         if ( currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE
1422             || currentFormat.mode == Format.TextMode.NORMALIZE
1423             || currentFormat.mode == Format.TextMode.TRIM )
1424         {
1425             while ( index >= 0 )
1426             {
1427                 if ( !isAllWhitespace( content.get( index - 1 ) ) )
1428                 {
1429                     break;
1430                 }
1431                 --index;
1432             }
1433         }
1434         return index;
1435     }
1436 
1437     // Return the next non-CDATA, non-Text, or non-EntityRef node,
1438     // index = content.size() is returned if there is no more non-CDATA,
1439     // non-Text, or non-EntiryRef nodes
1440     // @param start index to begin search (inclusive)
1441     private static int nextNonText( List<?> content, int start )
1442     {
1443         if ( start < 0 )
1444         {
1445             start = 0;
1446         }
1447 
1448         int index = start;
1449         int size = content.size();
1450         while ( index < size )
1451         {
1452             Object node = content.get( index );
1453             if ( !( ( node instanceof Text ) || ( node instanceof EntityRef ) ) )
1454             {
1455                 return index;
1456             }
1457             index++;
1458         }
1459         return size;
1460     }
1461 
1462     // Determine if a Object is all whitespace
1463     private boolean isAllWhitespace( Object obj )
1464     {
1465         String str = null;
1466 
1467         if ( obj instanceof String )
1468         {
1469             str = (String) obj;
1470         }
1471         else if ( obj instanceof Text )
1472         {
1473             str = ( (Text) obj ).getText();
1474         }
1475         else if ( obj instanceof EntityRef )
1476         {
1477             return false;
1478         }
1479         else
1480         {
1481             return false;
1482         }
1483 
1484         for ( int i = 0; i < str.length(); i++ )
1485         {
1486             if ( !isWhitespace( str.charAt( i ) ) )
1487             {
1488                 return false;
1489             }
1490         }
1491         return true;
1492     }
1493 
1494     // Determine if a string starts with a XML whitespace.
1495     private boolean startsWithWhite( String str )
1496     {
1497         return ( ( str != null ) && ( str.length() > 0 ) && isWhitespace( str.charAt( 0 ) ) );
1498     }
1499 
1500     // Determine if a string ends with a XML whitespace.
1501     private boolean endsWithWhite( String str )
1502     {
1503         return ( ( str != null ) && ( str.length() > 0 ) && isWhitespace( str.charAt( str.length() - 1 ) ) );
1504     }
1505 
1506     // Determine if a character is a XML whitespace.
1507     // XXX should this method be in Verifier
1508     private static boolean isWhitespace( char c )
1509     {
1510         return ( c == ' ' || c == '\n' || c == '\t' || c == '\r' );
1511     }
1512 
1513     /**
1514      * This will take the pre-defined entities in XML 1.0 and
1515      * convert their character representation to the appropriate
1516      * entity reference, suitable for XML attributes.  It does not convert
1517      * the single quote (') because it's not necessary as the outputter
1518      * writes attributes surrounded by double-quotes.
1519      *
1520      * @param str <code>String</code> input to escape.
1521      * @return <code>String</code> with escaped content.
1522      */
1523     public String escapeAttributeEntities( String str )
1524     {
1525         StringBuilder buffer;
1526         char ch;
1527         String entity;
1528         EscapeStrategy strategy = currentFormat.escapeStrategy;
1529 
1530         buffer = null;
1531         for ( int i = 0; i < str.length(); i++ )
1532         {
1533             ch = str.charAt( i );
1534             switch ( ch )
1535             {
1536                 case '<':
1537                     entity = "&lt;";
1538                     break;
1539                 case '>':
1540                     entity = "&gt;";
1541                     break;
1542 /*
1543                 case '\'' :
1544                     entity = "&apos;";
1545                     break;
1546 */
1547                 case '\"':
1548                     entity = "&quot;";
1549                     break;
1550                 case '&':
1551                     entity = "&amp;";
1552                     break;
1553                 case '\r':
1554                     entity = "&#xD;";
1555                     break;
1556                 case '\t':
1557                     entity = "&#x9;";
1558                     break;
1559                 case '\n':
1560                     entity = "&#xA;";
1561                     break;
1562                 default:
1563                     if ( strategy.shouldEscape( ch ) )
1564                     {
1565                         entity = "&#x" + Integer.toHexString( ch ) + ";";
1566                     }
1567                     else
1568                     {
1569                         entity = null;
1570                     }
1571                     break;
1572             }
1573             if ( buffer == null )
1574             {
1575                 if ( entity != null )
1576                 {
1577                     // An entity occurred, so we'll have to use StringBuilder
1578                     // (allocate room for it plus a few more entities).
1579                     buffer = new StringBuilder( str.length() + 20 );
1580                     // Copy previous skipped characters and fall through
1581                     // to pickup current character
1582                     buffer.append( str.substring( 0, i ) );
1583                     buffer.append( entity );
1584                 }
1585             }
1586             else
1587             {
1588                 if ( entity == null )
1589                 {
1590                     buffer.append( ch );
1591                 }
1592                 else
1593                 {
1594                     buffer.append( entity );
1595                 }
1596             }
1597         }
1598 
1599         // If there were any entities, return the escaped characters
1600         // that we put in the StringBuilder. Otherwise, just return
1601         // the unmodified input string.
1602         return ( buffer == null ) ? str : buffer.toString();
1603     }
1604 
1605 
1606     /**
1607      * This will take the three pre-defined entities in XML 1.0
1608      * (used specifically in XML elements) and convert their character
1609      * representation to the appropriate entity reference, suitable for
1610      * XML element content.
1611      *
1612      * @param str <code>String</code> input to escape.
1613      * @return <code>String</code> with escaped content.
1614      */
1615     public String escapeElementEntities( String str )
1616     {
1617         if ( escapeOutput == false )
1618         {
1619             return str;
1620         }
1621 
1622         StringBuilder buffer;
1623         char ch;
1624         String entity;
1625         EscapeStrategy strategy = currentFormat.escapeStrategy;
1626 
1627         buffer = null;
1628         for ( int i = 0; i < str.length(); i++ )
1629         {
1630             ch = str.charAt( i );
1631             switch ( ch )
1632             {
1633                 case '<':
1634                     entity = "&lt;";
1635                     break;
1636                 case '>':
1637                     entity = "&gt;";
1638                     break;
1639                 case '&':
1640                     entity = "&amp;";
1641                     break;
1642                 case '\r':
1643                     entity = "&#xD;";
1644                     break;
1645                 case '\n':
1646                     entity = currentFormat.lineSeparator;
1647                     break;
1648                 default:
1649                     if ( strategy.shouldEscape( ch ) )
1650                     {
1651                         entity = "&#x" + Integer.toHexString( ch ) + ";";
1652                     }
1653                     else
1654                     {
1655                         entity = null;
1656                     }
1657                     break;
1658             }
1659             if ( buffer == null )
1660             {
1661                 if ( entity != null )
1662                 {
1663                     // An entity occurred, so we'll have to use StringBuilder
1664                     // (allocate room for it plus a few more entities).
1665                     buffer = new StringBuilder( str.length() + 20 );
1666                     // Copy previous skipped characters and fall through
1667                     // to pickup current character
1668                     buffer.append( str.substring( 0, i ) );
1669                     buffer.append( entity );
1670                 }
1671             }
1672             else
1673             {
1674                 if ( entity == null )
1675                 {
1676                     buffer.append( ch );
1677                 }
1678                 else
1679                 {
1680                     buffer.append( entity );
1681                 }
1682             }
1683         }
1684 
1685         // If there were any entities, return the escaped characters
1686         // that we put in the StringBuilder. Otherwise, just return
1687         // the unmodified input string.
1688         return ( buffer == null ) ? str : buffer.toString();
1689     }
1690 
1691     /** Returns a copy of this XMLOutputter. */
1692     @Override
1693     public Object clone()
1694     {
1695         // Implementation notes: Since all state of an XMLOutputter is
1696         // embodied in simple private instance variables, Object.clone
1697         // can be used.  Note that since Object.clone is totally
1698         // broken, we must catch an exception that will never be
1699         // thrown.
1700         try
1701         {
1702             return super.clone();
1703         }
1704         catch ( java.lang.CloneNotSupportedException e )
1705         {
1706             // even though this should never ever happen, it's still
1707             // possible to fool Java into throwing a
1708             // CloneNotSupportedException.  If that happens, we
1709             // shouldn't swallow it.
1710             throw new RuntimeException( e.toString() );
1711         }
1712     }
1713 
1714     /**
1715      * Return a string listing of the settings for this
1716      * XMLOutputter instance.
1717      *
1718      * @return a string listing the settings for this XMLOutputter instance
1719      */
1720     @Override
1721     public String toString()
1722     {
1723         StringBuilder buffer = new StringBuilder();
1724         for ( int i = 0; i < userFormat.lineSeparator.length(); i++ )
1725         {
1726             char ch = userFormat.lineSeparator.charAt( i );
1727             switch ( ch )
1728             {
1729                 case '\r':
1730                     buffer.append( "\\r" );
1731                     break;
1732                 case '\n':
1733                     buffer.append( "\\n" );
1734                     break;
1735                 case '\t':
1736                     buffer.append( "\\t" );
1737                     break;
1738                 default:
1739                     buffer.append( "[" + ( (int) ch ) + "]" );
1740                     break;
1741             }
1742         }
1743 
1744         return (
1745             "XMLOutputter[omitDeclaration = " + userFormat.omitDeclaration + ", "
1746                 + "encoding = " + userFormat.encoding + ", "
1747                 + "omitEncoding = " + userFormat.omitEncoding + ", "
1748                 + "indent = '" + userFormat.indent + "'" + ", "
1749                 + "expandEmptyElements = " + userFormat.expandEmptyElements + ", "
1750                 + "lineSeparator = '" + buffer.toString() + "', "
1751                 + "textMode = " + userFormat.mode + "]"
1752         );
1753     }
1754 
1755     /**
1756      * Factory for making new NamespaceStack objects.  The NamespaceStack
1757      * created is actually an inner class extending the package protected
1758      * NamespaceStack, as a way to make NamespaceStack "friendly" toward
1759      * subclassers.
1760      */
1761     private NamespaceStack createNamespaceStack()
1762     {
1763         // actually returns a XMLOutputter.NamespaceStack (see below)
1764         return new NamespaceStack();
1765     }
1766 
1767     /**
1768      * Our own null subclass of NamespaceStack.  This plays a little
1769      * trick with Java access protection.  We want subclasses of
1770      * XMLOutputter to be able to override protected methods that
1771      * declare a NamespaceStack parameter, but we don't want to
1772      * declare the parent NamespaceStack class as public.
1773      */
1774     protected class NamespaceStack
1775         extends org.apache.maven.archetype.common.util.NamespaceStack
1776     {
1777     }
1778 
1779     // Support method to print a name without using elt.getQualifiedName()
1780     // and thus avoiding a StringBuilder creation and memory churn
1781     private void printQualifiedName( Writer out, Element e )
1782         throws IOException
1783     {
1784         if ( e.getNamespace().getPrefix().length() == 0 )
1785         {
1786             out.write( e.getName() );
1787         }
1788         else
1789         {
1790             out.write( e.getNamespace().getPrefix() );
1791             out.write( ':' );
1792             out.write( e.getName() );
1793         }
1794     }
1795 
1796     // Support method to print a name without using att.getQualifiedName()
1797     // and thus avoiding a StringBuilder creation and memory churn
1798     private void printQualifiedName( Writer out, Attribute a )
1799         throws IOException
1800     {
1801         String prefix = a.getNamespace().getPrefix();
1802         if ( ( prefix != null ) && ( !prefix.equals( "" ) ) )
1803         {
1804             out.write( prefix );
1805             out.write( ':' );
1806             out.write( a.getName() );
1807         }
1808         else
1809         {
1810             out.write( a.getName() );
1811         }
1812     }
1813 
1814     // * * * * * * * * * * Deprecated methods * * * * * * * * * *
1815 
1816     /* The methods below here are deprecations of protected methods.  We
1817      * don't usually deprecate protected methods, so they're commented out.
1818      * They're left here in case this mass deprecation causes people trouble.
1819      * Since we're getting close to 1.0 it's actually better for people to
1820      * raise issues early though.
1821      */
1822 
1823 }