Home | History | Annotate | Download | only in tagsoup
      1 // XMLWriter.java - serialize an XML document.
      2 // Written by David Megginson, david (at) megginson.com
      3 // and placed by him into the public domain.
      4 // Extensively modified by John Cowan for TagSoup.
      5 // TagSoup is licensed under the Apache License,
      6 // Version 2.0.  You may obtain a copy of this license at
      7 // http://www.apache.org/licenses/LICENSE-2.0 .  You may also have
      8 // additional legal rights not granted by this license.
      9 //
     10 // TagSoup is distributed in the hope that it will be useful, but
     11 // unless required by applicable law or agreed to in writing, TagSoup
     12 // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
     13 // OF ANY KIND, either express or implied; not even the implied warranty
     14 // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     15 
     16 package org.ccil.cowan.tagsoup;
     17 
     18 import java.io.IOException;
     19 import java.io.OutputStreamWriter;
     20 import java.io.Writer;
     21 import java.util.Enumeration;
     22 import java.util.Hashtable;
     23 import java.util.Properties;
     24 
     25 import org.xml.sax.Attributes;
     26 import org.xml.sax.SAXException;
     27 import org.xml.sax.XMLReader;
     28 import org.xml.sax.helpers.AttributesImpl;
     29 import org.xml.sax.helpers.NamespaceSupport;
     30 import org.xml.sax.helpers.XMLFilterImpl;
     31 import org.xml.sax.ext.LexicalHandler;
     32 
     33 
     34 /**
     35  * Filter to write an XML document from a SAX event stream.
     36  *
     37  * <p>This class can be used by itself or as part of a SAX event
     38  * stream: it takes as input a series of SAX2 ContentHandler
     39  * events and uses the information in those events to write
     40  * an XML document.  Since this class is a filter, it can also
     41  * pass the events on down a filter chain for further processing
     42  * (you can use the XMLWriter to take a snapshot of the current
     43  * state at any point in a filter chain), and it can be
     44  * used directly as a ContentHandler for a SAX2 XMLReader.</p>
     45  *
     46  * <p>The client creates a document by invoking the methods for
     47  * standard SAX2 events, always beginning with the
     48  * {@link #startDocument startDocument} method and ending with
     49  * the {@link #endDocument endDocument} method.  There are convenience
     50  * methods provided so that clients to not have to create empty
     51  * attribute lists or provide empty strings as parameters; for
     52  * example, the method invocation</p>
     53  *
     54  * <pre>
     55  * w.startElement("foo");
     56  * </pre>
     57  *
     58  * <p>is equivalent to the regular SAX2 ContentHandler method</p>
     59  *
     60  * <pre>
     61  * w.startElement("", "foo", "", new AttributesImpl());
     62  * </pre>
     63  *
     64  * <p>Except that it is more efficient because it does not allocate
     65  * a new empty attribute list each time.  The following code will send
     66  * a simple XML document to standard output:</p>
     67  *
     68  * <pre>
     69  * XMLWriter w = new XMLWriter();
     70  *
     71  * w.startDocument();
     72  * w.startElement("greeting");
     73  * w.characters("Hello, world!");
     74  * w.endElement("greeting");
     75  * w.endDocument();
     76  * </pre>
     77  *
     78  * <p>The resulting document will look like this:</p>
     79  *
     80  * <pre>
     81  * &lt;?xml version="1.0" standalone="yes"?>
     82  *
     83  * &lt;greeting>Hello, world!&lt;/greeting>
     84  * </pre>
     85  *
     86  * <p>In fact, there is an even simpler convenience method,
     87  * <var>dataElement</var>, designed for writing elements that
     88  * contain only character data, so the code to generate the
     89  * document could be shortened to</p>
     90  *
     91  * <pre>
     92  * XMLWriter w = new XMLWriter();
     93  *
     94  * w.startDocument();
     95  * w.dataElement("greeting", "Hello, world!");
     96  * w.endDocument();
     97  * </pre>
     98  *
     99  * <h2>Whitespace</h2>
    100  *
    101  * <p>According to the XML Recommendation, <em>all</em> whitespace
    102  * in an XML document is potentially significant to an application,
    103  * so this class never adds newlines or indentation.  If you
    104  * insert three elements in a row, as in</p>
    105  *
    106  * <pre>
    107  * w.dataElement("item", "1");
    108  * w.dataElement("item", "2");
    109  * w.dataElement("item", "3");
    110  * </pre>
    111  *
    112  * <p>you will end up with</p>
    113  *
    114  * <pre>
    115  * &lt;item>1&lt;/item>&lt;item>3&lt;/item>&lt;item>3&lt;/item>
    116  * </pre>
    117  *
    118  * <p>You need to invoke one of the <var>characters</var> methods
    119  * explicitly to add newlines or indentation.  Alternatively, you
    120  * can use {@link com.megginson.sax.DataWriter DataWriter}, which
    121  * is derived from this class -- it is optimized for writing
    122  * purely data-oriented (or field-oriented) XML, and does automatic
    123  * linebreaks and indentation (but does not support mixed content
    124  * properly).</p>
    125  *
    126  *
    127  * <h2>Namespace Support</h2>
    128  *
    129  * <p>The writer contains extensive support for XML Namespaces, so that
    130  * a client application does not have to keep track of prefixes and
    131  * supply <var>xmlns</var> attributes.  By default, the XML writer will
    132  * generate Namespace declarations in the form _NS1, _NS2, etc., wherever
    133  * they are needed, as in the following example:</p>
    134  *
    135  * <pre>
    136  * w.startDocument();
    137  * w.emptyElement("http://www.foo.com/ns/", "foo");
    138  * w.endDocument();
    139  * </pre>
    140  *
    141  * <p>The resulting document will look like this:</p>
    142  *
    143  * <pre>
    144  * &lt;?xml version="1.0" standalone="yes"?>
    145  *
    146  * &lt;_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
    147  * </pre>
    148  *
    149  * <p>In many cases, document authors will prefer to choose their
    150  * own prefixes rather than using the (ugly) default names.  The
    151  * XML writer allows two methods for selecting prefixes:</p>
    152  *
    153  * <ol>
    154  * <li>the qualified name</li>
    155  * <li>the {@link #setPrefix setPrefix} method.</li>
    156  * </ol>
    157  *
    158  * <p>Whenever the XML writer finds a new Namespace URI, it checks
    159  * to see if a qualified (prefixed) name is also available; if so
    160  * it attempts to use the name's prefix (as long as the prefix is
    161  * not already in use for another Namespace URI).</p>
    162  *
    163  * <p>Before writing a document, the client can also pre-map a prefix
    164  * to a Namespace URI with the setPrefix method:</p>
    165  *
    166  * <pre>
    167  * w.setPrefix("http://www.foo.com/ns/", "foo");
    168  * w.startDocument();
    169  * w.emptyElement("http://www.foo.com/ns/", "foo");
    170  * w.endDocument();
    171  * </pre>
    172  *
    173  * <p>The resulting document will look like this:</p>
    174  *
    175  * <pre>
    176  * &lt;?xml version="1.0" standalone="yes"?>
    177  *
    178  * &lt;foo:foo xmlns:foo="http://www.foo.com/ns/"/>
    179  * </pre>
    180  *
    181  * <p>The default Namespace simply uses an empty string as the prefix:</p>
    182  *
    183  * <pre>
    184  * w.setPrefix("http://www.foo.com/ns/", "");
    185  * w.startDocument();
    186  * w.emptyElement("http://www.foo.com/ns/", "foo");
    187  * w.endDocument();
    188  * </pre>
    189  *
    190  * <p>The resulting document will look like this:</p>
    191  *
    192  * <pre>
    193  * &lt;?xml version="1.0" standalone="yes"?>
    194  *
    195  * &lt;foo xmlns="http://www.foo.com/ns/"/>
    196  * </pre>
    197  *
    198  * <p>By default, the XML writer will not declare a Namespace until
    199  * it is actually used.  Sometimes, this approach will create
    200  * a large number of Namespace declarations, as in the following
    201  * example:</p>
    202  *
    203  * <pre>
    204  * &lt;xml version="1.0" standalone="yes"?>
    205  *
    206  * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    207  *  &lt;rdf:Description about="http://www.foo.com/ids/books/12345">
    208  *   &lt;dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night&lt;/dc:title>
    209  *   &lt;dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith&lt;/dc:title>
    210  *   &lt;dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09&lt;/dc:title>
    211  *  &lt;/rdf:Description>
    212  * &lt;/rdf:RDF>
    213  * </pre>
    214  *
    215  * <p>The "rdf" prefix is declared only once, because the RDF Namespace
    216  * is used by the root element and can be inherited by all of its
    217  * descendants; the "dc" prefix, on the other hand, is declared three
    218  * times, because no higher element uses the Namespace.  To solve this
    219  * problem, you can instruct the XML writer to predeclare Namespaces
    220  * on the root element even if they are not used there:</p>
    221  *
    222  * <pre>
    223  * w.forceNSDecl("http://www.purl.org/dc/");
    224  * </pre>
    225  *
    226  * <p>Now, the "dc" prefix will be declared on the root element even
    227  * though it's not needed there, and can be inherited by its
    228  * descendants:</p>
    229  *
    230  * <pre>
    231  * &lt;xml version="1.0" standalone="yes"?>
    232  *
    233  * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    234  *             xmlns:dc="http://www.purl.org/dc/">
    235  *  &lt;rdf:Description about="http://www.foo.com/ids/books/12345">
    236  *   &lt;dc:title>A Dark Night&lt;/dc:title>
    237  *   &lt;dc:creator>Jane Smith&lt;/dc:title>
    238  *   &lt;dc:date>2000-09-09&lt;/dc:title>
    239  *  &lt;/rdf:Description>
    240  * &lt;/rdf:RDF>
    241  * </pre>
    242  *
    243  * <p>This approach is also useful for declaring Namespace prefixes
    244  * that be used by qualified names appearing in attribute values or
    245  * character data.</p>
    246  *
    247  * @author David Megginson, david (at) megginson.com
    248  * @version 0.2
    249  * @see org.xml.sax.XMLFilter
    250  * @see org.xml.sax.ContentHandler
    251  */
    252 public class XMLWriter extends XMLFilterImpl implements LexicalHandler
    253 {
    254 
    255 
    256     ////////////////////////////////////////////////////////////////////
    258     // Constructors.
    259     ////////////////////////////////////////////////////////////////////
    260 
    261 
    262     /**
    263      * Create a new XML writer.
    264      *
    265      * <p>Write to standard output.</p>
    266      */
    267     public XMLWriter ()
    268     {
    269         init(null);
    270     }
    271 
    272 
    273     /**
    274      * Create a new XML writer.
    275      *
    276      * <p>Write to the writer provided.</p>
    277      *
    278      * @param writer The output destination, or null to use standard
    279      *        output.
    280      */
    281     public XMLWriter (Writer writer)
    282     {
    283         init(writer);
    284     }
    285 
    286 
    287     /**
    288      * Create a new XML writer.
    289      *
    290      * <p>Use the specified XML reader as the parent.</p>
    291      *
    292      * @param xmlreader The parent in the filter chain, or null
    293      *        for no parent.
    294      */
    295     public XMLWriter (XMLReader xmlreader)
    296     {
    297         super(xmlreader);
    298         init(null);
    299     }
    300 
    301 
    302     /**
    303      * Create a new XML writer.
    304      *
    305      * <p>Use the specified XML reader as the parent, and write
    306      * to the specified writer.</p>
    307      *
    308      * @param xmlreader The parent in the filter chain, or null
    309      *        for no parent.
    310      * @param writer The output destination, or null to use standard
    311      *        output.
    312      */
    313     public XMLWriter (XMLReader xmlreader, Writer writer)
    314     {
    315         super(xmlreader);
    316         init(writer);
    317     }
    318 
    319 
    320     /**
    321      * Internal initialization method.
    322      *
    323      * <p>All of the public constructors invoke this method.
    324      *
    325      * @param writer The output destination, or null to use
    326      *        standard output.
    327      */
    328     private void init (Writer writer)
    329     {
    330         setOutput(writer);
    331         nsSupport = new NamespaceSupport();
    332         prefixTable = new Hashtable();
    333         forcedDeclTable = new Hashtable();
    334         doneDeclTable = new Hashtable();
    335         outputProperties = new Properties();
    336     }
    337 
    338 
    339 
    340     ////////////////////////////////////////////////////////////////////
    342     // Public methods.
    343     ////////////////////////////////////////////////////////////////////
    344 
    345 
    346     /**
    347      * Reset the writer.
    348      *
    349      * <p>This method is especially useful if the writer throws an
    350      * exception before it is finished, and you want to reuse the
    351      * writer for a new document.  It is usually a good idea to
    352      * invoke {@link #flush flush} before resetting the writer,
    353      * to make sure that no output is lost.</p>
    354      *
    355      * <p>This method is invoked automatically by the
    356      * {@link #startDocument startDocument} method before writing
    357      * a new document.</p>
    358      *
    359      * <p><strong>Note:</strong> this method will <em>not</em>
    360      * clear the prefix or URI information in the writer or
    361      * the selected output writer.</p>
    362      *
    363      * @see #flush
    364      */
    365     public void reset ()
    366     {
    367         elementLevel = 0;
    368         prefixCounter = 0;
    369         nsSupport.reset();
    370     }
    371 
    372 
    373     /**
    374      * Flush the output.
    375      *
    376      * <p>This method flushes the output stream.  It is especially useful
    377      * when you need to make certain that the entire document has
    378      * been written to output but do not want to close the output
    379      * stream.</p>
    380      *
    381      * <p>This method is invoked automatically by the
    382      * {@link #endDocument endDocument} method after writing a
    383      * document.</p>
    384      *
    385      * @see #reset
    386      */
    387     public void flush ()
    388         throws IOException
    389     {
    390         output.flush();
    391     }
    392 
    393 
    394     /**
    395      * Set a new output destination for the document.
    396      *
    397      * @param writer The output destination, or null to use
    398      *        standard output.
    399      * @return The current output writer.
    400      * @see #flush
    401      */
    402     public void setOutput (Writer writer)
    403     {
    404         if (writer == null) {
    405             output = new OutputStreamWriter(System.out);
    406         } else {
    407             output = writer;
    408         }
    409     }
    410 
    411 
    412     /**
    413      * Specify a preferred prefix for a Namespace URI.
    414      *
    415      * <p>Note that this method does not actually force the Namespace
    416      * to be declared; to do that, use the {@link
    417      * #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p>
    418      *
    419      * @param uri The Namespace URI.
    420      * @param prefix The preferred prefix, or "" to select
    421      *        the default Namespace.
    422      * @see #getPrefix
    423      * @see #forceNSDecl(java.lang.String)
    424      * @see #forceNSDecl(java.lang.String,java.lang.String)
    425      */
    426     public void setPrefix (String uri, String prefix)
    427     {
    428         prefixTable.put(uri, prefix);
    429     }
    430 
    431 
    432     /**
    433      * Get the current or preferred prefix for a Namespace URI.
    434      *
    435      * @param uri The Namespace URI.
    436      * @return The preferred prefix, or "" for the default Namespace.
    437      * @see #setPrefix
    438      */
    439     public String getPrefix (String uri)
    440     {
    441         return (String)prefixTable.get(uri);
    442     }
    443 
    444 
    445     /**
    446      * Force a Namespace to be declared on the root element.
    447      *
    448      * <p>By default, the XMLWriter will declare only the Namespaces
    449      * needed for an element; as a result, a Namespace may be
    450      * declared many places in a document if it is not used on the
    451      * root element.</p>
    452      *
    453      * <p>This method forces a Namespace to be declared on the root
    454      * element even if it is not used there, and reduces the number
    455      * of xmlns attributes in the document.</p>
    456      *
    457      * @param uri The Namespace URI to declare.
    458      * @see #forceNSDecl(java.lang.String,java.lang.String)
    459      * @see #setPrefix
    460      */
    461     public void forceNSDecl (String uri)
    462     {
    463         forcedDeclTable.put(uri, Boolean.TRUE);
    464     }
    465 
    466 
    467     /**
    468      * Force a Namespace declaration with a preferred prefix.
    469      *
    470      * <p>This is a convenience method that invokes {@link
    471      * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String)
    472      * forceNSDecl}.</p>
    473      *
    474      * @param uri The Namespace URI to declare on the root element.
    475      * @param prefix The preferred prefix for the Namespace, or ""
    476      *        for the default Namespace.
    477      * @see #setPrefix
    478      * @see #forceNSDecl(java.lang.String)
    479      */
    480     public void forceNSDecl (String uri, String prefix)
    481     {
    482         setPrefix(uri, prefix);
    483         forceNSDecl(uri);
    484     }
    485 
    486 
    487 
    488     ////////////////////////////////////////////////////////////////////
    490     // Methods from org.xml.sax.ContentHandler.
    491     ////////////////////////////////////////////////////////////////////
    492 
    493 
    494     /**
    495      * Write the XML declaration at the beginning of the document.
    496      *
    497      * Pass the event on down the filter chain for further processing.
    498      *
    499      * @exception org.xml.sax.SAXException If there is an error
    500      *            writing the XML declaration, or if a handler further down
    501      *            the filter chain raises an exception.
    502      * @see org.xml.sax.ContentHandler#startDocument
    503      */
    504     public void startDocument ()
    505         throws SAXException
    506     {
    507         reset();
    508         if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION, "no")))) {
    509             write("<?xml");
    510             if (version == null) {
    511                 write(" version=\"1.0\"");
    512             } else {
    513                 write(" version=\"");
    514                 write(version);
    515                 write("\"");
    516             }
    517             if (outputEncoding != null && outputEncoding != "") {
    518                 write(" encoding=\"");
    519                 write(outputEncoding);
    520                 write("\"");
    521             }
    522             if (standalone == null) {
    523                 write(" standalone=\"yes\"?>\n");
    524             } else {
    525                 write(" standalone=\"");
    526                 write(standalone);
    527                 write("\"");
    528             }
    529         }
    530         super.startDocument();
    531     }
    532 
    533 
    534     /**
    535      * Write a newline at the end of the document.
    536      *
    537      * Pass the event on down the filter chain for further processing.
    538      *
    539      * @exception org.xml.sax.SAXException If there is an error
    540      *            writing the newline, or if a handler further down
    541      *            the filter chain raises an exception.
    542      * @see org.xml.sax.ContentHandler#endDocument
    543      */
    544     public void endDocument ()
    545         throws SAXException
    546     {
    547         write('\n');
    548         super.endDocument();
    549         try {
    550             flush();
    551         } catch (IOException e) {
    552             throw new SAXException(e);
    553         }
    554     }
    555 
    556 
    557     /**
    558      * Write a start tag.
    559      *
    560      * Pass the event on down the filter chain for further processing.
    561      *
    562      * @param uri The Namespace URI, or the empty string if none
    563      *        is available.
    564      * @param localName The element's local (unprefixed) name (required).
    565      * @param qName The element's qualified (prefixed) name, or the
    566      *        empty string is none is available.  This method will
    567      *        use the qName as a template for generating a prefix
    568      *        if necessary, but it is not guaranteed to use the
    569      *        same qName.
    570      * @param atts The element's attribute list (must not be null).
    571      * @exception org.xml.sax.SAXException If there is an error
    572      *            writing the start tag, or if a handler further down
    573      *            the filter chain raises an exception.
    574      * @see org.xml.sax.ContentHandler#startElement
    575      */
    576     public void startElement (String uri, String localName,
    577                               String qName, Attributes atts)
    578         throws SAXException
    579     {
    580         elementLevel++;
    581         nsSupport.pushContext();
    582 	if (forceDTD && !hasOutputDTD) startDTD(localName == null ? qName : localName, "", "");
    583         write('<');
    584         writeName(uri, localName, qName, true);
    585         writeAttributes(atts);
    586         if (elementLevel == 1) {
    587             forceNSDecls();
    588         }
    589         writeNSDecls();
    590         write('>');
    591 //	System.out.println("%%%% startElement [" + qName + "] htmlMode = " + htmlMode);
    592 	if (htmlMode && (qName.equals("script") || qName.equals("style"))) {
    593                 cdataElement = true;
    594 //		System.out.println("%%%% CDATA element");
    595                 }
    596         super.startElement(uri, localName, qName, atts);
    597     }
    598 
    599 
    600     /**
    601      * Write an end tag.
    602      *
    603      * Pass the event on down the filter chain for further processing.
    604      *
    605      * @param uri The Namespace URI, or the empty string if none
    606      *        is available.
    607      * @param localName The element's local (unprefixed) name (required).
    608      * @param qName The element's qualified (prefixed) name, or the
    609      *        empty string is none is available.  This method will
    610      *        use the qName as a template for generating a prefix
    611      *        if necessary, but it is not guaranteed to use the
    612      *        same qName.
    613      * @exception org.xml.sax.SAXException If there is an error
    614      *            writing the end tag, or if a handler further down
    615      *            the filter chain raises an exception.
    616      * @see org.xml.sax.ContentHandler#endElement
    617      */
    618     public void endElement (String uri, String localName, String qName)
    619         throws SAXException
    620     {
    621 	if (!(htmlMode &&
    622             (uri.equals("http://www.w3.org/1999/xhtml") ||
    623 		uri.equals("")) &&
    624             (qName.equals("area") || qName.equals("base") ||
    625             qName.equals("basefont") || qName.equals("br") ||
    626             qName.equals("col") || qName.equals("frame") ||
    627             qName.equals("hr") || qName.equals("img") ||
    628             qName.equals("input") || qName.equals("isindex") ||
    629             qName.equals("link") || qName.equals("meta") ||
    630             qName.equals("param")))) {
    631                 write("</");
    632                 writeName(uri, localName, qName, true);
    633                 write('>');
    634             }
    635         if (elementLevel == 1) {
    636             write('\n');
    637         }
    638         cdataElement = false;
    639         super.endElement(uri, localName, qName);
    640         nsSupport.popContext();
    641         elementLevel--;
    642     }
    643 
    644 
    645     /**
    646      * Write character data.
    647      *
    648      * Pass the event on down the filter chain for further processing.
    649      *
    650      * @param ch The array of characters to write.
    651      * @param start The starting position in the array.
    652      * @param length The number of characters to write.
    653      * @exception org.xml.sax.SAXException If there is an error
    654      *            writing the characters, or if a handler further down
    655      *            the filter chain raises an exception.
    656      * @see org.xml.sax.ContentHandler#characters
    657      */
    658     public void characters (char ch[], int start, int len)
    659         throws SAXException
    660     {
    661         if (!cdataElement) {
    662           writeEsc(ch, start, len, false);
    663           }
    664         else {
    665           for (int i = start; i < start + len; i++) {
    666             write(ch[i]);
    667             }
    668           }
    669         super.characters(ch, start, len);
    670     }
    671 
    672 
    673     /**
    674      * Write ignorable whitespace.
    675      *
    676      * Pass the event on down the filter chain for further processing.
    677      *
    678      * @param ch The array of characters to write.
    679      * @param start The starting position in the array.
    680      * @param length The number of characters to write.
    681      * @exception org.xml.sax.SAXException If there is an error
    682      *            writing the whitespace, or if a handler further down
    683      *            the filter chain raises an exception.
    684      * @see org.xml.sax.ContentHandler#ignorableWhitespace
    685      */
    686     public void ignorableWhitespace (char ch[], int start, int length)
    687         throws SAXException
    688     {
    689         writeEsc(ch, start, length, false);
    690         super.ignorableWhitespace(ch, start, length);
    691     }
    692 
    693 
    694 
    695     /**
    696      * Write a processing instruction.
    697      *
    698      * Pass the event on down the filter chain for further processing.
    699      *
    700      * @param target The PI target.
    701      * @param data The PI data.
    702      * @exception org.xml.sax.SAXException If there is an error
    703      *            writing the PI, or if a handler further down
    704      *            the filter chain raises an exception.
    705      * @see org.xml.sax.ContentHandler#processingInstruction
    706      */
    707     public void processingInstruction (String target, String data)
    708         throws SAXException
    709     {
    710         write("<?");
    711         write(target);
    712         write(' ');
    713         write(data);
    714         write("?>");
    715         if (elementLevel < 1) {
    716             write('\n');
    717         }
    718         super.processingInstruction(target, data);
    719     }
    720 
    721 
    722 
    723     ////////////////////////////////////////////////////////////////////
    725     // Additional markup.
    726     ////////////////////////////////////////////////////////////////////
    727 
    728     /**
    729      * Write an empty element.
    730      *
    731      * This method writes an empty element tag rather than a start tag
    732      * followed by an end tag.  Both a {@link #startElement
    733      * startElement} and an {@link #endElement endElement} event will
    734      * be passed on down the filter chain.
    735      *
    736      * @param uri The element's Namespace URI, or the empty string
    737      *        if the element has no Namespace or if Namespace
    738      *        processing is not being performed.
    739      * @param localName The element's local name (without prefix).  This
    740      *        parameter must be provided.
    741      * @param qName The element's qualified name (with prefix), or
    742      *        the empty string if none is available.  This parameter
    743      *        is strictly advisory: the writer may or may not use
    744      *        the prefix attached.
    745      * @param atts The element's attribute list.
    746      * @exception org.xml.sax.SAXException If there is an error
    747      *            writing the empty tag, or if a handler further down
    748      *            the filter chain raises an exception.
    749      * @see #startElement
    750      * @see #endElement
    751      */
    752     public void emptyElement (String uri, String localName,
    753                               String qName, Attributes atts)
    754         throws SAXException
    755     {
    756         nsSupport.pushContext();
    757         write('<');
    758         writeName(uri, localName, qName, true);
    759         writeAttributes(atts);
    760         if (elementLevel == 1) {
    761             forceNSDecls();
    762         }
    763         writeNSDecls();
    764         write("/>");
    765         super.startElement(uri, localName, qName, atts);
    766         super.endElement(uri, localName, qName);
    767     }
    768 
    769 
    770 
    771     ////////////////////////////////////////////////////////////////////
    773     // Convenience methods.
    774     ////////////////////////////////////////////////////////////////////
    775 
    776 
    777 
    778     /**
    779      * Start a new element without a qname or attributes.
    780      *
    781      * <p>This method will provide a default empty attribute
    782      * list and an empty string for the qualified name.
    783      * It invokes {@link
    784      * #startElement(String, String, String, Attributes)}
    785      * directly.</p>
    786      *
    787      * @param uri The element's Namespace URI.
    788      * @param localName The element's local name.
    789      * @exception org.xml.sax.SAXException If there is an error
    790      *            writing the start tag, or if a handler further down
    791      *            the filter chain raises an exception.
    792      * @see #startElement(String, String, String, Attributes)
    793      */
    794     public void startElement (String uri, String localName)
    795         throws SAXException
    796     {
    797         startElement(uri, localName, "", EMPTY_ATTS);
    798     }
    799 
    800 
    801     /**
    802      * Start a new element without a qname, attributes or a Namespace URI.
    803      *
    804      * <p>This method will provide an empty string for the
    805      * Namespace URI, and empty string for the qualified name,
    806      * and a default empty attribute list. It invokes
    807      * #startElement(String, String, String, Attributes)}
    808      * directly.</p>
    809      *
    810      * @param localName The element's local name.
    811      * @exception org.xml.sax.SAXException If there is an error
    812      *            writing the start tag, or if a handler further down
    813      *            the filter chain raises an exception.
    814      * @see #startElement(String, String, String, Attributes)
    815      */
    816     public void startElement (String localName)
    817         throws SAXException
    818     {
    819         startElement("", localName, "", EMPTY_ATTS);
    820     }
    821 
    822 
    823     /**
    824      * End an element without a qname.
    825      *
    826      * <p>This method will supply an empty string for the qName.
    827      * It invokes {@link #endElement(String, String, String)}
    828      * directly.</p>
    829      *
    830      * @param uri The element's Namespace URI.
    831      * @param localName The element's local name.
    832      * @exception org.xml.sax.SAXException If there is an error
    833      *            writing the end tag, or if a handler further down
    834      *            the filter chain raises an exception.
    835      * @see #endElement(String, String, String)
    836      */
    837     public void endElement (String uri, String localName)
    838         throws SAXException
    839     {
    840         endElement(uri, localName, "");
    841     }
    842 
    843 
    844     /**
    845      * End an element without a Namespace URI or qname.
    846      *
    847      * <p>This method will supply an empty string for the qName
    848      * and an empty string for the Namespace URI.
    849      * It invokes {@link #endElement(String, String, String)}
    850      * directly.</p>
    851      *
    852      * @param localName The element's local name.
    853      * @exception org.xml.sax.SAXException If there is an error
    854      *            writing the end tag, or if a handler further down
    855      *            the filter chain raises an exception.
    856      * @see #endElement(String, String, String)
    857      */
    858     public void endElement (String localName)
    859         throws SAXException
    860     {
    861         endElement("", localName, "");
    862     }
    863 
    864 
    865     /**
    866      * Add an empty element without a qname or attributes.
    867      *
    868      * <p>This method will supply an empty string for the qname
    869      * and an empty attribute list.  It invokes
    870      * {@link #emptyElement(String, String, String, Attributes)}
    871      * directly.</p>
    872      *
    873      * @param uri The element's Namespace URI.
    874      * @param localName The element's local name.
    875      * @exception org.xml.sax.SAXException If there is an error
    876      *            writing the empty tag, or if a handler further down
    877      *            the filter chain raises an exception.
    878      * @see #emptyElement(String, String, String, Attributes)
    879      */
    880     public void emptyElement (String uri, String localName)
    881         throws SAXException
    882     {
    883         emptyElement(uri, localName, "", EMPTY_ATTS);
    884     }
    885 
    886 
    887     /**
    888      * Add an empty element without a Namespace URI, qname or attributes.
    889      *
    890      * <p>This method will supply an empty string for the qname,
    891      * and empty string for the Namespace URI, and an empty
    892      * attribute list.  It invokes
    893      * {@link #emptyElement(String, String, String, Attributes)}
    894      * directly.</p>
    895      *
    896      * @param localName The element's local name.
    897      * @exception org.xml.sax.SAXException If there is an error
    898      *            writing the empty tag, or if a handler further down
    899      *            the filter chain raises an exception.
    900      * @see #emptyElement(String, String, String, Attributes)
    901      */
    902     public void emptyElement (String localName)
    903         throws SAXException
    904     {
    905         emptyElement("", localName, "", EMPTY_ATTS);
    906     }
    907 
    908 
    909     /**
    910      * Write an element with character data content.
    911      *
    912      * <p>This is a convenience method to write a complete element
    913      * with character data content, including the start tag
    914      * and end tag.</p>
    915      *
    916      * <p>This method invokes
    917      * {@link #startElement(String, String, String, Attributes)},
    918      * followed by
    919      * {@link #characters(String)}, followed by
    920      * {@link #endElement(String, String, String)}.</p>
    921      *
    922      * @param uri The element's Namespace URI.
    923      * @param localName The element's local name.
    924      * @param qName The element's default qualified name.
    925      * @param atts The element's attributes.
    926      * @param content The character data content.
    927      * @exception org.xml.sax.SAXException If there is an error
    928      *            writing the empty tag, or if a handler further down
    929      *            the filter chain raises an exception.
    930      * @see #startElement(String, String, String, Attributes)
    931      * @see #characters(String)
    932      * @see #endElement(String, String, String)
    933      */
    934     public void dataElement (String uri, String localName,
    935                              String qName, Attributes atts,
    936                              String content)
    937         throws SAXException
    938     {
    939         startElement(uri, localName, qName, atts);
    940         characters(content);
    941         endElement(uri, localName, qName);
    942     }
    943 
    944 
    945     /**
    946      * Write an element with character data content but no attributes.
    947      *
    948      * <p>This is a convenience method to write a complete element
    949      * with character data content, including the start tag
    950      * and end tag.  This method provides an empty string
    951      * for the qname and an empty attribute list.</p>
    952      *
    953      * <p>This method invokes
    954      * {@link #startElement(String, String, String, Attributes)},
    955      * followed by
    956      * {@link #characters(String)}, followed by
    957      * {@link #endElement(String, String, String)}.</p>
    958      *
    959      * @param uri The element's Namespace URI.
    960      * @param localName The element's local name.
    961      * @param content The character data content.
    962      * @exception org.xml.sax.SAXException If there is an error
    963      *            writing the empty tag, or if a handler further down
    964      *            the filter chain raises an exception.
    965      * @see #startElement(String, String, String, Attributes)
    966      * @see #characters(String)
    967      * @see #endElement(String, String, String)
    968      */
    969     public void dataElement (String uri, String localName, String content)
    970         throws SAXException
    971     {
    972         dataElement(uri, localName, "", EMPTY_ATTS, content);
    973     }
    974 
    975 
    976     /**
    977      * Write an element with character data content but no attributes or Namespace URI.
    978      *
    979      * <p>This is a convenience method to write a complete element
    980      * with character data content, including the start tag
    981      * and end tag.  The method provides an empty string for the
    982      * Namespace URI, and empty string for the qualified name,
    983      * and an empty attribute list.</p>
    984      *
    985      * <p>This method invokes
    986      * {@link #startElement(String, String, String, Attributes)},
    987      * followed by
    988      * {@link #characters(String)}, followed by
    989      * {@link #endElement(String, String, String)}.</p>
    990      *
    991      * @param localName The element's local name.
    992      * @param content The character data content.
    993      * @exception org.xml.sax.SAXException If there is an error
    994      *            writing the empty tag, or if a handler further down
    995      *            the filter chain raises an exception.
    996      * @see #startElement(String, String, String, Attributes)
    997      * @see #characters(String)
    998      * @see #endElement(String, String, String)
    999      */
   1000     public void dataElement (String localName, String content)
   1001         throws SAXException
   1002     {
   1003         dataElement("", localName, "", EMPTY_ATTS, content);
   1004     }
   1005 
   1006 
   1007     /**
   1008      * Write a string of character data, with XML escaping.
   1009      *
   1010      * <p>This is a convenience method that takes an XML
   1011      * String, converts it to a character array, then invokes
   1012      * {@link #characters(char[], int, int)}.</p>
   1013      *
   1014      * @param data The character data.
   1015      * @exception org.xml.sax.SAXException If there is an error
   1016      *            writing the string, or if a handler further down
   1017      *            the filter chain raises an exception.
   1018      * @see #characters(char[], int, int)
   1019      */
   1020     public void characters (String data)
   1021         throws SAXException
   1022     {
   1023         char ch[] = data.toCharArray();
   1024         characters(ch, 0, ch.length);
   1025     }
   1026 
   1027 
   1028 
   1029     ////////////////////////////////////////////////////////////////////
   1031     // Internal methods.
   1032     ////////////////////////////////////////////////////////////////////
   1033 
   1034 
   1035     /**
   1036      * Force all Namespaces to be declared.
   1037      *
   1038      * This method is used on the root element to ensure that
   1039      * the predeclared Namespaces all appear.
   1040      */
   1041     private void forceNSDecls ()
   1042     {
   1043         Enumeration prefixes = forcedDeclTable.keys();
   1044         while (prefixes.hasMoreElements()) {
   1045             String prefix = (String)prefixes.nextElement();
   1046             doPrefix(prefix, null, true);
   1047         }
   1048     }
   1049 
   1050 
   1051     /**
   1052      * Determine the prefix for an element or attribute name.
   1053      *
   1054      * TODO: this method probably needs some cleanup.
   1055      *
   1056      * @param uri The Namespace URI.
   1057      * @param qName The qualified name (optional); this will be used
   1058      *        to indicate the preferred prefix if none is currently
   1059      *        bound.
   1060      * @param isElement true if this is an element name, false
   1061      *        if it is an attribute name (which cannot use the
   1062      *        default Namespace).
   1063      */
   1064     private String doPrefix (String uri, String qName, boolean isElement)
   1065     {
   1066         String defaultNS = nsSupport.getURI("");
   1067         if ("".equals(uri)) {
   1068             if (isElement && defaultNS != null)
   1069                 nsSupport.declarePrefix("", "");
   1070             return null;
   1071         }
   1072         String prefix;
   1073         if (isElement && defaultNS != null && uri.equals(defaultNS)) {
   1074             prefix = "";
   1075         } else {
   1076             prefix = nsSupport.getPrefix(uri);
   1077         }
   1078         if (prefix != null) {
   1079             return prefix;
   1080         }
   1081         prefix = (String) doneDeclTable.get(uri);
   1082         if (prefix != null &&
   1083             ((!isElement || defaultNS != null) &&
   1084              "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
   1085             prefix = null;
   1086         }
   1087         if (prefix == null) {
   1088             prefix = (String) prefixTable.get(uri);
   1089             if (prefix != null &&
   1090                 ((!isElement || defaultNS != null) &&
   1091                  "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
   1092                 prefix = null;
   1093             }
   1094         }
   1095         if (prefix == null && qName != null && !"".equals(qName)) {
   1096             int i = qName.indexOf(':');
   1097             if (i == -1) {
   1098                 if (isElement && defaultNS == null) {
   1099                     prefix = "";
   1100                 }
   1101             } else {
   1102                 prefix = qName.substring(0, i);
   1103             }
   1104         }
   1105         for (;
   1106              prefix == null || nsSupport.getURI(prefix) != null;
   1107              prefix = "__NS" + ++prefixCounter)
   1108             ;
   1109         nsSupport.declarePrefix(prefix, uri);
   1110         doneDeclTable.put(uri, prefix);
   1111         return prefix;
   1112     }
   1113 
   1114 
   1115     /**
   1116      * Write a raw character.
   1117      *
   1118      * @param c The character to write.
   1119      * @exception org.xml.sax.SAXException If there is an error writing
   1120      *            the character, this method will throw an IOException
   1121      *            wrapped in a SAXException.
   1122      */
   1123     private void write (char c)
   1124         throws SAXException
   1125     {
   1126         try {
   1127             output.write(c);
   1128         } catch (IOException e) {
   1129             throw new SAXException(e);
   1130         }
   1131     }
   1132 
   1133 
   1134     /**
   1135      * Write a raw string.
   1136      *
   1137      * @param s
   1138      * @exception org.xml.sax.SAXException If there is an error writing
   1139      *            the string, this method will throw an IOException
   1140      *            wrapped in a SAXException
   1141      */
   1142     private void write (String s)
   1143     throws SAXException
   1144     {
   1145         try {
   1146             output.write(s);
   1147         } catch (IOException e) {
   1148             throw new SAXException(e);
   1149         }
   1150     }
   1151 
   1152 
   1153     /**
   1154      * Write out an attribute list, escaping values.
   1155      *
   1156      * The names will have prefixes added to them.
   1157      *
   1158      * @param atts The attribute list to write.
   1159      * @exception org.xml.SAXException If there is an error writing
   1160      *            the attribute list, this method will throw an
   1161      *            IOException wrapped in a SAXException.
   1162      */
   1163     private void writeAttributes (Attributes atts)
   1164         throws SAXException
   1165     {
   1166         int len = atts.getLength();
   1167         for (int i = 0; i < len; i++) {
   1168             char ch[] = atts.getValue(i).toCharArray();
   1169             write(' ');
   1170             writeName(atts.getURI(i), atts.getLocalName(i),
   1171                       atts.getQName(i), false);
   1172             if (htmlMode &&
   1173                 booleanAttribute(atts.getLocalName(i), atts.getQName(i), atts.getValue(i))) break;
   1174             write("=\"");
   1175             writeEsc(ch, 0, ch.length, true);
   1176             write('"');
   1177         }
   1178     }
   1179 
   1180 
   1181     private String[] booleans = {"checked", "compact", "declare", "defer",
   1182                                  "disabled", "ismap", "multiple",
   1183                                  "nohref", "noresize", "noshade",
   1184                                  "nowrap", "readonly", "selected"};
   1185 
   1186     // Return true if the attribute is an HTML boolean from the above list.
   1187     private boolean booleanAttribute (String localName, String qName, String value)
   1188     {
   1189         String name = localName;
   1190         if (name == null) {
   1191             int i = qName.indexOf(':');
   1192             if (i != -1) name = qName.substring(i + 1, qName.length());
   1193         }
   1194         if (!name.equals(value)) return false;
   1195         for (int j = 0; j < booleans.length; j++) {
   1196             if (name.equals(booleans[j])) return true;
   1197             }
   1198         return false;
   1199     }
   1200 
   1201     /**
   1202      * Write an array of data characters with escaping.
   1203      *
   1204      * @param ch The array of characters.
   1205      * @param start The starting position.
   1206      * @param length The number of characters to use.
   1207      * @param isAttVal true if this is an attribute value literal.
   1208      * @exception org.xml.SAXException If there is an error writing
   1209      *            the characters, this method will throw an
   1210      *            IOException wrapped in a SAXException.
   1211      */
   1212     private void writeEsc (char ch[], int start,
   1213                              int length, boolean isAttVal)
   1214         throws SAXException
   1215     {
   1216         for (int i = start; i < start + length; i++) {
   1217             switch (ch[i]) {
   1218             case '&':
   1219                 write("&amp;");
   1220                 break;
   1221             case '<':
   1222                 write("&lt;");
   1223                 break;
   1224             case '>':
   1225                 write("&gt;");
   1226                 break;
   1227             case '\"':
   1228                 if (isAttVal) {
   1229                     write("&quot;");
   1230                 } else {
   1231                     write('\"');
   1232                 }
   1233                 break;
   1234             default:
   1235                 if (!unicodeMode && ch[i] > '\u007f') {
   1236                     write("&#");
   1237                     write(Integer.toString(ch[i]));
   1238                     write(';');
   1239                 } else {
   1240                     write(ch[i]);
   1241                 }
   1242             }
   1243         }
   1244     }
   1245 
   1246 
   1247     /**
   1248      * Write out the list of Namespace declarations.
   1249      *
   1250      * @exception org.xml.sax.SAXException This method will throw
   1251      *            an IOException wrapped in a SAXException if
   1252      *            there is an error writing the Namespace
   1253      *            declarations.
   1254      */
   1255     private void writeNSDecls ()
   1256         throws SAXException
   1257     {
   1258         Enumeration prefixes = nsSupport.getDeclaredPrefixes();
   1259         while (prefixes.hasMoreElements()) {
   1260             String prefix = (String) prefixes.nextElement();
   1261             String uri = nsSupport.getURI(prefix);
   1262             if (uri == null) {
   1263                 uri = "";
   1264             }
   1265             char ch[] = uri.toCharArray();
   1266             write(' ');
   1267             if ("".equals(prefix)) {
   1268                 write("xmlns=\"");
   1269             } else {
   1270                 write("xmlns:");
   1271                 write(prefix);
   1272                 write("=\"");
   1273             }
   1274             writeEsc(ch, 0, ch.length, true);
   1275             write('\"');
   1276         }
   1277     }
   1278 
   1279 
   1280     /**
   1281      * Write an element or attribute name.
   1282      *
   1283      * @param uri The Namespace URI.
   1284      * @param localName The local name.
   1285      * @param qName The prefixed name, if available, or the empty string.
   1286      * @param isElement true if this is an element name, false if it
   1287      *        is an attribute name.
   1288      * @exception org.xml.sax.SAXException This method will throw an
   1289      *            IOException wrapped in a SAXException if there is
   1290      *            an error writing the name.
   1291      */
   1292     private void writeName (String uri, String localName,
   1293                               String qName, boolean isElement)
   1294         throws SAXException
   1295     {
   1296         String prefix = doPrefix(uri, qName, isElement);
   1297         if (prefix != null && !"".equals(prefix)) {
   1298             write(prefix);
   1299             write(':');
   1300         }
   1301         if (localName != null && !"".equals(localName)) {
   1302             write(localName);
   1303         } else {
   1304             int i = qName.indexOf(':');
   1305             write(qName.substring(i + 1, qName.length()));
   1306         }
   1307     }
   1308 
   1309 
   1310 
   1312     ////////////////////////////////////////////////////////////////////
   1313     // Default LexicalHandler implementation
   1314     ////////////////////////////////////////////////////////////////////
   1315 
   1316     public void comment(char[] ch, int start, int length) throws SAXException
   1317     {
   1318         write("<!--");
   1319         for (int i = start; i < start + length; i++) {
   1320                 write(ch[i]);
   1321                 if (ch[i] == '-' && i + 1 <= start + length && ch[i+1] == '-')
   1322                         write(' ');
   1323                 }
   1324         write("-->");
   1325     }
   1326 
   1327     public void endCDATA() throws SAXException { }
   1328     public void endDTD() throws SAXException { }
   1329     public void endEntity(String name) throws SAXException { }
   1330     public void startCDATA() throws SAXException { }
   1331     public void startDTD(String name, String publicid, String systemid) throws SAXException {
   1332         if (name == null) return;               // can't cope
   1333 	if (hasOutputDTD) return;		// only one DTD
   1334 	hasOutputDTD = true;
   1335         write("<!DOCTYPE ");
   1336         write(name);
   1337         if (systemid == null) systemid = "";
   1338 	if (overrideSystem != null) systemid = overrideSystem;
   1339         char sysquote = (systemid.indexOf('"') != -1) ? '\'': '"';
   1340 	if (overridePublic != null) publicid = overridePublic;
   1341         if (!(publicid == null || "".equals(publicid))) {
   1342                 char pubquote = (publicid.indexOf('"') != -1) ? '\'': '"';
   1343                 write(" PUBLIC ");
   1344                 write(pubquote);
   1345                 write(publicid);
   1346                 write(pubquote);
   1347                 write(' ');
   1348                 }
   1349         else {
   1350                 write(" SYSTEM ");
   1351                 }
   1352         write(sysquote);
   1353         write(systemid);
   1354         write(sysquote);
   1355         write(">\n");
   1356         }
   1357 
   1358     public void startEntity(String name) throws SAXException { }
   1359 
   1360 
   1361     ////////////////////////////////////////////////////////////////////
   1362     // Output properties
   1363     ////////////////////////////////////////////////////////////////////
   1364 
   1365     public String getOutputProperty(String key) {
   1366         return outputProperties.getProperty(key);
   1367     }
   1368 
   1369     public void setOutputProperty(String key, String value) {
   1370         outputProperties.setProperty(key, value);
   1371 //	System.out.println("%%%% key = [" + key + "] value = [" + value +"]");
   1372         if (key.equals(ENCODING)) {
   1373             outputEncoding = value;
   1374             unicodeMode = value.substring(0, 3).equalsIgnoreCase("utf");
   1375 //                System.out.println("%%%% unicodeMode = " + unicodeMode);
   1376 	}
   1377 	else if (key.equals(METHOD)) {
   1378 		htmlMode = value.equals("html");
   1379 	}
   1380 	else if (key.equals(DOCTYPE_PUBLIC)) {
   1381 		overridePublic = value;
   1382 		forceDTD = true;
   1383 		}
   1384 	else if (key.equals(DOCTYPE_SYSTEM)) {
   1385 		overrideSystem = value;
   1386 		forceDTD = true;
   1387 		}
   1388 	else if (key.equals(VERSION)) {
   1389 		version = value;
   1390 		}
   1391 	else if (key.equals(STANDALONE)) {
   1392 		standalone = value;
   1393 		}
   1394 //	System.out.println("%%%% htmlMode = " + htmlMode);
   1395     }
   1396 
   1397 
   1398     ////////////////////////////////////////////////////////////////////
   1400     // Constants.
   1401     ////////////////////////////////////////////////////////////////////
   1402 
   1403     private final Attributes EMPTY_ATTS = new AttributesImpl();
   1404     public static final String CDATA_SECTION_ELEMENTS =
   1405         "cdata-section-elements";
   1406     public static final String DOCTYPE_PUBLIC = "doctype-public";
   1407     public static final String DOCTYPE_SYSTEM = "doctype-system";
   1408     public static final String ENCODING = "encoding";
   1409     public static final String INDENT = "indent";  // currently ignored
   1410     public static final String MEDIA_TYPE = "media-type";  // currently ignored
   1411     public static final String METHOD = "method";  // currently html or xml
   1412     public static final String OMIT_XML_DECLARATION = "omit-xml-declaration";
   1413     public static final String STANDALONE = "standalone";  // currently ignored
   1414     public static final String VERSION = "version";
   1415 
   1416 
   1417 
   1418     ////////////////////////////////////////////////////////////////////
   1420     // Internal state.
   1421     ////////////////////////////////////////////////////////////////////
   1422 
   1423     private Hashtable prefixTable;
   1424     private Hashtable forcedDeclTable;
   1425     private Hashtable doneDeclTable;
   1426     private int elementLevel = 0;
   1427     private Writer output;
   1428     private NamespaceSupport nsSupport;
   1429     private int prefixCounter = 0;
   1430     private Properties outputProperties;
   1431     private boolean unicodeMode = false;
   1432     private String outputEncoding = "";
   1433     private boolean htmlMode = false;
   1434     private boolean forceDTD = false;
   1435     private boolean hasOutputDTD = false;
   1436     private String overridePublic = null;
   1437     private String overrideSystem = null;
   1438     private String version = null;
   1439     private String standalone = null;
   1440     private boolean cdataElement = false;
   1441 
   1442 }
   1443 
   1444 // end of XMLWriter.java
   1445