Coverage Report - org.apache.commons.xmlio.out.XMLWriter
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLWriter
0%
0/186
0%
0/46
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  
  * &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  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>&lt;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>&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  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>&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  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>&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  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>&lt;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>&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  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>&lt;/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>&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  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>&lt;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>&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  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>&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  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  
 }