Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
XMLWriter |
|
| 1.4807692307692308;1.481 |
1 | /* | |
2 | * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons-sandbox//xmlio/src/java/org/apache/commons/xmlio/out/XMLWriter.java,v 1.1 2004/10/08 11:56:20 ozeigermann Exp $ | |
3 | * $Revision: 155476 $ | |
4 | * $Date: 2005-02-26 13:31:24 +0000 (Sat, 26 Feb 2005) $ | |
5 | * | |
6 | * ==================================================================== | |
7 | * | |
8 | * Copyright 2004 The Apache Software Foundation | |
9 | * | |
10 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
11 | * you may not use this file except in compliance with the License. | |
12 | * You may obtain a copy of the License at | |
13 | * | |
14 | * http://www.apache.org/licenses/LICENSE-2.0 | |
15 | * | |
16 | * Unless required by applicable law or agreed to in writing, software | |
17 | * distributed under the License is distributed on an "AS IS" BASIS, | |
18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
19 | * See the License for the specific language governing permissions and | |
20 | * limitations under the License. | |
21 | * | |
22 | */ | |
23 | ||
24 | package org.apache.commons.xmlio.out; | |
25 | ||
26 | import java.io.*; | |
27 | ||
28 | import org.xml.sax.Attributes; | |
29 | ||
30 | /** | |
31 | * {@link FilterWriter} adding formatted and encoded XML export | |
32 | * functionality to the underlying writer. Formatting and | |
33 | * encoding is done as straight forward as possible. <br> | |
34 | * Everything you know better than this class must be done by you, e.g. you will | |
35 | * have to tell <code>XMLWriter</code> where you wish to have | |
36 | * newlines.In effect, no unexpected so called | |
37 | * <em>intelligent</em> behavior is to be feared. Another effect is high speed. | |
38 | * <br> | |
39 | * <br> | |
40 | * A simple example: Suppose your <code>XMLWriter</code> object is xmlWriter. | |
41 | * The following sequence of code <br><br> | |
42 | * <code> | |
43 | * xmlWriter.writeStartTag("<root>");<br> | |
44 | * xmlWriter.writeStartTag("<next1>", false);<br> | |
45 | * xmlWriter.writeEmptyTag("<emptyTag/>", false);<br> | |
46 | * xmlWriter.writeEndTag("</next1>");<br> | |
47 | * xmlWriter.writeStartTag("</root>");<br> | |
48 | * </code> | |
49 | * <br> | |
50 | * will write this to the underlying writer<br><br> | |
51 | * <code> | |
52 | * <root><br> | |
53 | * <next1><emptyTag/></next1><br> | |
54 | * </root><br> | |
55 | *</code> | |
56 | * <br> | |
57 | * <br> | |
58 | * <em>Caution</em>: Do not forget to call {@link #flush} at the end of your | |
59 | * exporting process as otherwise no data might be written. | |
60 | * | |
61 | */ | |
62 | public class XMLWriter extends FilterWriter { | |
63 | ||
64 | public final static boolean NEWLINE = true; | |
65 | public final static boolean NO_NEWLINE = false; | |
66 | ||
67 | 0 | protected int tabWidth = 2; |
68 | ||
69 | /** Current depth of the tree. Do not know what this is good for, but | |
70 | * who knows... | |
71 | */ | |
72 | 0 | protected int depth = 0; |
73 | ||
74 | /** Current indentation. Depth does not contain sufficient information as | |
75 | * tabWidth may change during output (should not). | |
76 | */ | |
77 | 0 | protected int indent = 0; |
78 | ||
79 | 0 | protected boolean prettyPrintMode = true; |
80 | ||
81 | 0 | protected boolean nlAfterEmptyTag = true; |
82 | 0 | protected boolean nlAfterStartTag = true; |
83 | 0 | protected boolean nlAfterEndTag = true; |
84 | ||
85 | /** Flag indicating if the XML declaration has already been writter. | |
86 | * Check this using {@link #isXMLDeclarationWritten()}. | |
87 | * It might be useful to | |
88 | * avoid writing twice or more times in different contexts writing | |
89 | * to same writer. | |
90 | * <br> | |
91 | * <em>Caution</em>: If you subclass, be sure to set this in | |
92 | * {@link #writeXMLDeclaration()}. | |
93 | */ | |
94 | 0 | protected boolean xmlDeclWritten = false; |
95 | ||
96 | 0 | private boolean needsIndent = false; |
97 | 0 | private boolean indentStringCacheValid = true; |
98 | 0 | private String indentStringCache = ""; |
99 | ||
100 | /** Convenience method for creating an end tag. | |
101 | * @param tagName name of the end tag | |
102 | */ | |
103 | public final static String createEndTag(String tagName) { | |
104 | 0 | return "</" + tagName + ">"; |
105 | } | |
106 | ||
107 | /** Convenience method for creating a start tag having no attributes. | |
108 | * @param tagName name of the start tag | |
109 | */ | |
110 | public final static String createStartTag(String tagName) { | |
111 | 0 | return "<" + tagName + ">"; |
112 | } | |
113 | ||
114 | /** Convenience method for creating an <em>empty</em> tag | |
115 | * having no attributes. E.g. <code><tagName/></code>. | |
116 | * @param tagName name of the tag | |
117 | */ | |
118 | public final static String createEmptyTag(String tagName) { | |
119 | 0 | return "<" + tagName + "/>"; |
120 | } | |
121 | ||
122 | /** Convenience method for creating a start tag. | |
123 | * @param tagName name of the start tag | |
124 | * @param attrNames names of attributes to be included into start tag | |
125 | * @param attrValues values of attributes to be included into start tag - | |
126 | * there should be just as many entries as in <code>attrNames</code>, | |
127 | * if a value is <code>null</code> corresponding attribute will not be included | |
128 | * @param isEmpty decides wheter this is start tag is for an empty element | |
129 | */ | |
130 | public final static String createStartTag( | |
131 | String tagName, | |
132 | String[] attrNames, | |
133 | String[] attrValues, | |
134 | boolean isEmpty) { | |
135 | 0 | return createStartTag(tagName, attrNames, attrValues, isEmpty, true, '"'); |
136 | } | |
137 | ||
138 | /** Convenience method for creating a <em>non empty</em> start tag. | |
139 | * @param tagName name of the start tag | |
140 | * @param attrNames names of attributes to be included into start tag | |
141 | * @param attrValues values of attributes to be included into start tag - | |
142 | * there should be just as many entries as in <code>attrNames</code>, | |
143 | * if a value is <code>null</code> corresponding attribute will not be included | |
144 | */ | |
145 | public final static String createStartTag(String tagName, String[] attrNames, String[] attrValues) { | |
146 | 0 | return createStartTag(tagName, attrNames, attrValues, false); |
147 | } | |
148 | ||
149 | /** Convenience method for creating an <em>empty</em> tag. | |
150 | * @param tagName name of the tag | |
151 | * @param attrNames names of attributes to be included into tag | |
152 | * @param attrValues values of attributes to be included into tag - | |
153 | * there should be just as many entries as in <code>attrNames</code>, | |
154 | * if a value is <code>null</code> corresponding attribute will not be included | |
155 | * @see #createEmptyTag(String) | |
156 | */ | |
157 | public final static String createEmptyTag(String tagName, String[] attrNames, String[] attrValues) { | |
158 | 0 | return createStartTag(tagName, attrNames, attrValues, true); |
159 | } | |
160 | ||
161 | /** Convenience method for creating a start tag. | |
162 | * @param tagName name of the start tag | |
163 | * @param attrName name of attribute to be included into start tag | |
164 | * @param attrValue value of attribute to be included into start tag, | |
165 | * if attrValue is <code>null</code> attribute will not be included | |
166 | * @param isEmpty decides wheter this is start tag is for an empty element | |
167 | */ | |
168 | public final static String createStartTag(String tagName, String attrName, String attrValue, boolean isEmpty) { | |
169 | 0 | return createStartTag(tagName, new String[] { attrName }, new String[] { attrValue }, isEmpty); |
170 | } | |
171 | ||
172 | /** Convenience method for creating a <em>non empty</em> start tag. | |
173 | * @param tagName name of the start tag | |
174 | * @param attrName name of attribute to be included into start tag | |
175 | * @param attrValue value of attribute to be included into start tag, | |
176 | * if attrValue is <code>null</code> attribute will not be included | |
177 | */ | |
178 | public final static String createStartTag(String tagName, String attrName, String attrValue) { | |
179 | 0 | return createStartTag(tagName, attrName, attrValue, false); |
180 | } | |
181 | ||
182 | /** Convenience method for creating an <em>empty</em> tag. | |
183 | * @param tagName name of the tag | |
184 | * @param attrName name of attribute to be included into tag | |
185 | * @param attrValue value of attribute to be included into tag, | |
186 | * if attrValue is <code>null</code> attribute will not be included | |
187 | * @see #createEmptyTag(String) | |
188 | */ | |
189 | public final static String createEmptyTag(String tagName, String attrName, String attrValue) { | |
190 | 0 | return createStartTag(tagName, attrName, attrValue, true); |
191 | } | |
192 | ||
193 | /** Convenience method for creating a start tag. | |
194 | * @param tagName name of the start tag | |
195 | * @param attrNames names of attributes to be included into start tag | |
196 | * @param attrValues values of attributes to be included into start tag - | |
197 | * there should be just as many entries as in <code>attrNames</code>, | |
198 | * if a value is <code>null</code> corresponding attribute will not be included | |
199 | * @param isEmpty decides wheter this is start tag is for an empty element | |
200 | * @param encodeAttrs set this to have your attribute values encoded for XML | |
201 | * @param quoteChar if you choose encoding this is the char that quotes | |
202 | * your attributes | |
203 | */ | |
204 | public final static String createStartTag( | |
205 | String tagName, | |
206 | String[] attrNames, | |
207 | String[] attrValues, | |
208 | boolean isEmpty, | |
209 | boolean encodeAttrs, | |
210 | char quoteChar) { | |
211 | // estimate buffer size | |
212 | 0 | StringBuffer buf = new StringBuffer((attrNames.length + 1) * 15); |
213 | 0 | buf.append('<').append(tagName); |
214 | ||
215 | 0 | if (attrNames.length != 0 && (attrNames.length <= attrValues.length)) { |
216 | 0 | for (int i = 0; i < attrNames.length; i++) { |
217 | 0 | String name = attrNames[i]; |
218 | 0 | String value = attrValues[i]; |
219 | 0 | if (value == null) |
220 | 0 | continue; |
221 | 0 | if (encodeAttrs) |
222 | 0 | value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar); |
223 | 0 | buf.append(' ').append(name).append('=').append(value); |
224 | } | |
225 | } | |
226 | ||
227 | 0 | if (isEmpty) { |
228 | 0 | buf.append("/>"); |
229 | } else { | |
230 | 0 | buf.append('>'); |
231 | } | |
232 | 0 | return buf.toString(); |
233 | } | |
234 | ||
235 | /** Convenience method for creating a start tag. | |
236 | * @param tagName name of the start tag | |
237 | * @param attrPairs name/value pairs of attributes to be included into start tag - | |
238 | * if a value is <code>null</code> corresponding attribute will not be included | |
239 | * @param isEmpty decides wheter this is start tag is for an empty element | |
240 | */ | |
241 | public final static String createStartTag(String tagName, String[][] attrPairs, boolean isEmpty) { | |
242 | 0 | return createStartTag(tagName, attrPairs, isEmpty, true, '"'); |
243 | } | |
244 | ||
245 | /** Convenience method for creating a <em>non empty</em> start tag. | |
246 | * @param tagName name of the start tag | |
247 | * @param attrPairs name/value pairs of attributes to be included into start tag - | |
248 | * if a value is <code>null</code> corresponding attribute will not be included | |
249 | */ | |
250 | public final static String createStartTag(String tagName, String[][] attrPairs) { | |
251 | 0 | return createStartTag(tagName, attrPairs, false); |
252 | } | |
253 | ||
254 | /** Convenience method for creating an <em>empty</em> tag. | |
255 | * @param tagName name of the tag | |
256 | * @param attrPairs name/value pairs of attributes to be included into tag - | |
257 | * if a value is <code>null</code> corresponding attribute will not be included | |
258 | * @see #createEmptyTag(String) | |
259 | */ | |
260 | public final static String createEmptyTag(String tagName, String[][] attrPairs) { | |
261 | 0 | return createStartTag(tagName, attrPairs, true); |
262 | } | |
263 | ||
264 | /** Convenience method for creating a start tag. | |
265 | * @param tagName name of the start tag | |
266 | * @param attrPairs name/value pairs of attributes to be included into start tag - | |
267 | * if a value is <code>null</code> corresponding attribute will not be included | |
268 | * @param isEmpty decides wheter this is start tag is for an empty element | |
269 | * @param encodeAttrs set this to have your attribute values encoded for XML | |
270 | * @param quoteChar if you choose encoding this is the char that quotes | |
271 | * your attributes | |
272 | */ | |
273 | public final static String createStartTag( | |
274 | String tagName, | |
275 | String[][] attrPairs, | |
276 | boolean isEmpty, | |
277 | boolean encodeAttrs, | |
278 | char quoteChar) { | |
279 | // estimate buffer size | |
280 | 0 | StringBuffer buf = new StringBuffer((attrPairs.length + 1) * 15); |
281 | 0 | buf.append('<').append(tagName); |
282 | ||
283 | 0 | for (int i = 0; i < attrPairs.length; i++) { |
284 | 0 | String name = attrPairs[i][0]; |
285 | 0 | String value = attrPairs[i][1]; |
286 | 0 | if (value == null) |
287 | 0 | continue; |
288 | 0 | if (encodeAttrs) |
289 | 0 | value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar); |
290 | 0 | buf.append(' ').append(name).append('=').append(value); |
291 | } | |
292 | ||
293 | 0 | if (isEmpty) { |
294 | 0 | buf.append("/>"); |
295 | } else { | |
296 | 0 | buf.append('>'); |
297 | } | |
298 | 0 | return buf.toString(); |
299 | } | |
300 | ||
301 | /** Convenience method for creating an <em>empty</em> tag. | |
302 | * @param tagName name of the tag | |
303 | * @param attributes SAX attributes to be included into start tag | |
304 | * @see #createEmptyTag(String) | |
305 | */ | |
306 | public final static String createEmptyTag(String tagName, Attributes attributes) { | |
307 | 0 | return createStartTag(tagName, attributes, true); |
308 | } | |
309 | ||
310 | /** Convenience method for creating a start tag. | |
311 | * @param tagName name of the start tag | |
312 | * @param attributes SAX attributes to be included into start tag | |
313 | */ | |
314 | public final static String createStartTag(String tagName, Attributes attributes) { | |
315 | 0 | return createStartTag(tagName, attributes, false); |
316 | } | |
317 | ||
318 | /** Convenience method for creating a start tag. | |
319 | * @param tagName name of the start tag | |
320 | * @param attributes SAX attributes to be included into start tag | |
321 | * @param isEmpty decides wheter this is start tag is for an empty element | |
322 | */ | |
323 | public final static String createStartTag(String tagName, Attributes attributes, boolean isEmpty) { | |
324 | 0 | return createStartTag(tagName, attributes, isEmpty, true, '"'); |
325 | } | |
326 | ||
327 | /** Convenience method for creating a start tag. | |
328 | * @param tagName name of the start tag | |
329 | * @param attributes SAX attributes to be included into start tag | |
330 | * @param isEmpty decides wheter this is start tag is for an empty element | |
331 | * @param encodeAttrs set this to have your attribute values encoded for XML | |
332 | * @param quoteChar if you choose encoding this is the char that quotes | |
333 | * your attributes | |
334 | */ | |
335 | public final static String createStartTag( | |
336 | String tagName, | |
337 | Attributes attributes, | |
338 | boolean isEmpty, | |
339 | boolean encodeAttrs, | |
340 | char quoteChar) { | |
341 | // estimate buffer size | |
342 | 0 | StringBuffer buf = new StringBuffer((attributes.getLength() + 1) * 15); |
343 | 0 | buf.append('<').append(tagName); |
344 | ||
345 | 0 | for (int i = 0; i < attributes.getLength(); i++) { |
346 | 0 | String name = attributes.getQName(i); |
347 | 0 | String value = attributes.getValue(i); |
348 | 0 | if (encodeAttrs) |
349 | 0 | value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar); |
350 | 0 | buf.append(' ').append(name).append('=').append(value); |
351 | } | |
352 | ||
353 | 0 | if (isEmpty) { |
354 | 0 | buf.append("/>"); |
355 | } else { | |
356 | 0 | buf.append('>'); |
357 | } | |
358 | 0 | return buf.toString(); |
359 | } | |
360 | ||
361 | /** Convenience method for creating <em>and writing</em> a whole element. | |
362 | * Added to normal non-static write methods purely for my own laziness.<br> | |
363 | * It is non-static as it differs from all other write methods as it | |
364 | * combines generating and writing. This is normally avoided to keep every | |
365 | * everything simple, clear and fast.<br> | |
366 | * <br> | |
367 | * You can write<br> | |
368 | * <code>XMLOutputStreamWriter.generateAndWriteElementWithCData(writer, "tag", "cdata"); | |
369 | * </code><br> | |
370 | * <br> | |
371 | * to generate<br> | |
372 | * <code><tag>cdata</tag> | |
373 | * </code><br> | |
374 | * | |
375 | * @param xmlWriter writer to write generated stuff to | |
376 | * @param tagName name of the element | |
377 | * @param attrPairs name/value pairs of attributes to be included into start tag - | |
378 | * if a value is <code>null</code> corresponding attribute will not be included | |
379 | * @param cData the character data of the element | |
380 | * @see #writeElementWithCData(String, String, String) | |
381 | * @see #createStartTag(String, String[][]) | |
382 | * @see #createEndTag(String) | |
383 | */ | |
384 | public final static void generateAndWriteElementWithCData( | |
385 | XMLWriter xmlWriter, | |
386 | String tagName, | |
387 | String[][] attrPairs, | |
388 | String cData) | |
389 | throws IOException { | |
390 | 0 | String startTag = createStartTag(tagName, attrPairs); |
391 | 0 | String endTag = createEndTag(tagName); |
392 | 0 | xmlWriter.writeElementWithCData(startTag, cData, endTag); |
393 | 0 | } |
394 | ||
395 | /** Convenience method for creating <em>and writing</em> a whole element. | |
396 | * @param xmlWriter writer to write generated stuff to | |
397 | * @param tagName name of the element | |
398 | * @param attrNames names of attributes to be included into start tag | |
399 | * @param attrValues values of attributes to be included into start tag - | |
400 | * there should be just as many entries as in <code>attrNames</code>, | |
401 | * if a value is <code>null</code> corresponding attribute will not be included | |
402 | * @param cData the character data of the element | |
403 | * @see #generateAndWriteElementWithCData(XMLWriter, String, String[][], String) | |
404 | * @see #writeElementWithCData(String, String, String) | |
405 | * @see #createStartTag(String, String[], String[]) | |
406 | * @see #createEndTag(String) | |
407 | */ | |
408 | public final static void generateAndWriteElementWithCData( | |
409 | XMLWriter xmlWriter, | |
410 | String tagName, | |
411 | String[] attrNames, | |
412 | String[] attrValues, | |
413 | String cData) | |
414 | throws IOException { | |
415 | 0 | String startTag = createStartTag(tagName, attrNames, attrValues); |
416 | 0 | String endTag = createEndTag(tagName); |
417 | 0 | xmlWriter.writeElementWithCData(startTag, cData, endTag); |
418 | 0 | } |
419 | ||
420 | /** Creates a new filter writer for XML export. | |
421 | * @param writer the underlying writer the formatted XML is exported to | |
422 | */ | |
423 | public XMLWriter(Writer writer) { | |
424 | 0 | super(writer); |
425 | 0 | } |
426 | ||
427 | /** Switches on/off pretty print mode. | |
428 | * <br> | |
429 | * Having it switched on (which is the default) makes output | |
430 | * pretty as newlines after tags and indentataion is done. Unfortunately, | |
431 | * if your application is sensible to whitespace in CDATA this might lead | |
432 | * to unwanted additional spaces and newlines. | |
433 | * <br> | |
434 | * If it is switched off the output is guaranteed to be correct, but looks | |
435 | * pretty funny. After before markup close (> or />) a newline is inserted | |
436 | * as otherwise you may get extremely long output lines. | |
437 | */ | |
438 | public void setPrettyPrintMode(boolean prettyPrintMode) { | |
439 | 0 | this.prettyPrintMode = prettyPrintMode; |
440 | 0 | } |
441 | ||
442 | /** Gets property described in {@link #setPrettyPrintMode}. */ | |
443 | public boolean getPrettyPrintMode() { | |
444 | 0 | return prettyPrintMode; |
445 | } | |
446 | ||
447 | /** Sets the amount of spaces to increase indentation with element level. | |
448 | * <br> | |
449 | * This only takes effect when {@link #setPrettyPrintMode} is set to true. | |
450 | * <br> | |
451 | * <em>Caution</em>: You should better avoid to change this property while | |
452 | * exporting as this may result in unexpected output. | |
453 | */ | |
454 | public void setTabWidth(int tabWidth) { | |
455 | 0 | this.tabWidth = tabWidth; |
456 | 0 | } |
457 | ||
458 | /** Gets property described in {@link #setTabWidth}. */ | |
459 | public int getTabWidth() { | |
460 | 0 | return tabWidth; |
461 | } | |
462 | ||
463 | /** Sets if a newline is inserted after an empty start element | |
464 | * by default. | |
465 | */ | |
466 | public void setNlAfterEmptyTag(boolean nlAfterEmptyTag) { | |
467 | 0 | this.nlAfterEmptyTag = nlAfterEmptyTag; |
468 | 0 | } |
469 | ||
470 | /** Gets property described in {@link #setNlAfterEmptyTag}. */ | |
471 | public boolean getNlAfterEmptyTag() { | |
472 | 0 | return nlAfterEmptyTag; |
473 | } | |
474 | ||
475 | /** Sets if a newline is inserted after an end tag | |
476 | * by default. */ | |
477 | public void setNlAfterEndTag(boolean nlAfterEndTag) { | |
478 | 0 | this.nlAfterEndTag = nlAfterEndTag; |
479 | 0 | } |
480 | ||
481 | /** Gets property described in {@link #setNlAfterEndTag}. */ | |
482 | public boolean getNlAfterEndTag() { | |
483 | 0 | return nlAfterEndTag; |
484 | } | |
485 | ||
486 | /** Sets if a newline is inserted after a non empty start tag | |
487 | * by default. */ | |
488 | public void setNlAfterStartTag(boolean nlAfterStartTag) { | |
489 | 0 | this.nlAfterStartTag = nlAfterStartTag; |
490 | 0 | } |
491 | ||
492 | /** Gets property described in {@link #setNlAfterStartTag}. */ | |
493 | public boolean getNlAfterStartTag() { | |
494 | 0 | return nlAfterStartTag; |
495 | } | |
496 | ||
497 | /** Writes XML declaration. | |
498 | * XML declaration will be written | |
499 | * using version 1.0 and no encoding defaulting | |
500 | * to standard encoding (supports UTF-8 and UTF-16):<br> | |
501 | * <code><?xml version="1.0"?></code> | |
502 | * <br> | |
503 | * If you want to have a different encoding or the standalone declaration | |
504 | * use {@link #writeProlog(String)}.<br> | |
505 | * This sets {@link #setXMLDeclarationWritten xmlDeclWritten} to | |
506 | * <code>true</code>. | |
507 | * | |
508 | */ | |
509 | public void writeXMLDeclaration() throws IOException { | |
510 | 0 | xmlDeclWritten = true; |
511 | 0 | needsIndent = false; |
512 | 0 | write("<?xml version=\"1.0\"?>\n"); |
513 | 0 | } |
514 | ||
515 | /** Indicates whether the XML declaration has been written, yet. | |
516 | * As it may only be written once, you can check this when writing | |
517 | * in different contexts to same writer. | |
518 | */ | |
519 | public boolean isXMLDeclarationWritten() { | |
520 | 0 | return xmlDeclWritten; |
521 | } | |
522 | ||
523 | /** Manually sets or resets whether XML declaration has been written. | |
524 | * This is done implicly by {@link #writeXMLDeclaration}, but to give you | |
525 | * the full freedom, this can be done here as well. | |
526 | * Use {@link #isXMLDeclarationWritten} to check it. | |
527 | */ | |
528 | public void setXMLDeclarationWritten(boolean xmlDeclWritten) { | |
529 | 0 | this.xmlDeclWritten = xmlDeclWritten; |
530 | 0 | } |
531 | ||
532 | /** Writes prolog data like doctype delcaration and | |
533 | * DTD parts followed by a newline. | |
534 | * <br> | |
535 | * Do not misuse this to write plain text, but rather - if you really | |
536 | * have to - use the standard {@link #write} methods. | |
537 | */ | |
538 | public void writeProlog(String prolog) throws IOException { | |
539 | 0 | needsIndent = false; |
540 | 0 | write(prolog); |
541 | 0 | writeNl(); |
542 | 0 | } |
543 | ||
544 | /** Writes a single newline. */ | |
545 | public void writeNl() throws IOException { | |
546 | 0 | needsIndent = true; |
547 | 0 | write('\n'); |
548 | 0 | } |
549 | ||
550 | /** Writes <code>comment</code> encoded as comment. */ | |
551 | public void writeComment(String comment) throws IOException { | |
552 | 0 | needsIndent = false; |
553 | 0 | write("<!-- "); |
554 | 0 | write(comment); |
555 | 0 | write(" -->"); |
556 | 0 | } |
557 | ||
558 | /** Writes a processing instruction. */ | |
559 | public void writePI(String target, String data) throws IOException { | |
560 | 0 | needsIndent = false; |
561 | 0 | write("<?" + target + " " + data + "?>"); |
562 | 0 | } |
563 | ||
564 | /** Writes a start tag. | |
565 | * @param startTag the complete start tag, e.g. <code><start></code> | |
566 | * @param nl decides whether there should be a newline after the tag | |
567 | */ | |
568 | public void writeStartTag(String startTag, boolean nl) throws IOException { | |
569 | 0 | writeTag(startTag, nl); |
570 | 0 | depthPlus(); |
571 | 0 | } |
572 | ||
573 | /** Writes a start tag. | |
574 | * @param startTag the complete start tag, e.g. <code><start></code> | |
575 | * @see #setNlAfterStartTag | |
576 | */ | |
577 | public void writeStartTag(String startTag) throws IOException { | |
578 | 0 | writeStartTag(startTag, nlAfterStartTag); |
579 | 0 | } |
580 | ||
581 | /** Writes an end tag. | |
582 | * @param endTag the complete end tag, e.g. <code></end></code> | |
583 | * @param nl decides whether there should be a newline after the tag | |
584 | */ | |
585 | public void writeEndTag(String endTag, boolean nl) throws IOException { | |
586 | 0 | depthMinus(); |
587 | 0 | writeTag(endTag, nl); |
588 | 0 | } |
589 | ||
590 | /** Writes an end tag. | |
591 | * @param endTag the complete end tag, e.g. <code></end></code> | |
592 | * @see #setNlAfterEndTag | |
593 | */ | |
594 | public void writeEndTag(String endTag) throws IOException { | |
595 | 0 | writeEndTag(endTag, nlAfterEndTag); |
596 | 0 | } |
597 | ||
598 | /** Writes an empty element. | |
599 | * @param emptyTag the complete tag for an empty element, e.g. <code><empty/></code> | |
600 | * @param nl decides whether there should be a newline after the tag | |
601 | */ | |
602 | public void writeEmptyElement(String emptyTag, boolean nl) throws IOException { | |
603 | 0 | writeTag(emptyTag, nl); |
604 | 0 | } |
605 | ||
606 | /** Writes an empty element. | |
607 | * @param emptyTag the complete tag for an empty element, e.g. <code><start/></code> | |
608 | * @see #setNlAfterEmptyTag | |
609 | */ | |
610 | public void writeEmptyElement(String emptyTag) throws IOException { | |
611 | 0 | writeEmptyElement(emptyTag, nlAfterEmptyTag); |
612 | 0 | } |
613 | ||
614 | /** Writes character data with encoding. | |
615 | * @param cData the character data to write | |
616 | */ | |
617 | public void writeCData(String cData) throws IOException { | |
618 | 0 | String encoded = XMLEncode.xmlEncodeText(cData); |
619 | 0 | writePCData(encoded); |
620 | 0 | } |
621 | ||
622 | /** Writes character data <em>without</em> encoding. | |
623 | * @param pcData the <em>parseable</em> character data to write | |
624 | */ | |
625 | public void writePCData(String pcData) throws IOException { | |
626 | 0 | needsIndent = false; |
627 | 0 | write(pcData); |
628 | 0 | } |
629 | ||
630 | /** Writes a full element consisting of a start tag, character data and | |
631 | * an end tag. There will be no newline after start tag, so character data | |
632 | * is literally preserved. | |
633 | * <br> | |
634 | * The character data will be encoded. | |
635 | * | |
636 | * @param startTag the complete start tag, e.g. <code><element></code> | |
637 | * @param cData the character data to write | |
638 | * @param endTag the complete end tag, e.g. <code></element></code> | |
639 | */ | |
640 | public void writeElementWithCData(String startTag, String cData, String endTag) throws IOException { | |
641 | 0 | writeStartTag(startTag, false); |
642 | 0 | writeCData(cData); |
643 | 0 | writeEndTag(endTag); |
644 | 0 | } |
645 | ||
646 | /** Writes a full element consisting of a start tag, character data and | |
647 | * an end tag. There will be no newline after start tag, so character data | |
648 | * is literally preserved. | |
649 | * <br> | |
650 | * The character data will <em>not</em> be encoded. | |
651 | * | |
652 | * @param startTag the complete start tag, e.g. <code><element></code> | |
653 | * @param pcData the <em>parseable</em> character data to write | |
654 | * @param endTag the complete end tag, e.g. <code></element></code> | |
655 | */ | |
656 | public void writeElementWithPCData(String startTag, String pcData, String endTag) throws IOException { | |
657 | 0 | writeStartTag(startTag, false); |
658 | 0 | writePCData(pcData); |
659 | 0 | writeEndTag(endTag); |
660 | 0 | } |
661 | ||
662 | private void writeTag(String tag, boolean nl) throws IOException { | |
663 | 0 | writeIndent(); |
664 | 0 | needsIndent = false; |
665 | 0 | if (nl) { |
666 | 0 | if (getPrettyPrintMode()) { |
667 | 0 | write(tag); |
668 | 0 | writeNl(); |
669 | } else { | |
670 | // in correct mode we need to break tag before closing > resp. /> | |
671 | 0 | int length = tag.length(); |
672 | int pos; | |
673 | 0 | if ((pos = tag.indexOf("/>")) != -1) { |
674 | 0 | write(tag, 0, pos); |
675 | 0 | write('\n'); |
676 | 0 | write(tag, pos, length - pos); |
677 | 0 | } else if ((pos = tag.indexOf(">")) != -1) { |
678 | 0 | write(tag, 0, pos); |
679 | 0 | write('\n'); |
680 | 0 | write(tag, pos, length - pos); |
681 | } else { | |
682 | 0 | write(tag); |
683 | 0 | write('\n'); |
684 | } | |
685 | 0 | } |
686 | } else { | |
687 | 0 | write(tag); |
688 | } | |
689 | 0 | } |
690 | ||
691 | private void writeIndent() throws IOException { | |
692 | // indentation is only needed after a newline in pretty print mode | |
693 | 0 | if (!needsIndent) |
694 | 0 | return; |
695 | ||
696 | // every indentation destroys literal write | |
697 | 0 | if (!getPrettyPrintMode()) |
698 | 0 | return; |
699 | ||
700 | // shortcut | |
701 | 0 | if (indent == 0) |
702 | 0 | return; |
703 | ||
704 | // save some computation time when indent does not change | |
705 | 0 | if (!indentStringCacheValid) { |
706 | 0 | StringBuffer buf = new StringBuffer(indent); |
707 | 0 | for (int i = 0; i < indent; i++) { |
708 | 0 | buf.append(' '); |
709 | } | |
710 | 0 | indentStringCache = buf.toString(); |
711 | 0 | indentStringCacheValid = true; |
712 | } | |
713 | ||
714 | 0 | write(indentStringCache); |
715 | 0 | } |
716 | ||
717 | private void depthPlus() { | |
718 | 0 | indent += tabWidth; |
719 | 0 | depth++; |
720 | 0 | indentStringCacheValid = false; |
721 | 0 | } |
722 | ||
723 | private void depthMinus() { | |
724 | 0 | indent -= tabWidth; |
725 | 0 | if (indent < 0) |
726 | 0 | indent = 0; |
727 | 0 | depth--; |
728 | 0 | indentStringCacheValid = false; |
729 | 0 | } |
730 | } |