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