• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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@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     ////////////////////////////////////////////////////////////////////
257     // Constructors.
258     ////////////////////////////////////////////////////////////////////
259 
260 
261     /**
262      * Create a new XML writer.
263      *
264      * <p>Write to standard output.</p>
265      */
XMLWriter()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      */
XMLWriter(Writer writer)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      */
XMLWriter(XMLReader xmlreader)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      */
XMLWriter(XMLReader xmlreader, Writer writer)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      */
init(Writer writer)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      */
reset()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      */
flush()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      */
setOutput(Writer writer)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      */
setPrefix(String uri, String prefix)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      */
getPrefix(String uri)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      */
forceNSDecl(String uri)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      */
forceNSDecl(String uri, String prefix)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      */
startDocument()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      */
endDocument()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      */
startElement(String uri, String localName, String qName, Attributes atts)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      */
endElement(String uri, String localName, String qName)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      */
characters(char ch[], int start, int len)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      */
ignorableWhitespace(char ch[], int start, int length)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      */
processingInstruction(String target, String data)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      */
emptyElement(String uri, String localName, String qName, Attributes atts)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      */
startElement(String uri, String localName)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      */
startElement(String localName)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      */
endElement(String uri, String localName)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      */
endElement(String localName)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      */
emptyElement(String uri, String localName)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      */
emptyElement(String localName)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      */
dataElement(String uri, String localName, String qName, Attributes atts, String content)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      */
dataElement(String uri, String localName, String content)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      */
dataElement(String localName, String content)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      */
characters(String data)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      */
forceNSDecls()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      */
doPrefix(String uri, String qName, boolean isElement)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      */
write(char c)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      */
write(String s)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      */
writeAttributes(Attributes atts)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.
booleanAttribute(String localName, String qName, String value)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      */
writeEsc(char ch[], int start, int length, boolean isAttVal)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      */
writeNSDecls()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      */
writeName(String uri, String localName, String qName, boolean isElement)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 
comment(char[] ch, int start, int length)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 
endCDATA()1320     public void endCDATA() throws SAXException { }
endDTD()1321     public void endDTD() throws SAXException { }
endEntity(String name)1322     public void endEntity(String name) throws SAXException { }
startCDATA()1323     public void startCDATA() throws SAXException { }
startDTD(String name, String publicid, String systemid)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 
startEntity(String name)1351     public void startEntity(String name) throws SAXException { }
1352 
1353 
1354     ////////////////////////////////////////////////////////////////////
1355     // Output properties
1356     ////////////////////////////////////////////////////////////////////
1357 
getOutputProperty(String key)1358     public String getOutputProperty(String key) {
1359         return outputProperties.getProperty(key);
1360     }
1361 
setOutputProperty(String key, String value)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