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