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