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 <empty/>, but this can be
113 * configured with <code>{@link Format#setExpandEmptyElements}</code> to cause
114 * them to be expanded to <empty></empty>.
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 & Will (from Post Tool Design)
122 * @author Dan Schaffer
123 * @author Alex Chaffee
124 * @author Bradley S. Huffman
125 * @version $Revision: 961255 $, $Date: 2015-08-09 18:06:52 +0000 (Sun, 09 Aug 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: 961255 $ $Date: 2015-08-09 18:06:52 +0000 (Sun, 09 Aug 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 "<description>JDOM is
263 * <b>fun>!</description>".
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 "<description>JDOM is
279 * <b>fun>!</description>".
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 "<description>JDOM is
485 * <b>fun>!</description>".
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 "<description>JDOM is
503 * <b>fun>!</description>".
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>&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 = "<";
1556 break;
1557 case '>':
1558 entity = ">";
1559 break;
1560 /*
1561 case '\'' :
1562 entity = "'";
1563 break;
1564 */
1565 case '\"':
1566 entity = """;
1567 break;
1568 case '&':
1569 entity = "&";
1570 break;
1571 case '\r':
1572 entity = "
";
1573 break;
1574 case '\t':
1575 entity = "	";
1576 break;
1577 case '\n':
1578 entity = "
";
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 = "<";
1653 break;
1654 case '>':
1655 entity = ">";
1656 break;
1657 case '&':
1658 entity = "&";
1659 break;
1660 case '\r':
1661 entity = "
";
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 }