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