1 package org.codehaus.plexus.util.xml;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.io.PrintWriter;
20 import java.io.Writer;
21 import java.util.LinkedList;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import org.codehaus.plexus.util.StringUtils;
26
27
28
29
30
31
32 public class PrettyPrintXMLWriter implements XMLWriter {
33
34 protected static final String LS = System.getProperty("line.separator");
35
36 private PrintWriter writer;
37
38 private LinkedList<String> elementStack = new LinkedList<String>();
39
40 private boolean tagInProgress;
41
42 private int depth;
43
44 private String lineIndenter;
45
46 private String lineSeparator;
47
48 private String encoding;
49
50 private String docType;
51
52 private boolean readyForNewLine;
53
54 private boolean tagIsEmpty;
55
56
57
58
59
60 public PrettyPrintXMLWriter(PrintWriter writer, String lineIndenter) {
61 this(writer, lineIndenter, null, null);
62 }
63
64
65
66
67
68 public PrettyPrintXMLWriter(Writer writer, String lineIndenter) {
69 this(new PrintWriter(writer), lineIndenter);
70 }
71
72
73
74
75 public PrettyPrintXMLWriter(PrintWriter writer) {
76 this(writer, null, null);
77 }
78
79
80
81
82 public PrettyPrintXMLWriter(Writer writer) {
83 this(new PrintWriter(writer));
84 }
85
86
87
88
89
90
91
92 public PrettyPrintXMLWriter(PrintWriter writer, String lineIndenter, String encoding, String doctype) {
93 this(writer, lineIndenter, LS, encoding, doctype);
94 }
95
96
97
98
99
100
101
102 public PrettyPrintXMLWriter(Writer writer, String lineIndenter, String encoding, String doctype) {
103 this(new PrintWriter(writer), lineIndenter, encoding, doctype);
104 }
105
106
107
108
109
110
111 public PrettyPrintXMLWriter(PrintWriter writer, String encoding, String doctype) {
112 this(writer, " ", encoding, doctype);
113 }
114
115
116
117
118
119
120 public PrettyPrintXMLWriter(Writer writer, String encoding, String doctype) {
121 this(new PrintWriter(writer), encoding, doctype);
122 }
123
124
125
126
127
128
129
130
131 public PrettyPrintXMLWriter(
132 PrintWriter writer, String lineIndenter, String lineSeparator, String encoding, String doctype) {
133 setWriter(writer);
134
135 setLineIndenter(lineIndenter);
136
137 setLineSeparator(lineSeparator);
138
139 setEncoding(encoding);
140
141 setDocType(doctype);
142
143 if (doctype != null || encoding != null) {
144 writeDocumentHeaders();
145 }
146 }
147
148
149 @Override
150 public void startElement(String name) {
151 tagIsEmpty = false;
152
153 finishTag();
154
155 write("<");
156
157 write(name);
158
159 elementStack.addLast(name);
160
161 tagInProgress = true;
162
163 setDepth(getDepth() + 1);
164
165 readyForNewLine = true;
166
167 tagIsEmpty = true;
168 }
169
170
171 @Override
172 public void writeText(String text) {
173 writeText(text, true);
174 }
175
176
177 @Override
178 public void writeMarkup(String text) {
179 writeText(text, false);
180 }
181
182 private void writeText(String text, boolean escapeXml) {
183 readyForNewLine = false;
184
185 tagIsEmpty = false;
186
187 finishTag();
188
189 if (escapeXml) {
190 text = escapeXml(text);
191 }
192
193 write(StringUtils.unifyLineSeparators(text, lineSeparator));
194 }
195
196 private static final Pattern amp = Pattern.compile("&");
197
198 private static final Pattern lt = Pattern.compile("<");
199
200 private static final Pattern gt = Pattern.compile(">");
201
202 private static final Pattern dqoute = Pattern.compile("\"");
203
204 private static final Pattern sqoute = Pattern.compile("\'");
205
206 private static String escapeXml(String text) {
207 if (text.indexOf('&') >= 0) {
208 text = amp.matcher(text).replaceAll("&");
209 }
210 if (text.indexOf('<') >= 0) {
211 text = lt.matcher(text).replaceAll("<");
212 }
213 if (text.indexOf('>') >= 0) {
214 text = gt.matcher(text).replaceAll(">");
215 }
216 if (text.indexOf('"') >= 0) {
217 text = dqoute.matcher(text).replaceAll(""");
218 }
219 if (text.indexOf('\'') >= 0) {
220 text = sqoute.matcher(text).replaceAll("'");
221 }
222
223 return text;
224 }
225
226 private static final String crlf_str = "\r\n";
227
228 private static final Pattern crlf = Pattern.compile(crlf_str);
229
230 private static final Pattern lowers = Pattern.compile("([\000-\037])");
231
232 private static String escapeXmlAttribute(String text) {
233 text = escapeXml(text);
234
235
236 Matcher crlfmatcher = crlf.matcher(text);
237 if (text.contains(crlf_str)) {
238 text = crlfmatcher.replaceAll(" ");
239 }
240
241 Matcher m = lowers.matcher(text);
242 StringBuffer b = new StringBuffer();
243 while (m.find()) {
244 m = m.appendReplacement(b, "&#" + Integer.toString(m.group(1).charAt(0)) + ";");
245 }
246 m.appendTail(b);
247
248 return b.toString();
249 }
250
251
252 @Override
253 public void addAttribute(String key, String value) {
254 write(" ");
255
256 write(key);
257
258 write("=\"");
259
260 write(escapeXmlAttribute(value));
261
262 write("\"");
263 }
264
265
266 @Override
267 public void endElement() {
268 setDepth(getDepth() - 1);
269
270 if (tagIsEmpty) {
271 write("/");
272
273 readyForNewLine = false;
274
275 finishTag();
276
277 elementStack.removeLast();
278 } else {
279 finishTag();
280
281
282
283
284
285
286 write("</");
287 write(elementStack.removeLast());
288 write(">");
289 }
290
291 readyForNewLine = true;
292 }
293
294
295
296
297
298
299 private void write(String str) {
300 getWriter().write(str);
301 }
302
303 private void finishTag() {
304 if (tagInProgress) {
305 write(">");
306 }
307
308 tagInProgress = false;
309
310 if (readyForNewLine) {
311 endOfLine();
312 }
313 readyForNewLine = false;
314
315 tagIsEmpty = false;
316 }
317
318
319
320
321
322
323 protected String getLineIndenter() {
324 return lineIndenter;
325 }
326
327
328
329
330
331
332 protected void setLineIndenter(String lineIndenter) {
333 this.lineIndenter = lineIndenter;
334 }
335
336
337
338
339
340
341
342 protected String getLineSeparator() {
343 return lineSeparator;
344 }
345
346
347
348
349
350
351 protected void setLineSeparator(String lineSeparator) {
352 this.lineSeparator = lineSeparator;
353 }
354
355
356
357
358
359
360
361 protected void endOfLine() {
362 write(getLineSeparator());
363
364 for (int i = 0; i < getDepth(); i++) {
365 write(getLineIndenter());
366 }
367 }
368
369 private void writeDocumentHeaders() {
370 write("<?xml version=\"1.0\"");
371
372 if (getEncoding() != null) {
373 write(" encoding=\"" + getEncoding() + "\"");
374 }
375
376 write("?>");
377
378 endOfLine();
379
380 if (getDocType() != null) {
381 write("<!DOCTYPE ");
382
383 write(getDocType());
384
385 write(">");
386
387 endOfLine();
388 }
389 }
390
391
392
393
394
395
396 protected void setWriter(PrintWriter writer) {
397 if (writer == null) {
398 throw new IllegalArgumentException("writer could not be null");
399 }
400
401 this.writer = writer;
402 }
403
404
405
406
407
408
409 protected PrintWriter getWriter() {
410 return writer;
411 }
412
413
414
415
416
417
418 protected void setDepth(int depth) {
419 this.depth = depth;
420 }
421
422
423
424
425
426
427 protected int getDepth() {
428 return depth;
429 }
430
431
432
433
434
435
436 protected void setEncoding(String encoding) {
437 this.encoding = encoding;
438 }
439
440
441
442
443
444
445 protected String getEncoding() {
446 return encoding;
447 }
448
449
450
451
452
453
454 protected void setDocType(String docType) {
455 this.docType = docType;
456 }
457
458
459
460
461
462
463 protected String getDocType() {
464 return docType;
465 }
466
467
468
469
470 protected LinkedList<String> getElementStack() {
471 return elementStack;
472 }
473 }