View Javadoc

1   package org.apache.maven.shared.utils.xml;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.PrintWriter;
23  import java.io.Writer;
24  import java.util.LinkedList;
25  
26  import org.apache.maven.shared.utils.Os;
27  
28  /**
29   * XMLWriter with nice indentation
30   */
31  public class PrettyPrintXMLWriter
32      implements XMLWriter
33  {
34      private PrintWriter writer;
35  
36      private LinkedList<String> elementStack = new LinkedList<String>();
37  
38      private boolean processingElement = false;
39  
40      private boolean documentStarted = false;
41  
42      private boolean endOnSameLine = false;
43  
44      private int depth = 0;
45  
46      private String lineIndent;
47  
48      private String lineSeparator;
49  
50      private String encoding;
51  
52      private String docType;
53  
54      /**
55       * @param writer not null
56       * @param lineIndent could be null, but the normal way is some spaces.
57       */
58      public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent )
59      {
60          this( writer, lineIndent, null, null );
61      }
62  
63      /**
64       * @param writer not null
65       * @param lineIndent could be null, but the normal way is some spaces.
66       */
67      public PrettyPrintXMLWriter( Writer writer, String lineIndent )
68      {
69          this( new PrintWriter( writer ), lineIndent );
70      }
71  
72      /**
73       * @param writer not null
74       */
75      public PrettyPrintXMLWriter( PrintWriter writer )
76      {
77          this( writer, null, null );
78      }
79  
80      /**
81       * @param writer not null
82       */
83      public PrettyPrintXMLWriter( Writer writer )
84      {
85          this( new PrintWriter( writer ) );
86      }
87  
88      /**
89       * @param writer not null
90       * @param lineIndent could be null, but the normal way is some spaces.
91       * @param encoding could be null or invalid.
92       * @param doctype could be null.
93       */
94      public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String encoding, String doctype )
95      {
96          this( writer, lineIndent, Os.LINE_SEP, encoding, doctype );
97      }
98  
99      /**
100      * @param writer not null
101      * @param lineIndent could be null, but the normal way is some spaces.
102      * @param encoding could be null or invalid.
103      * @param doctype could be null.
104      */
105     public PrettyPrintXMLWriter( Writer writer, String lineIndent, String encoding, String doctype )
106     {
107         this( new PrintWriter( writer ), lineIndent, encoding, doctype );
108     }
109 
110     /**
111      * @param writer not null
112      * @param encoding could be null or invalid.
113      * @param doctype could be null.
114      */
115     public PrettyPrintXMLWriter( PrintWriter writer, String encoding, String doctype )
116     {
117         this( writer, "  ", encoding, doctype );
118     }
119 
120     /**
121      * @param writer not null
122      * @param encoding could be null or invalid.
123      * @param doctype could be null.
124      */
125     public PrettyPrintXMLWriter( Writer writer, String encoding, String doctype )
126     {
127         this( new PrintWriter( writer ), encoding, doctype );
128     }
129 
130     /**
131      * @param writer not null
132      * @param lineIndent could be null, but the normal way is some spaces.
133      * @param lineSeparator could be null, but the normal way is valid line separator
134      * @param encoding could be null or the encoding to use.
135      * @param doctype could be null.
136      */
137     public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String lineSeparator, String encoding,
138                                  String doctype )
139     {
140         this.writer = writer;
141         this.lineIndent = lineIndent;
142         this.lineSeparator = lineSeparator;
143         this.encoding = encoding;
144         this.docType = doctype;
145 
146         depth = 0;
147     }
148 
149     public void addAttribute( String key, String value )
150     {
151         if ( !processingElement )
152         {
153             throw new IllegalStateException( "currently processing no element" );
154         }
155 
156         writer.write( ' ' );
157         writer.write( key );
158         writer.write( '=' );
159         writer.write( XMLEncode.xmlEncodeTextForAttribute( value, '"' ) );
160     }
161 
162     public void setEncoding( String encoding )
163     {
164         if ( documentStarted )
165         {
166             throw new IllegalStateException( "Document headers already written!" );
167         }
168 
169         this.encoding = encoding;
170     }
171 
172     public void setDocType( String docType )
173     {
174         if ( documentStarted )
175         {
176             throw new IllegalStateException( "Document headers already written!" );
177         }
178 
179         this.docType = docType;
180     }
181 
182     public void setLineSeparator( String lineSeparator )
183     {
184         if ( documentStarted )
185         {
186             throw new IllegalStateException( "Document headers already written!" );
187         }
188 
189         this.lineSeparator = lineSeparator;
190     }
191 
192     public void setLineIndenter( String lineIndent )
193     {
194         if ( documentStarted )
195         {
196             throw new IllegalStateException( "Document headers already written!" );
197         }
198 
199         this.lineIndent = lineIndent;
200     }
201 
202     public void startElement( String elementName )
203     {
204         boolean firstLine = ensureDocumentStarted();
205 
206         completePreviouslyOpenedElement();
207 
208         if ( !firstLine )
209         {
210             newLine();
211         }
212 
213         writer.write( '<' );
214         writer.write( elementName );
215 
216         processingElement = true;
217         depth++;
218 
219         elementStack.addLast( elementName );
220     }
221 
222     public void writeText( String text )
223     {
224         ensureDocumentStarted();
225 
226         completePreviouslyOpenedElement();
227 
228         writer.write( XMLEncode.xmlEncodeText( text ) );
229 
230         endOnSameLine = true;
231     }
232 
233     public void writeMarkup( String markup )
234     {
235         ensureDocumentStarted();
236 
237         completePreviouslyOpenedElement();
238 
239         writer.write( markup );
240     }
241 
242     public void endElement()
243     {
244         depth--;
245 
246         if ( processingElement )
247         {
248             // this means we don't have any content yet so we just add a />
249             writer.write( "/>" );
250 
251             elementStack.removeLast();
252             processingElement = false;
253         }
254         else
255         {
256             if ( !endOnSameLine )
257             {
258                 newLine();
259             }
260 
261             // otherwise we need a full closing tag for that element
262             writer.write( "</" + elementStack.removeLast() + ">" );
263         }
264 
265         endOnSameLine = false;
266     }
267 
268     /**
269      * Write the documents if not already done.
270      * 
271      * @return <code>true</code> if the document headers have freshly been written.
272      */
273     private boolean ensureDocumentStarted()
274     {
275         if ( !documentStarted )
276         {
277             if ( docType != null || encoding != null )
278             {
279                 writeDocumentHeader();
280             }
281 
282             documentStarted = true;
283 
284             return true;
285         }
286 
287         return false;
288     }
289 
290     private void writeDocumentHeader()
291     {
292         writer.write( "<?xml version=\"1.0\"" );
293 
294         if ( encoding != null )
295         {
296             writer.write( " encoding=\"" + encoding + "\"" );
297         }
298 
299         writer.write( "?>" );
300 
301         newLine();
302 
303         if ( docType != null )
304         {
305             newLine();
306             writer.write( "<!DOCTYPE " + docType + ">" );
307         }
308     }
309 
310     private void newLine()
311     {
312         writer.write( lineSeparator );
313 
314         for ( int i = 0; i < depth; i++ )
315         {
316             writer.write( lineIndent );
317         }
318     }
319 
320     private void completePreviouslyOpenedElement()
321     {
322         if ( processingElement )
323         {
324             writer.write( '>' );
325             processingElement = false;
326         }
327     }
328 
329 }