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 org.apache.maven.shared.utils.Os;
23  
24  import java.io.PrintWriter;
25  import java.io.Writer;
26  import java.util.ArrayList;
27  
28  /**
29   * XMLWriter with nice indentation
30   */
31  /**
32   * @author kama
33   *
34   */
35  public class PrettyPrintXMLWriter
36      implements XMLWriter
37  {
38      private static final char[] CLOSE_1 = "/>".toCharArray();
39  
40      private static final char[] CLOSE_2 = "</".toCharArray();
41  
42      private static final char[] DEFAULT_LINE_INDENT = new char[]{ ' ', ' ' };
43  
44      private PrintWriter writer;
45  
46      private ArrayList<String> elementStack = new ArrayList<String>();
47  
48      private boolean processingElement = false;
49  
50      private boolean documentStarted = false;
51  
52      private boolean endOnSameLine = false;
53  
54      private int depth = 0;
55  
56      private char[] lineIndent;
57  
58      private char[] lineSeparator;
59  
60      private String encoding;
61  
62      private String docType;
63  
64      /**
65       * @param writer not null
66       * @param lineIndent could be null, but the normal way is some spaces.
67       */
68      public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent )
69      {
70          this( writer, lineIndent, null, null );
71      }
72  
73      /**
74       * @param writer not null
75       * @param lineIndent could be null, but the normal way is some spaces.
76       */
77      public PrettyPrintXMLWriter( Writer writer, String lineIndent )
78      {
79          this( new PrintWriter( writer ), lineIndent );
80      }
81  
82      /**
83       * @param writer not null
84       */
85      public PrettyPrintXMLWriter( PrintWriter writer )
86      {
87          this( writer, null, null );
88      }
89  
90      /**
91       * @param writer not null
92       */
93      public PrettyPrintXMLWriter( Writer writer )
94      {
95          this( new PrintWriter( writer ) );
96      }
97  
98      /**
99       * @param writer not null
100      * @param lineIndent could be null, but the normal way is some spaces.
101      * @param encoding could be null or invalid.
102      * @param doctype could be null.
103      */
104     public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String encoding, String doctype )
105     {
106         this( writer, lineIndent.toCharArray(), Os.LINE_SEP.toCharArray(), encoding, doctype );
107     }
108 
109     /**
110      * @param writer not null
111      * @param lineIndent could be null, but the normal way is some spaces.
112      * @param encoding could be null or invalid.
113      * @param doctype could be null.
114      */
115     public PrettyPrintXMLWriter( Writer writer, String lineIndent, String encoding, String doctype )
116     {
117         this( new PrintWriter( writer ), lineIndent, 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( PrintWriter writer, String encoding, String doctype )
126     {
127         this( writer, DEFAULT_LINE_INDENT, Os.LINE_SEP.toCharArray(), encoding, doctype );
128     }
129 
130     /**
131      * @param writer not null
132      * @param encoding could be null or invalid.
133      * @param doctype could be null.
134      */
135     public PrettyPrintXMLWriter( Writer writer, String encoding, String doctype )
136     {
137         this( new PrintWriter( writer ), encoding, doctype );
138     }
139 
140     /**
141      * @param writer not null
142      * @param lineIndent could be null, but the normal way is some spaces.
143      * @param lineSeparator could be null, but the normal way is valid line separator
144      * @param encoding could be null or the encoding to use.
145      * @param doctype could be null.
146      */
147     public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String lineSeparator, String encoding,
148                                  String doctype )
149     {
150         this( writer, lineIndent.toCharArray(), lineSeparator.toCharArray(), encoding, doctype );
151     }
152 
153     /**
154      * @param writer        not null
155      * @param lineIndent    could be null, but the normal way is some spaces.
156      * @param lineSeparator could be null, but the normal way is valid line separator
157      * @param encoding      could be null or the encoding to use.
158      * @param doctype       could be null.
159      */
160     private PrettyPrintXMLWriter( PrintWriter writer, char[] lineIndent, char[] lineSeparator, String encoding,
161                                   String doctype )
162     {
163         this.writer = writer;
164         this.lineIndent = lineIndent;
165         this.lineSeparator = lineSeparator;
166         this.encoding = encoding;
167         this.docType = doctype;
168 
169         depth = 0;
170     }
171 
172     /** {@inheritDoc} */
173     public void addAttribute( String key, String value )
174     {
175         if ( !processingElement )
176         {
177             throw new IllegalStateException( "currently processing no element" );
178         }
179 
180         writer.write( ' ' );
181         writer.write( key );
182         writer.write( '=' );
183         XMLEncode.xmlEncodeTextAsPCDATA( value, true, '"', writer );
184     }
185 
186     /** {@inheritDoc} */
187     public void setEncoding( String encoding )
188     {
189         if ( documentStarted )
190         {
191             throw new IllegalStateException( "Document headers already written!" );
192         }
193 
194         this.encoding = encoding;
195     }
196 
197     /** {@inheritDoc} */
198     public void setDocType( String docType )
199     {
200         if ( documentStarted )
201         {
202             throw new IllegalStateException( "Document headers already written!" );
203         }
204 
205         this.docType = docType;
206     }
207 
208     /**
209      * @param lineSeparator The line separator to be used.
210      */
211     public void setLineSeparator( String lineSeparator )
212     {
213         if ( documentStarted )
214         {
215             throw new IllegalStateException( "Document headers already written!" );
216         }
217 
218         this.lineSeparator = lineSeparator.toCharArray();
219     }
220 
221     /**
222      * @param lineIndentParameter The line indent parameter.
223      */
224     public void setLineIndenter( String lineIndentParameter )
225     {
226         if ( documentStarted )
227         {
228             throw new IllegalStateException( "Document headers already written!" );
229         }
230 
231         this.lineIndent = lineIndentParameter.toCharArray();
232     }
233 
234     /** {@inheritDoc} */
235     public void startElement( String elementName )
236     {
237         boolean firstLine = ensureDocumentStarted();
238 
239         completePreviouslyOpenedElement();
240 
241         if ( !firstLine )
242         {
243             newLine();
244         }
245 
246         writer.write( '<' );
247         writer.write( elementName );
248 
249         processingElement = true;
250 
251         elementStack.add( depth++, elementName );
252     }
253 
254     /** {@inheritDoc} */
255     public void writeText( String text )
256     {
257         ensureDocumentStarted();
258 
259         completePreviouslyOpenedElement();
260 
261         XMLEncode.xmlEncodeText( text, writer );
262 
263         endOnSameLine = true;
264     }
265 
266     /** {@inheritDoc} */
267     public void writeMarkup( String markup )
268     {
269         ensureDocumentStarted();
270 
271         completePreviouslyOpenedElement();
272 
273         writer.write( markup );
274     }
275 
276     /** {@inheritDoc} */
277     public void endElement()
278     {
279         String chars = elementStack.get( --depth );
280         if ( processingElement )
281         {
282             // this means we don't have any content yet so we just add a />
283             writer.write( CLOSE_1 );
284 
285             processingElement = false;
286         }
287         else
288         {
289             if ( !endOnSameLine )
290             {
291                 newLine();
292             }
293 
294             // otherwise we need a full closing tag for that element
295             writer.write( CLOSE_2 );
296             writer.write( chars );
297             writer.write( '>' );
298         }
299 
300         endOnSameLine = false;
301     }
302 
303     /**
304      * Write the documents if not already done.
305      *
306      * @return <code>true</code> if the document headers have freshly been written.
307      */
308     private boolean ensureDocumentStarted()
309     {
310         if ( !documentStarted )
311         {
312             if ( docType != null || encoding != null )
313             {
314                 writeDocumentHeader();
315             }
316 
317             documentStarted = true;
318 
319             return true;
320         }
321 
322         return false;
323     }
324 
325     private void writeDocumentHeader()
326     {
327         writer.write( "<?xml version=\"1.0\"" );
328 
329         if ( encoding != null )
330         {
331             writer.write( " encoding=\"" );
332             writer.write( encoding );
333             writer.write( '\"' );
334         }
335 
336         writer.write( "?>" );
337 
338         newLine();
339 
340         if ( docType != null )
341         {
342             newLine();
343             writer.write( "<!DOCTYPE " );
344             writer.write( docType );
345             writer.write( '>' );
346         }
347     }
348 
349     private void newLine()
350     {
351         writer.write( lineSeparator );
352 
353         for ( int i = 0; i < depth; i++ )
354         {
355             writer.write( lineIndent );
356         }
357     }
358 
359     private void completePreviouslyOpenedElement()
360     {
361         if ( processingElement )
362         {
363             writer.write( '>' );
364             processingElement = false;
365         }
366     }
367 
368 }