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
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
66
67
68 public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent )
69 {
70 this( writer, lineIndent, null, null );
71 }
72
73
74
75
76
77 public PrettyPrintXMLWriter( Writer writer, String lineIndent )
78 {
79 this( new PrintWriter( writer ), lineIndent );
80 }
81
82
83
84
85 public PrettyPrintXMLWriter( PrintWriter writer )
86 {
87 this( writer, null, null );
88 }
89
90
91
92
93 public PrettyPrintXMLWriter( Writer writer )
94 {
95 this( new PrintWriter( writer ) );
96 }
97
98
99
100
101
102
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
111
112
113
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
122
123
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
132
133
134
135 public PrettyPrintXMLWriter( Writer writer, String encoding, String doctype )
136 {
137 this( new PrintWriter( writer ), encoding, doctype );
138 }
139
140
141
142
143
144
145
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
155
156
157
158
159
160 private PrettyPrintXMLWriter( PrintWriter writer, char[] lineIndent, char[] lineSeparator, String encoding,
161 String doctype )
162 {
163 super();
164 this.writer = writer;
165 this.lineIndent = lineIndent;
166 this.lineSeparator = lineSeparator;
167 this.encoding = encoding;
168 this.docType = doctype;
169
170 depth = 0;
171
172
173 assert !writer.checkError() : "Unexpected error state PrintWriter passed to PrettyPrintXMLWriter.";
174 }
175
176
177 public void addAttribute( String key, String value ) throws IOException
178 {
179 if ( !processingElement )
180 {
181 throw new IllegalStateException( "currently processing no element" );
182 }
183
184 writer.write( ' ' );
185 writer.write( key );
186 writer.write( '=' );
187 XMLEncode.xmlEncodeTextAsPCDATA( value, true, '"', writer );
188 if ( writer.checkError() )
189 {
190 throw new IOException( "Failure adding attribute '" + key + "' with value '" + value + "'" );
191 }
192 }
193
194
195 public void setEncoding( String encoding )
196 {
197 if ( documentStarted )
198 {
199 throw new IllegalStateException( "Document headers already written!" );
200 }
201
202 this.encoding = encoding;
203 }
204
205
206 public void setDocType( String docType )
207 {
208 if ( documentStarted )
209 {
210 throw new IllegalStateException( "Document headers already written!" );
211 }
212
213 this.docType = docType;
214 }
215
216
217
218
219 public void setLineSeparator( String lineSeparator )
220 {
221 if ( documentStarted )
222 {
223 throw new IllegalStateException( "Document headers already written!" );
224 }
225
226 this.lineSeparator = lineSeparator.toCharArray();
227 }
228
229
230
231
232 public void setLineIndenter( String lineIndentParameter )
233 {
234 if ( documentStarted )
235 {
236 throw new IllegalStateException( "Document headers already written!" );
237 }
238
239 this.lineIndent = lineIndentParameter.toCharArray();
240 }
241
242
243 public void startElement( String elementName ) throws IOException
244 {
245 boolean firstLine = ensureDocumentStarted();
246
247 completePreviouslyOpenedElement();
248
249 if ( !firstLine )
250 {
251 newLine();
252 }
253
254 writer.write( '<' );
255 writer.write( elementName );
256 if ( writer.checkError() )
257 {
258 throw new IOException( "Failure starting element '" + elementName + "'." );
259 }
260
261 processingElement = true;
262
263 elementStack.add( depth++, elementName );
264 }
265
266
267 public void writeText( String text ) throws IOException
268 {
269 ensureDocumentStarted();
270
271 completePreviouslyOpenedElement();
272
273 XMLEncode.xmlEncodeText( text, writer );
274
275 endOnSameLine = true;
276
277 if ( writer.checkError() )
278 {
279 throw new IOException( "Failure writing text." );
280 }
281 }
282
283
284 public void writeMarkup( String markup ) throws IOException
285 {
286 ensureDocumentStarted();
287
288 completePreviouslyOpenedElement();
289
290 writer.write( markup );
291
292 if ( writer.checkError() )
293 {
294 throw new IOException( "Failure writing markup." );
295 }
296 }
297
298
299 public void endElement() throws IOException
300 {
301 String chars = elementStack.get( --depth );
302 if ( processingElement )
303 {
304
305 writer.write( CLOSE_1 );
306
307 processingElement = false;
308 }
309 else
310 {
311 if ( !endOnSameLine )
312 {
313 newLine();
314 }
315
316
317 writer.write( CLOSE_2 );
318 writer.write( chars );
319 writer.write( '>' );
320 }
321
322 endOnSameLine = false;
323
324 if ( writer.checkError() )
325 {
326 throw new IOException( "Failure ending element." );
327 }
328 }
329
330
331
332
333
334
335 private boolean ensureDocumentStarted()
336 {
337 if ( !documentStarted )
338 {
339 if ( docType != null || encoding != null )
340 {
341 writeDocumentHeader();
342 }
343
344 documentStarted = true;
345
346 return true;
347 }
348
349 return false;
350 }
351
352 private void writeDocumentHeader()
353 {
354 writer.write( "<?xml version=\"1.0\"" );
355
356 if ( encoding != null )
357 {
358 writer.write( " encoding=\"" );
359 writer.write( encoding );
360 writer.write( '\"' );
361 }
362
363 writer.write( "?>" );
364
365 newLine();
366
367 if ( docType != null )
368 {
369 newLine();
370 writer.write( "<!DOCTYPE " );
371 writer.write( docType );
372 writer.write( '>' );
373 }
374 }
375
376 private void newLine()
377 {
378 writer.write( lineSeparator );
379
380 for ( int i = 0; i < depth; i++ )
381 {
382 writer.write( lineIndent );
383 }
384 }
385
386 private void completePreviouslyOpenedElement()
387 {
388 if ( processingElement )
389 {
390 writer.write( '>' );
391 processingElement = false;
392 }
393 }
394
395 }