1 package org.apache.maven.shared.utils.xml;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.io.Writer;
25 import java.util.ArrayList;
26 import org.apache.maven.shared.utils.Os;
27
28
29
30
31
32
33 public class PrettyPrintXMLWriter
34 implements XMLWriter
35 {
36 private static final char[] CLOSE_1 = "/>".toCharArray();
37
38 private static final char[] CLOSE_2 = "</".toCharArray();
39
40 private static final char[] DEFAULT_LINE_INDENT = new char[]{ ' ', ' ' };
41
42 private PrintWriter writer;
43
44 private ArrayList<String> elementStack = new ArrayList<String>();
45
46 private boolean processingElement = false;
47
48 private boolean documentStarted = false;
49
50 private boolean endOnSameLine = false;
51
52 private int depth = 0;
53
54 private char[] lineIndent;
55
56 private char[] lineSeparator;
57
58 private String encoding;
59
60 private String docType;
61
62
63
64
65
66 public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent )
67 {
68 this( writer, lineIndent, null, null );
69 }
70
71
72
73
74
75 public PrettyPrintXMLWriter( Writer writer, String lineIndent )
76 {
77 this( new PrintWriter( writer ), lineIndent );
78 }
79
80
81
82
83 public PrettyPrintXMLWriter( PrintWriter writer )
84 {
85 this( writer, null, null );
86 }
87
88
89
90
91 public PrettyPrintXMLWriter( Writer writer )
92 {
93 this( new PrintWriter( writer ) );
94 }
95
96
97
98
99
100
101
102 public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String encoding, String doctype )
103 {
104 this( writer, lineIndent.toCharArray(), Os.LINE_SEP.toCharArray(), encoding, doctype );
105 }
106
107
108
109
110
111
112
113 public PrettyPrintXMLWriter( Writer writer, String lineIndent, String encoding, String doctype )
114 {
115 this( new PrintWriter( writer ), lineIndent, encoding, doctype );
116 }
117
118
119
120
121
122
123 public PrettyPrintXMLWriter( PrintWriter writer, String encoding, String doctype )
124 {
125 this( writer, DEFAULT_LINE_INDENT, Os.LINE_SEP.toCharArray(), encoding, doctype );
126 }
127
128
129
130
131
132
133 public PrettyPrintXMLWriter( Writer writer, String encoding, String doctype )
134 {
135 this( new PrintWriter( writer ), encoding, doctype );
136 }
137
138
139
140
141
142
143
144
145 public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String lineSeparator, String encoding,
146 String doctype )
147 {
148 this( writer, lineIndent.toCharArray(), lineSeparator.toCharArray(), encoding, doctype );
149 }
150
151
152
153
154
155
156
157
158 private PrettyPrintXMLWriter( PrintWriter writer, char[] lineIndent, char[] lineSeparator, String encoding,
159 String doctype )
160 {
161 super();
162 this.writer = writer;
163 this.lineIndent = lineIndent;
164 this.lineSeparator = lineSeparator;
165 this.encoding = encoding;
166 this.docType = doctype;
167
168 depth = 0;
169
170
171 assert !writer.checkError() : "Unexpected error state PrintWriter passed to PrettyPrintXMLWriter.";
172 }
173
174
175 public void addAttribute( String key, String value ) throws IOException
176 {
177 if ( !processingElement )
178 {
179 throw new IllegalStateException( "currently processing no element" );
180 }
181
182 writer.write( ' ' );
183 writer.write( key );
184 writer.write( '=' );
185 XMLEncode.xmlEncodeTextAsPCDATA( value, true, '"', writer );
186 if ( writer.checkError() )
187 {
188 throw new IOException( "Failure adding attribute '" + key + "' with value '" + value + "'" );
189 }
190 }
191
192
193 public void setEncoding( String encoding )
194 {
195 if ( documentStarted )
196 {
197 throw new IllegalStateException( "Document headers already written!" );
198 }
199
200 this.encoding = encoding;
201 }
202
203
204 public void setDocType( String docType )
205 {
206 if ( documentStarted )
207 {
208 throw new IllegalStateException( "Document headers already written!" );
209 }
210
211 this.docType = docType;
212 }
213
214
215
216
217 public void setLineSeparator( String lineSeparator )
218 {
219 if ( documentStarted )
220 {
221 throw new IllegalStateException( "Document headers already written!" );
222 }
223
224 this.lineSeparator = lineSeparator.toCharArray();
225 }
226
227
228
229
230 public void setLineIndenter( String lineIndentParameter )
231 {
232 if ( documentStarted )
233 {
234 throw new IllegalStateException( "Document headers already written!" );
235 }
236
237 this.lineIndent = lineIndentParameter.toCharArray();
238 }
239
240
241 public void startElement( String elementName ) throws IOException
242 {
243 boolean firstLine = ensureDocumentStarted();
244
245 completePreviouslyOpenedElement();
246
247 if ( !firstLine )
248 {
249 newLine();
250 }
251
252 writer.write( '<' );
253 writer.write( elementName );
254 if ( writer.checkError() )
255 {
256 throw new IOException( "Failure starting element '" + elementName + "'." );
257 }
258
259 processingElement = true;
260
261 elementStack.add( depth++, elementName );
262 }
263
264
265 public void writeText( String text ) throws IOException
266 {
267 ensureDocumentStarted();
268
269 completePreviouslyOpenedElement();
270
271 XMLEncode.xmlEncodeText( text, writer );
272
273 endOnSameLine = true;
274
275 if ( writer.checkError() )
276 {
277 throw new IOException( "Failure writing text." );
278 }
279 }
280
281
282 public void writeMarkup( String markup ) throws IOException
283 {
284 ensureDocumentStarted();
285
286 completePreviouslyOpenedElement();
287
288 writer.write( markup );
289
290 if ( writer.checkError() )
291 {
292 throw new IOException( "Failure writing markup." );
293 }
294 }
295
296
297 public void endElement() throws IOException
298 {
299 String chars = elementStack.get( --depth );
300 if ( processingElement )
301 {
302
303 writer.write( CLOSE_1 );
304
305 processingElement = false;
306 }
307 else
308 {
309 if ( !endOnSameLine )
310 {
311 newLine();
312 }
313
314
315 writer.write( CLOSE_2 );
316 writer.write( chars );
317 writer.write( '>' );
318 }
319
320 endOnSameLine = false;
321
322 if ( writer.checkError() )
323 {
324 throw new IOException( "Failure ending element." );
325 }
326 }
327
328
329
330
331
332
333 private boolean ensureDocumentStarted()
334 {
335 if ( !documentStarted )
336 {
337 if ( docType != null || encoding != null )
338 {
339 writeDocumentHeader();
340 }
341
342 documentStarted = true;
343
344 return true;
345 }
346
347 return false;
348 }
349
350 private void writeDocumentHeader()
351 {
352 writer.write( "<?xml version=\"1.0\"" );
353
354 if ( encoding != null )
355 {
356 writer.write( " encoding=\"" );
357 writer.write( encoding );
358 writer.write( '\"' );
359 }
360
361 writer.write( "?>" );
362
363 newLine();
364
365 if ( docType != null )
366 {
367 writer.write( "<!DOCTYPE " );
368 writer.write( docType );
369 writer.write( '>' );
370 newLine();
371 }
372 }
373
374 private void newLine()
375 {
376 writer.write( lineSeparator );
377
378 for ( int i = 0; i < depth; i++ )
379 {
380 writer.write( lineIndent );
381 }
382 }
383
384 private void completePreviouslyOpenedElement()
385 {
386 if ( processingElement )
387 {
388 writer.write( '>' );
389 processingElement = false;
390 }
391 }
392
393 }