View Javadoc

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   * &nbsp;&nbsp;xmlWriter.writeStartTag("&lt;root>");<br>
44   * &nbsp;&nbsp;xmlWriter.writeStartTag("&lt;next1>", false);<br>
45   * &nbsp;&nbsp;xmlWriter.writeEmptyTag("&lt;emptyTag/>", false);<br>
46   * &nbsp;&nbsp;xmlWriter.writeEndTag("&lt;/next1>");<br>
47   * &nbsp;&nbsp;xmlWriter.writeStartTag("&lt;/root>");<br>
48   * </code>
49   * <br>
50   * will write this to the underlying writer<br><br>
51   * <code>
52   * &lt;root><br>
53   * &nbsp;&nbsp;&lt;next1>&lt;emptyTag/>&lt;/next1><br>
54   * &lt;/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>&lt;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>&lt;tag>cdata&lt;/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>&lt;?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>&lt;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>&lt;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>&lt;/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>&lt;/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>&lt;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>&lt;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>&lt;element></code>
637      * @param cData the character data to write
638      * @param endTag the complete end tag, e.g. <code>&lt;/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>&lt;element></code>
653      * @param pcData the <em>parseable</em> character data to write
654      * @param endTag the complete end tag, e.g. <code>&lt;/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 }