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 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 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 protected int indent = 0;
78
79 protected boolean prettyPrintMode = true;
80
81 protected boolean nlAfterEmptyTag = true;
82 protected boolean nlAfterStartTag = true;
83 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 protected boolean xmlDeclWritten = false;
95
96 private boolean needsIndent = false;
97 private boolean indentStringCacheValid = true;
98 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 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 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 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 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 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 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 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 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 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 StringBuffer buf = new StringBuffer((attrNames.length + 1) * 15);
213 buf.append('<').append(tagName);
214
215 if (attrNames.length != 0 && (attrNames.length <= attrValues.length)) {
216 for (int i = 0; i < attrNames.length; i++) {
217 String name = attrNames[i];
218 String value = attrValues[i];
219 if (value == null)
220 continue;
221 if (encodeAttrs)
222 value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar);
223 buf.append(' ').append(name).append('=').append(value);
224 }
225 }
226
227 if (isEmpty) {
228 buf.append("/>");
229 } else {
230 buf.append('>');
231 }
232 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 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 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 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 StringBuffer buf = new StringBuffer((attrPairs.length + 1) * 15);
281 buf.append('<').append(tagName);
282
283 for (int i = 0; i < attrPairs.length; i++) {
284 String name = attrPairs[i][0];
285 String value = attrPairs[i][1];
286 if (value == null)
287 continue;
288 if (encodeAttrs)
289 value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar);
290 buf.append(' ').append(name).append('=').append(value);
291 }
292
293 if (isEmpty) {
294 buf.append("/>");
295 } else {
296 buf.append('>');
297 }
298 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 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 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 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 StringBuffer buf = new StringBuffer((attributes.getLength() + 1) * 15);
343 buf.append('<').append(tagName);
344
345 for (int i = 0; i < attributes.getLength(); i++) {
346 String name = attributes.getQName(i);
347 String value = attributes.getValue(i);
348 if (encodeAttrs)
349 value = XMLEncode.xmlEncodeTextForAttribute(value, quoteChar);
350 buf.append(' ').append(name).append('=').append(value);
351 }
352
353 if (isEmpty) {
354 buf.append("/>");
355 } else {
356 buf.append('>');
357 }
358 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 String startTag = createStartTag(tagName, attrPairs);
391 String endTag = createEndTag(tagName);
392 xmlWriter.writeElementWithCData(startTag, cData, endTag);
393 }
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 String startTag = createStartTag(tagName, attrNames, attrValues);
416 String endTag = createEndTag(tagName);
417 xmlWriter.writeElementWithCData(startTag, cData, endTag);
418 }
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 super(writer);
425 }
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 this.prettyPrintMode = prettyPrintMode;
440 }
441
442 /** Gets property described in {@link #setPrettyPrintMode}. */
443 public boolean getPrettyPrintMode() {
444 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 this.tabWidth = tabWidth;
456 }
457
458 /** Gets property described in {@link #setTabWidth}. */
459 public int getTabWidth() {
460 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 this.nlAfterEmptyTag = nlAfterEmptyTag;
468 }
469
470 /** Gets property described in {@link #setNlAfterEmptyTag}. */
471 public boolean getNlAfterEmptyTag() {
472 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 this.nlAfterEndTag = nlAfterEndTag;
479 }
480
481 /** Gets property described in {@link #setNlAfterEndTag}. */
482 public boolean getNlAfterEndTag() {
483 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 this.nlAfterStartTag = nlAfterStartTag;
490 }
491
492 /** Gets property described in {@link #setNlAfterStartTag}. */
493 public boolean getNlAfterStartTag() {
494 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 xmlDeclWritten = true;
511 needsIndent = false;
512 write("<?xml version=\"1.0\"?>\n");
513 }
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 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 this.xmlDeclWritten = xmlDeclWritten;
530 }
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 needsIndent = false;
540 write(prolog);
541 writeNl();
542 }
543
544 /** Writes a single newline. */
545 public void writeNl() throws IOException {
546 needsIndent = true;
547 write('\n');
548 }
549
550 /** Writes <code>comment</code> encoded as comment. */
551 public void writeComment(String comment) throws IOException {
552 needsIndent = false;
553 write("<!-- ");
554 write(comment);
555 write(" -->");
556 }
557
558 /** Writes a processing instruction. */
559 public void writePI(String target, String data) throws IOException {
560 needsIndent = false;
561 write("<?" + target + " " + data + "?>");
562 }
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 writeTag(startTag, nl);
570 depthPlus();
571 }
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 writeStartTag(startTag, nlAfterStartTag);
579 }
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 depthMinus();
587 writeTag(endTag, nl);
588 }
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 writeEndTag(endTag, nlAfterEndTag);
596 }
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 writeTag(emptyTag, nl);
604 }
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 writeEmptyElement(emptyTag, nlAfterEmptyTag);
612 }
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 String encoded = XMLEncode.xmlEncodeText(cData);
619 writePCData(encoded);
620 }
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 needsIndent = false;
627 write(pcData);
628 }
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 writeStartTag(startTag, false);
642 writeCData(cData);
643 writeEndTag(endTag);
644 }
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 writeStartTag(startTag, false);
658 writePCData(pcData);
659 writeEndTag(endTag);
660 }
661
662 private void writeTag(String tag, boolean nl) throws IOException {
663 writeIndent();
664 needsIndent = false;
665 if (nl) {
666 if (getPrettyPrintMode()) {
667 write(tag);
668 writeNl();
669 } else {
670 // in correct mode we need to break tag before closing > resp. />
671 int length = tag.length();
672 int pos;
673 if ((pos = tag.indexOf("/>")) != -1) {
674 write(tag, 0, pos);
675 write('\n');
676 write(tag, pos, length - pos);
677 } else if ((pos = tag.indexOf(">")) != -1) {
678 write(tag, 0, pos);
679 write('\n');
680 write(tag, pos, length - pos);
681 } else {
682 write(tag);
683 write('\n');
684 }
685 }
686 } else {
687 write(tag);
688 }
689 }
690
691 private void writeIndent() throws IOException {
692 // indentation is only needed after a newline in pretty print mode
693 if (!needsIndent)
694 return;
695
696 // every indentation destroys literal write
697 if (!getPrettyPrintMode())
698 return;
699
700 // shortcut
701 if (indent == 0)
702 return;
703
704 // save some computation time when indent does not change
705 if (!indentStringCacheValid) {
706 StringBuffer buf = new StringBuffer(indent);
707 for (int i = 0; i < indent; i++) {
708 buf.append(' ');
709 }
710 indentStringCache = buf.toString();
711 indentStringCacheValid = true;
712 }
713
714 write(indentStringCache);
715 }
716
717 private void depthPlus() {
718 indent += tabWidth;
719 depth++;
720 indentStringCacheValid = false;
721 }
722
723 private void depthMinus() {
724 indent -= tabWidth;
725 if (indent < 0)
726 indent = 0;
727 depth--;
728 indentStringCacheValid = false;
729 }
730 }