1// XMLWriter.java - serialize an XML document.
2// Written by David Megginson, david@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
16package org.ccil.cowan.tagsoup;
17
18import java.io.IOException;
19import java.io.OutputStreamWriter;
20import java.io.Writer;
21import java.util.Enumeration;
22import java.util.Hashtable;
23import java.util.Properties;
24
25import org.xml.sax.Attributes;
26import org.xml.sax.SAXException;
27import org.xml.sax.XMLReader;
28import org.xml.sax.helpers.AttributesImpl;
29import org.xml.sax.helpers.NamespaceSupport;
30import org.xml.sax.helpers.XMLFilterImpl;
31import 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@megginson.com
248 * @version 0.2
249 * @see org.xml.sax.XMLFilter
250 * @see org.xml.sax.ContentHandler
251 */
252public class XMLWriter extends XMLFilterImpl implements LexicalHandler
253{
254
255
256    ////////////////////////////////////////////////////////////////////
257    // Constructors.
258    ////////////////////////////////////////////////////////////////////
259
260
261    /**
262     * Create a new XML writer.
263     *
264     * <p>Write to standard output.</p>
265     */
266    public XMLWriter ()
267    {
268        init(null);
269    }
270
271
272    /**
273     * Create a new XML writer.
274     *
275     * <p>Write to the writer provided.</p>
276     *
277     * @param writer The output destination, or null to use standard
278     *        output.
279     */
280    public XMLWriter (Writer writer)
281    {
282        init(writer);
283    }
284
285
286    /**
287     * Create a new XML writer.
288     *
289     * <p>Use the specified XML reader as the parent.</p>
290     *
291     * @param xmlreader The parent in the filter chain, or null
292     *        for no parent.
293     */
294    public XMLWriter (XMLReader xmlreader)
295    {
296        super(xmlreader);
297        init(null);
298    }
299
300
301    /**
302     * Create a new XML writer.
303     *
304     * <p>Use the specified XML reader as the parent, and write
305     * to the specified writer.</p>
306     *
307     * @param xmlreader The parent in the filter chain, or null
308     *        for no parent.
309     * @param writer The output destination, or null to use standard
310     *        output.
311     */
312    public XMLWriter (XMLReader xmlreader, Writer writer)
313    {
314        super(xmlreader);
315        init(writer);
316    }
317
318
319    /**
320     * Internal initialization method.
321     *
322     * <p>All of the public constructors invoke this method.
323     *
324     * @param writer The output destination, or null to use
325     *        standard output.
326     */
327    private void init (Writer writer)
328    {
329        setOutput(writer);
330        nsSupport = new NamespaceSupport();
331        prefixTable = new Hashtable();
332        forcedDeclTable = new Hashtable();
333        doneDeclTable = new Hashtable();
334        outputProperties = new Properties();
335    }
336
337
338
339    ////////////////////////////////////////////////////////////////////
340    // Public methods.
341    ////////////////////////////////////////////////////////////////////
342
343
344    /**
345     * Reset the writer.
346     *
347     * <p>This method is especially useful if the writer throws an
348     * exception before it is finished, and you want to reuse the
349     * writer for a new document.  It is usually a good idea to
350     * invoke {@link #flush flush} before resetting the writer,
351     * to make sure that no output is lost.</p>
352     *
353     * <p>This method is invoked automatically by the
354     * {@link #startDocument startDocument} method before writing
355     * a new document.</p>
356     *
357     * <p><strong>Note:</strong> this method will <em>not</em>
358     * clear the prefix or URI information in the writer or
359     * the selected output writer.</p>
360     *
361     * @see #flush
362     */
363    public void reset ()
364    {
365        elementLevel = 0;
366        prefixCounter = 0;
367        nsSupport.reset();
368    }
369
370
371    /**
372     * Flush the output.
373     *
374     * <p>This method flushes the output stream.  It is especially useful
375     * when you need to make certain that the entire document has
376     * been written to output but do not want to close the output
377     * stream.</p>
378     *
379     * <p>This method is invoked automatically by the
380     * {@link #endDocument endDocument} method after writing a
381     * document.</p>
382     *
383     * @see #reset
384     */
385    public void flush ()
386        throws IOException
387    {
388        output.flush();
389    }
390
391
392    /**
393     * Set a new output destination for the document.
394     *
395     * @param writer The output destination, or null to use
396     *        standard output.
397     * @return The current output writer.
398     * @see #flush
399     */
400    public void setOutput (Writer writer)
401    {
402        if (writer == null) {
403            output = new OutputStreamWriter(System.out);
404        } else {
405            output = writer;
406        }
407    }
408
409
410    /**
411     * Specify a preferred prefix for a Namespace URI.
412     *
413     * <p>Note that this method does not actually force the Namespace
414     * to be declared; to do that, use the {@link
415     * #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p>
416     *
417     * @param uri The Namespace URI.
418     * @param prefix The preferred prefix, or "" to select
419     *        the default Namespace.
420     * @see #getPrefix
421     * @see #forceNSDecl(java.lang.String)
422     * @see #forceNSDecl(java.lang.String,java.lang.String)
423     */
424    public void setPrefix (String uri, String prefix)
425    {
426        prefixTable.put(uri, prefix);
427    }
428
429
430    /**
431     * Get the current or preferred prefix for a Namespace URI.
432     *
433     * @param uri The Namespace URI.
434     * @return The preferred prefix, or "" for the default Namespace.
435     * @see #setPrefix
436     */
437    public String getPrefix (String uri)
438    {
439        return (String)prefixTable.get(uri);
440    }
441
442
443    /**
444     * Force a Namespace to be declared on the root element.
445     *
446     * <p>By default, the XMLWriter will declare only the Namespaces
447     * needed for an element; as a result, a Namespace may be
448     * declared many places in a document if it is not used on the
449     * root element.</p>
450     *
451     * <p>This method forces a Namespace to be declared on the root
452     * element even if it is not used there, and reduces the number
453     * of xmlns attributes in the document.</p>
454     *
455     * @param uri The Namespace URI to declare.
456     * @see #forceNSDecl(java.lang.String,java.lang.String)
457     * @see #setPrefix
458     */
459    public void forceNSDecl (String uri)
460    {
461        forcedDeclTable.put(uri, Boolean.TRUE);
462    }
463
464
465    /**
466     * Force a Namespace declaration with a preferred prefix.
467     *
468     * <p>This is a convenience method that invokes {@link
469     * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String)
470     * forceNSDecl}.</p>
471     *
472     * @param uri The Namespace URI to declare on the root element.
473     * @param prefix The preferred prefix for the Namespace, or ""
474     *        for the default Namespace.
475     * @see #setPrefix
476     * @see #forceNSDecl(java.lang.String)
477     */
478    public void forceNSDecl (String uri, String prefix)
479    {
480        setPrefix(uri, prefix);
481        forceNSDecl(uri);
482    }
483
484
485
486    ////////////////////////////////////////////////////////////////////
487    // Methods from org.xml.sax.ContentHandler.
488    ////////////////////////////////////////////////////////////////////
489
490
491    /**
492     * Write the XML declaration at the beginning of the document.
493     *
494     * Pass the event on down the filter chain for further processing.
495     *
496     * @exception org.xml.sax.SAXException If there is an error
497     *            writing the XML declaration, or if a handler further down
498     *            the filter chain raises an exception.
499     * @see org.xml.sax.ContentHandler#startDocument
500     */
501    public void startDocument ()
502        throws SAXException
503    {
504        reset();
505        if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION, "no")))) {
506            write("<?xml");
507            if (version == null) {
508                write(" version=\"1.0\"");
509            } else {
510                write(" version=\"");
511                write(version);
512                write("\"");
513            }
514            if (outputEncoding != null && outputEncoding != "") {
515                write(" encoding=\"");
516                write(outputEncoding);
517                write("\"");
518            }
519            if (standalone == null) {
520                write(" standalone=\"yes\"?>\n");
521            } else {
522                write(" standalone=\"");
523                write(standalone);
524                write("\"");
525            }
526        }
527        super.startDocument();
528    }
529
530
531    /**
532     * Write a newline at the end of the document.
533     *
534     * Pass the event on down the filter chain for further processing.
535     *
536     * @exception org.xml.sax.SAXException If there is an error
537     *            writing the newline, or if a handler further down
538     *            the filter chain raises an exception.
539     * @see org.xml.sax.ContentHandler#endDocument
540     */
541    public void endDocument ()
542        throws SAXException
543    {
544        write('\n');
545        super.endDocument();
546        try {
547            flush();
548        } catch (IOException e) {
549            throw new SAXException(e);
550        }
551    }
552
553
554    /**
555     * Write a start tag.
556     *
557     * Pass the event on down the filter chain for further processing.
558     *
559     * @param uri The Namespace URI, or the empty string if none
560     *        is available.
561     * @param localName The element's local (unprefixed) name (required).
562     * @param qName The element's qualified (prefixed) name, or the
563     *        empty string is none is available.  This method will
564     *        use the qName as a template for generating a prefix
565     *        if necessary, but it is not guaranteed to use the
566     *        same qName.
567     * @param atts The element's attribute list (must not be null).
568     * @exception org.xml.sax.SAXException If there is an error
569     *            writing the start tag, or if a handler further down
570     *            the filter chain raises an exception.
571     * @see org.xml.sax.ContentHandler#startElement
572     */
573    public void startElement (String uri, String localName,
574                              String qName, Attributes atts)
575        throws SAXException
576    {
577        elementLevel++;
578        nsSupport.pushContext();
579	if (forceDTD && !hasOutputDTD) startDTD(localName == null ? qName : localName, "", "");
580        write('<');
581        writeName(uri, localName, qName, true);
582        writeAttributes(atts);
583        if (elementLevel == 1) {
584            forceNSDecls();
585        }
586        writeNSDecls();
587        write('>');
588//	System.out.println("%%%% startElement [" + qName + "] htmlMode = " + htmlMode);
589	if (htmlMode && (qName.equals("script") || qName.equals("style"))) {
590                cdataElement = true;
591//		System.out.println("%%%% CDATA element");
592                }
593        super.startElement(uri, localName, qName, atts);
594    }
595
596
597    /**
598     * Write an end tag.
599     *
600     * Pass the event on down the filter chain for further processing.
601     *
602     * @param uri The Namespace URI, or the empty string if none
603     *        is available.
604     * @param localName The element's local (unprefixed) name (required).
605     * @param qName The element's qualified (prefixed) name, or the
606     *        empty string is none is available.  This method will
607     *        use the qName as a template for generating a prefix
608     *        if necessary, but it is not guaranteed to use the
609     *        same qName.
610     * @exception org.xml.sax.SAXException If there is an error
611     *            writing the end tag, or if a handler further down
612     *            the filter chain raises an exception.
613     * @see org.xml.sax.ContentHandler#endElement
614     */
615    public void endElement (String uri, String localName, String qName)
616        throws SAXException
617    {
618	if (!(htmlMode &&
619            (uri.equals("http://www.w3.org/1999/xhtml") ||
620		uri.equals("")) &&
621            (qName.equals("area") || qName.equals("base") ||
622            qName.equals("basefont") || qName.equals("br") ||
623            qName.equals("col") || qName.equals("frame") ||
624            qName.equals("hr") || qName.equals("img") ||
625            qName.equals("input") || qName.equals("isindex") ||
626            qName.equals("link") || qName.equals("meta") ||
627            qName.equals("param")))) {
628                write("</");
629                writeName(uri, localName, qName, true);
630                write('>');
631            }
632        if (elementLevel == 1) {
633            write('\n');
634        }
635        cdataElement = false;
636        super.endElement(uri, localName, qName);
637        nsSupport.popContext();
638        elementLevel--;
639    }
640
641
642    /**
643     * Write character data.
644     *
645     * Pass the event on down the filter chain for further processing.
646     *
647     * @param ch The array of characters to write.
648     * @param start The starting position in the array.
649     * @param length The number of characters to write.
650     * @exception org.xml.sax.SAXException If there is an error
651     *            writing the characters, or if a handler further down
652     *            the filter chain raises an exception.
653     * @see org.xml.sax.ContentHandler#characters
654     */
655    public void characters (char ch[], int start, int len)
656        throws SAXException
657    {
658        if (!cdataElement) {
659          writeEsc(ch, start, len, false);
660          }
661        else {
662          for (int i = start; i < start + len; i++) {
663            write(ch[i]);
664            }
665          }
666        super.characters(ch, start, len);
667    }
668
669
670    /**
671     * Write ignorable whitespace.
672     *
673     * Pass the event on down the filter chain for further processing.
674     *
675     * @param ch The array of characters to write.
676     * @param start The starting position in the array.
677     * @param length The number of characters to write.
678     * @exception org.xml.sax.SAXException If there is an error
679     *            writing the whitespace, or if a handler further down
680     *            the filter chain raises an exception.
681     * @see org.xml.sax.ContentHandler#ignorableWhitespace
682     */
683    public void ignorableWhitespace (char ch[], int start, int length)
684        throws SAXException
685    {
686        writeEsc(ch, start, length, false);
687        super.ignorableWhitespace(ch, start, length);
688    }
689
690
691
692    /**
693     * Write a processing instruction.
694     *
695     * Pass the event on down the filter chain for further processing.
696     *
697     * @param target The PI target.
698     * @param data The PI data.
699     * @exception org.xml.sax.SAXException If there is an error
700     *            writing the PI, or if a handler further down
701     *            the filter chain raises an exception.
702     * @see org.xml.sax.ContentHandler#processingInstruction
703     */
704    public void processingInstruction (String target, String data)
705        throws SAXException
706    {
707        write("<?");
708        write(target);
709        write(' ');
710        write(data);
711        write("?>");
712        if (elementLevel < 1) {
713            write('\n');
714        }
715        super.processingInstruction(target, data);
716    }
717
718
719
720    ////////////////////////////////////////////////////////////////////
721    // Additional markup.
722    ////////////////////////////////////////////////////////////////////
723
724    /**
725     * Write an empty element.
726     *
727     * This method writes an empty element tag rather than a start tag
728     * followed by an end tag.  Both a {@link #startElement
729     * startElement} and an {@link #endElement endElement} event will
730     * be passed on down the filter chain.
731     *
732     * @param uri The element's Namespace URI, or the empty string
733     *        if the element has no Namespace or if Namespace
734     *        processing is not being performed.
735     * @param localName The element's local name (without prefix).  This
736     *        parameter must be provided.
737     * @param qName The element's qualified name (with prefix), or
738     *        the empty string if none is available.  This parameter
739     *        is strictly advisory: the writer may or may not use
740     *        the prefix attached.
741     * @param atts The element's attribute list.
742     * @exception org.xml.sax.SAXException If there is an error
743     *            writing the empty tag, or if a handler further down
744     *            the filter chain raises an exception.
745     * @see #startElement
746     * @see #endElement
747     */
748    public void emptyElement (String uri, String localName,
749                              String qName, Attributes atts)
750        throws SAXException
751    {
752        nsSupport.pushContext();
753        write('<');
754        writeName(uri, localName, qName, true);
755        writeAttributes(atts);
756        if (elementLevel == 1) {
757            forceNSDecls();
758        }
759        writeNSDecls();
760        write("/>");
761        super.startElement(uri, localName, qName, atts);
762        super.endElement(uri, localName, qName);
763    }
764
765
766
767    ////////////////////////////////////////////////////////////////////
768    // Convenience methods.
769    ////////////////////////////////////////////////////////////////////
770
771
772
773    /**
774     * Start a new element without a qname or attributes.
775     *
776     * <p>This method will provide a default empty attribute
777     * list and an empty string for the qualified name.
778     * It invokes {@link
779     * #startElement(String, String, String, Attributes)}
780     * directly.</p>
781     *
782     * @param uri The element's Namespace URI.
783     * @param localName The element's local name.
784     * @exception org.xml.sax.SAXException If there is an error
785     *            writing the start tag, or if a handler further down
786     *            the filter chain raises an exception.
787     * @see #startElement(String, String, String, Attributes)
788     */
789    public void startElement (String uri, String localName)
790        throws SAXException
791    {
792        startElement(uri, localName, "", EMPTY_ATTS);
793    }
794
795
796    /**
797     * Start a new element without a qname, attributes or a Namespace URI.
798     *
799     * <p>This method will provide an empty string for the
800     * Namespace URI, and empty string for the qualified name,
801     * and a default empty attribute list. It invokes
802     * #startElement(String, String, String, Attributes)}
803     * directly.</p>
804     *
805     * @param localName The element's local name.
806     * @exception org.xml.sax.SAXException If there is an error
807     *            writing the start tag, or if a handler further down
808     *            the filter chain raises an exception.
809     * @see #startElement(String, String, String, Attributes)
810     */
811    public void startElement (String localName)
812        throws SAXException
813    {
814        startElement("", localName, "", EMPTY_ATTS);
815    }
816
817
818    /**
819     * End an element without a qname.
820     *
821     * <p>This method will supply an empty string for the qName.
822     * It invokes {@link #endElement(String, String, String)}
823     * directly.</p>
824     *
825     * @param uri The element's Namespace URI.
826     * @param localName The element's local name.
827     * @exception org.xml.sax.SAXException If there is an error
828     *            writing the end tag, or if a handler further down
829     *            the filter chain raises an exception.
830     * @see #endElement(String, String, String)
831     */
832    public void endElement (String uri, String localName)
833        throws SAXException
834    {
835        endElement(uri, localName, "");
836    }
837
838
839    /**
840     * End an element without a Namespace URI or qname.
841     *
842     * <p>This method will supply an empty string for the qName
843     * and an empty string for the Namespace URI.
844     * It invokes {@link #endElement(String, String, String)}
845     * directly.</p>
846     *
847     * @param localName The element's local name.
848     * @exception org.xml.sax.SAXException If there is an error
849     *            writing the end tag, or if a handler further down
850     *            the filter chain raises an exception.
851     * @see #endElement(String, String, String)
852     */
853    public void endElement (String localName)
854        throws SAXException
855    {
856        endElement("", localName, "");
857    }
858
859
860    /**
861     * Add an empty element without a qname or attributes.
862     *
863     * <p>This method will supply an empty string for the qname
864     * and an empty attribute list.  It invokes
865     * {@link #emptyElement(String, String, String, Attributes)}
866     * directly.</p>
867     *
868     * @param uri The element's Namespace URI.
869     * @param localName The element's local name.
870     * @exception org.xml.sax.SAXException If there is an error
871     *            writing the empty tag, or if a handler further down
872     *            the filter chain raises an exception.
873     * @see #emptyElement(String, String, String, Attributes)
874     */
875    public void emptyElement (String uri, String localName)
876        throws SAXException
877    {
878        emptyElement(uri, localName, "", EMPTY_ATTS);
879    }
880
881
882    /**
883     * Add an empty element without a Namespace URI, qname or attributes.
884     *
885     * <p>This method will supply an empty string for the qname,
886     * and empty string for the Namespace URI, and an empty
887     * attribute list.  It invokes
888     * {@link #emptyElement(String, String, String, Attributes)}
889     * directly.</p>
890     *
891     * @param localName The element's local name.
892     * @exception org.xml.sax.SAXException If there is an error
893     *            writing the empty tag, or if a handler further down
894     *            the filter chain raises an exception.
895     * @see #emptyElement(String, String, String, Attributes)
896     */
897    public void emptyElement (String localName)
898        throws SAXException
899    {
900        emptyElement("", localName, "", EMPTY_ATTS);
901    }
902
903
904    /**
905     * Write an element with character data content.
906     *
907     * <p>This is a convenience method to write a complete element
908     * with character data content, including the start tag
909     * and end tag.</p>
910     *
911     * <p>This method invokes
912     * {@link #startElement(String, String, String, Attributes)},
913     * followed by
914     * {@link #characters(String)}, followed by
915     * {@link #endElement(String, String, String)}.</p>
916     *
917     * @param uri The element's Namespace URI.
918     * @param localName The element's local name.
919     * @param qName The element's default qualified name.
920     * @param atts The element's attributes.
921     * @param content The character data content.
922     * @exception org.xml.sax.SAXException If there is an error
923     *            writing the empty tag, or if a handler further down
924     *            the filter chain raises an exception.
925     * @see #startElement(String, String, String, Attributes)
926     * @see #characters(String)
927     * @see #endElement(String, String, String)
928     */
929    public void dataElement (String uri, String localName,
930                             String qName, Attributes atts,
931                             String content)
932        throws SAXException
933    {
934        startElement(uri, localName, qName, atts);
935        characters(content);
936        endElement(uri, localName, qName);
937    }
938
939
940    /**
941     * Write an element with character data content but no attributes.
942     *
943     * <p>This is a convenience method to write a complete element
944     * with character data content, including the start tag
945     * and end tag.  This method provides an empty string
946     * for the qname and an empty attribute list.</p>
947     *
948     * <p>This method invokes
949     * {@link #startElement(String, String, String, Attributes)},
950     * followed by
951     * {@link #characters(String)}, followed by
952     * {@link #endElement(String, String, String)}.</p>
953     *
954     * @param uri The element's Namespace URI.
955     * @param localName The element's local name.
956     * @param content The character data content.
957     * @exception org.xml.sax.SAXException If there is an error
958     *            writing the empty tag, or if a handler further down
959     *            the filter chain raises an exception.
960     * @see #startElement(String, String, String, Attributes)
961     * @see #characters(String)
962     * @see #endElement(String, String, String)
963     */
964    public void dataElement (String uri, String localName, String content)
965        throws SAXException
966    {
967        dataElement(uri, localName, "", EMPTY_ATTS, content);
968    }
969
970
971    /**
972     * Write an element with character data content but no attributes or Namespace URI.
973     *
974     * <p>This is a convenience method to write a complete element
975     * with character data content, including the start tag
976     * and end tag.  The method provides an empty string for the
977     * Namespace URI, and empty string for the qualified name,
978     * and an empty attribute list.</p>
979     *
980     * <p>This method invokes
981     * {@link #startElement(String, String, String, Attributes)},
982     * followed by
983     * {@link #characters(String)}, followed by
984     * {@link #endElement(String, String, String)}.</p>
985     *
986     * @param localName The element's local name.
987     * @param content The character data content.
988     * @exception org.xml.sax.SAXException If there is an error
989     *            writing the empty tag, or if a handler further down
990     *            the filter chain raises an exception.
991     * @see #startElement(String, String, String, Attributes)
992     * @see #characters(String)
993     * @see #endElement(String, String, String)
994     */
995    public void dataElement (String localName, String content)
996        throws SAXException
997    {
998        dataElement("", localName, "", EMPTY_ATTS, content);
999    }
1000
1001
1002    /**
1003     * Write a string of character data, with XML escaping.
1004     *
1005     * <p>This is a convenience method that takes an XML
1006     * String, converts it to a character array, then invokes
1007     * {@link #characters(char[], int, int)}.</p>
1008     *
1009     * @param data The character data.
1010     * @exception org.xml.sax.SAXException If there is an error
1011     *            writing the string, or if a handler further down
1012     *            the filter chain raises an exception.
1013     * @see #characters(char[], int, int)
1014     */
1015    public void characters (String data)
1016        throws SAXException
1017    {
1018        char ch[] = data.toCharArray();
1019        characters(ch, 0, ch.length);
1020    }
1021
1022
1023
1024    ////////////////////////////////////////////////////////////////////
1025    // Internal methods.
1026    ////////////////////////////////////////////////////////////////////
1027
1028
1029    /**
1030     * Force all Namespaces to be declared.
1031     *
1032     * This method is used on the root element to ensure that
1033     * the predeclared Namespaces all appear.
1034     */
1035    private void forceNSDecls ()
1036    {
1037        Enumeration prefixes = forcedDeclTable.keys();
1038        while (prefixes.hasMoreElements()) {
1039            String prefix = (String)prefixes.nextElement();
1040            doPrefix(prefix, null, true);
1041        }
1042    }
1043
1044
1045    /**
1046     * Determine the prefix for an element or attribute name.
1047     *
1048     * TODO: this method probably needs some cleanup.
1049     *
1050     * @param uri The Namespace URI.
1051     * @param qName The qualified name (optional); this will be used
1052     *        to indicate the preferred prefix if none is currently
1053     *        bound.
1054     * @param isElement true if this is an element name, false
1055     *        if it is an attribute name (which cannot use the
1056     *        default Namespace).
1057     */
1058    private String doPrefix (String uri, String qName, boolean isElement)
1059    {
1060        String defaultNS = nsSupport.getURI("");
1061        if ("".equals(uri)) {
1062            if (isElement && defaultNS != null)
1063                nsSupport.declarePrefix("", "");
1064            return null;
1065        }
1066        String prefix;
1067        if (isElement && defaultNS != null && uri.equals(defaultNS)) {
1068            prefix = "";
1069        } else {
1070            prefix = nsSupport.getPrefix(uri);
1071        }
1072        if (prefix != null) {
1073            return prefix;
1074        }
1075        prefix = (String) doneDeclTable.get(uri);
1076        if (prefix != null &&
1077            ((!isElement || defaultNS != null) &&
1078             "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
1079            prefix = null;
1080        }
1081        if (prefix == null) {
1082            prefix = (String) prefixTable.get(uri);
1083            if (prefix != null &&
1084                ((!isElement || defaultNS != null) &&
1085                 "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
1086                prefix = null;
1087            }
1088        }
1089        if (prefix == null && qName != null && !"".equals(qName)) {
1090            int i = qName.indexOf(':');
1091            if (i == -1) {
1092                if (isElement && defaultNS == null) {
1093                    prefix = "";
1094                }
1095            } else {
1096                prefix = qName.substring(0, i);
1097            }
1098        }
1099        for (;
1100             prefix == null || nsSupport.getURI(prefix) != null;
1101             prefix = "__NS" + ++prefixCounter)
1102            ;
1103        nsSupport.declarePrefix(prefix, uri);
1104        doneDeclTable.put(uri, prefix);
1105        return prefix;
1106    }
1107
1108
1109    /**
1110     * Write a raw character.
1111     *
1112     * @param c The character to write.
1113     * @exception org.xml.sax.SAXException If there is an error writing
1114     *            the character, this method will throw an IOException
1115     *            wrapped in a SAXException.
1116     */
1117    private void write (char c)
1118        throws SAXException
1119    {
1120        try {
1121            output.write(c);
1122        } catch (IOException e) {
1123            throw new SAXException(e);
1124        }
1125    }
1126
1127
1128    /**
1129     * Write a raw string.
1130     *
1131     * @param s
1132     * @exception org.xml.sax.SAXException If there is an error writing
1133     *            the string, this method will throw an IOException
1134     *            wrapped in a SAXException
1135     */
1136    private void write (String s)
1137    throws SAXException
1138    {
1139        try {
1140            output.write(s);
1141        } catch (IOException e) {
1142            throw new SAXException(e);
1143        }
1144    }
1145
1146
1147    /**
1148     * Write out an attribute list, escaping values.
1149     *
1150     * The names will have prefixes added to them.
1151     *
1152     * @param atts The attribute list to write.
1153     * @exception org.xml.SAXException If there is an error writing
1154     *            the attribute list, this method will throw an
1155     *            IOException wrapped in a SAXException.
1156     */
1157    private void writeAttributes (Attributes atts)
1158        throws SAXException
1159    {
1160        int len = atts.getLength();
1161        for (int i = 0; i < len; i++) {
1162            char ch[] = atts.getValue(i).toCharArray();
1163            write(' ');
1164            writeName(atts.getURI(i), atts.getLocalName(i),
1165                      atts.getQName(i), false);
1166            if (htmlMode &&
1167                booleanAttribute(atts.getLocalName(i), atts.getQName(i), atts.getValue(i))) break;
1168            write("=\"");
1169            writeEsc(ch, 0, ch.length, true);
1170            write('"');
1171        }
1172    }
1173
1174
1175    private String[] booleans = {"checked", "compact", "declare", "defer",
1176                                 "disabled", "ismap", "multiple",
1177                                 "nohref", "noresize", "noshade",
1178                                 "nowrap", "readonly", "selected"};
1179
1180    // Return true if the attribute is an HTML boolean from the above list.
1181    private boolean booleanAttribute (String localName, String qName, String value)
1182    {
1183        String name = localName;
1184        if (name == null) {
1185            int i = qName.indexOf(':');
1186            if (i != -1) name = qName.substring(i + 1, qName.length());
1187        }
1188        if (!name.equals(value)) return false;
1189        for (int j = 0; j < booleans.length; j++) {
1190            if (name.equals(booleans[j])) return true;
1191            }
1192        return false;
1193    }
1194
1195    /**
1196     * Write an array of data characters with escaping.
1197     *
1198     * @param ch The array of characters.
1199     * @param start The starting position.
1200     * @param length The number of characters to use.
1201     * @param isAttVal true if this is an attribute value literal.
1202     * @exception org.xml.SAXException If there is an error writing
1203     *            the characters, this method will throw an
1204     *            IOException wrapped in a SAXException.
1205     */
1206    private void writeEsc (char ch[], int start,
1207                             int length, boolean isAttVal)
1208        throws SAXException
1209    {
1210        for (int i = start; i < start + length; i++) {
1211            switch (ch[i]) {
1212            case '&':
1213                write("&amp;");
1214                break;
1215            case '<':
1216                write("&lt;");
1217                break;
1218            case '>':
1219                write("&gt;");
1220                break;
1221            case '\"':
1222                if (isAttVal) {
1223                    write("&quot;");
1224                } else {
1225                    write('\"');
1226                }
1227                break;
1228            default:
1229                if (!unicodeMode && ch[i] > '\u007f') {
1230                    write("&#");
1231                    write(Integer.toString(ch[i]));
1232                    write(';');
1233                } else {
1234                    write(ch[i]);
1235                }
1236            }
1237        }
1238    }
1239
1240
1241    /**
1242     * Write out the list of Namespace declarations.
1243     *
1244     * @exception org.xml.sax.SAXException This method will throw
1245     *            an IOException wrapped in a SAXException if
1246     *            there is an error writing the Namespace
1247     *            declarations.
1248     */
1249    private void writeNSDecls ()
1250        throws SAXException
1251    {
1252        Enumeration prefixes = nsSupport.getDeclaredPrefixes();
1253        while (prefixes.hasMoreElements()) {
1254            String prefix = (String) prefixes.nextElement();
1255            String uri = nsSupport.getURI(prefix);
1256            if (uri == null) {
1257                uri = "";
1258            }
1259            char ch[] = uri.toCharArray();
1260            write(' ');
1261            if ("".equals(prefix)) {
1262                write("xmlns=\"");
1263            } else {
1264                write("xmlns:");
1265                write(prefix);
1266                write("=\"");
1267            }
1268            writeEsc(ch, 0, ch.length, true);
1269            write('\"');
1270        }
1271    }
1272
1273
1274    /**
1275     * Write an element or attribute name.
1276     *
1277     * @param uri The Namespace URI.
1278     * @param localName The local name.
1279     * @param qName The prefixed name, if available, or the empty string.
1280     * @param isElement true if this is an element name, false if it
1281     *        is an attribute name.
1282     * @exception org.xml.sax.SAXException This method will throw an
1283     *            IOException wrapped in a SAXException if there is
1284     *            an error writing the name.
1285     */
1286    private void writeName (String uri, String localName,
1287                              String qName, boolean isElement)
1288        throws SAXException
1289    {
1290        String prefix = doPrefix(uri, qName, isElement);
1291        if (prefix != null && !"".equals(prefix)) {
1292            write(prefix);
1293            write(':');
1294        }
1295        if (localName != null && !"".equals(localName)) {
1296            write(localName);
1297        } else {
1298            int i = qName.indexOf(':');
1299            write(qName.substring(i + 1, qName.length()));
1300        }
1301    }
1302
1303
1304
1305    ////////////////////////////////////////////////////////////////////
1306    // Default LexicalHandler implementation
1307    ////////////////////////////////////////////////////////////////////
1308
1309    public void comment(char[] ch, int start, int length) throws SAXException
1310    {
1311        write("<!--");
1312        for (int i = start; i < start + length; i++) {
1313                write(ch[i]);
1314                if (ch[i] == '-' && i + 1 <= start + length && ch[i+1] == '-')
1315                        write(' ');
1316                }
1317        write("-->");
1318    }
1319
1320    public void endCDATA() throws SAXException { }
1321    public void endDTD() throws SAXException { }
1322    public void endEntity(String name) throws SAXException { }
1323    public void startCDATA() throws SAXException { }
1324    public void startDTD(String name, String publicid, String systemid) throws SAXException {
1325        if (name == null) return;               // can't cope
1326	if (hasOutputDTD) return;		// only one DTD
1327	hasOutputDTD = true;
1328        write("<!DOCTYPE ");
1329        write(name);
1330        if (systemid == null) systemid = "";
1331	if (overrideSystem != null) systemid = overrideSystem;
1332        char sysquote = (systemid.indexOf('"') != -1) ? '\'': '"';
1333	if (overridePublic != null) publicid = overridePublic;
1334        if (!(publicid == null || "".equals(publicid))) {
1335                char pubquote = (publicid.indexOf('"') != -1) ? '\'': '"';
1336                write(" PUBLIC ");
1337                write(pubquote);
1338                write(publicid);
1339                write(pubquote);
1340                write(' ');
1341                }
1342        else {
1343                write(" SYSTEM ");
1344                }
1345        write(sysquote);
1346        write(systemid);
1347        write(sysquote);
1348        write(">\n");
1349        }
1350
1351    public void startEntity(String name) throws SAXException { }
1352
1353
1354    ////////////////////////////////////////////////////////////////////
1355    // Output properties
1356    ////////////////////////////////////////////////////////////////////
1357
1358    public String getOutputProperty(String key) {
1359        return outputProperties.getProperty(key);
1360    }
1361
1362    public void setOutputProperty(String key, String value) {
1363        outputProperties.setProperty(key, value);
1364//	System.out.println("%%%% key = [" + key + "] value = [" + value +"]");
1365        if (key.equals(ENCODING)) {
1366            outputEncoding = value;
1367            unicodeMode = value.substring(0, 3).equalsIgnoreCase("utf");
1368//                System.out.println("%%%% unicodeMode = " + unicodeMode);
1369	}
1370	else if (key.equals(METHOD)) {
1371		htmlMode = value.equals("html");
1372	}
1373	else if (key.equals(DOCTYPE_PUBLIC)) {
1374		overridePublic = value;
1375		forceDTD = true;
1376		}
1377	else if (key.equals(DOCTYPE_SYSTEM)) {
1378		overrideSystem = value;
1379		forceDTD = true;
1380		}
1381	else if (key.equals(VERSION)) {
1382		version = value;
1383		}
1384	else if (key.equals(STANDALONE)) {
1385		standalone = value;
1386		}
1387//	System.out.println("%%%% htmlMode = " + htmlMode);
1388    }
1389
1390
1391    ////////////////////////////////////////////////////////////////////
1392    // Constants.
1393    ////////////////////////////////////////////////////////////////////
1394
1395    private final Attributes EMPTY_ATTS = new AttributesImpl();
1396    public static final String CDATA_SECTION_ELEMENTS =
1397        "cdata-section-elements";
1398    public static final String DOCTYPE_PUBLIC = "doctype-public";
1399    public static final String DOCTYPE_SYSTEM = "doctype-system";
1400    public static final String ENCODING = "encoding";
1401    public static final String INDENT = "indent";  // currently ignored
1402    public static final String MEDIA_TYPE = "media-type";  // currently ignored
1403    public static final String METHOD = "method";  // currently html or xml
1404    public static final String OMIT_XML_DECLARATION = "omit-xml-declaration";
1405    public static final String STANDALONE = "standalone";  // currently ignored
1406    public static final String VERSION = "version";
1407
1408
1409
1410    ////////////////////////////////////////////////////////////////////
1411    // Internal state.
1412    ////////////////////////////////////////////////////////////////////
1413
1414    private Hashtable prefixTable;
1415    private Hashtable forcedDeclTable;
1416    private Hashtable doneDeclTable;
1417    private int elementLevel = 0;
1418    private Writer output;
1419    private NamespaceSupport nsSupport;
1420    private int prefixCounter = 0;
1421    private Properties outputProperties;
1422    private boolean unicodeMode = false;
1423    private String outputEncoding = "";
1424    private boolean htmlMode = false;
1425    private boolean forceDTD = false;
1426    private boolean hasOutputDTD = false;
1427    private String overridePublic = null;
1428    private String overrideSystem = null;
1429    private String version = null;
1430    private String standalone = null;
1431    private boolean cdataElement = false;
1432
1433}
1434
1435// end of XMLWriter.java
1436