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