KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ccil > cowan > tagsoup > XMLWriter


1 // XMLWriter.java - serialize an XML document.
2
// Written by David Megginson, david@megginson.com
3
// NO WARRANTY! This class is in the public domain.
4
// Modified by John Cowan and Leigh Klotz for the TagSoup project. Still in the public domain.
5
// New features:
6
// it is a LexicalHandler
7
// it prints a comment if the LexicalHandler#comment method is called
8
// it supports certain XSLT output properties using get/setOutputProperty
9

10 // $Id: XMLWriter.java,v 1.1 2004/01/28 05:35:43 joe Exp $
11

12 package org.ccil.cowan.tagsoup;
13
14 import java.io.IOException JavaDoc;
15 import java.io.OutputStreamWriter JavaDoc;
16 import java.io.Writer JavaDoc;
17 import java.util.Enumeration JavaDoc;
18 import java.util.Hashtable JavaDoc;
19 import java.util.Properties JavaDoc;
20
21 import org.xml.sax.Attributes JavaDoc;
22 import org.xml.sax.SAXException JavaDoc;
23 import org.xml.sax.XMLReader JavaDoc;
24 import org.xml.sax.helpers.AttributesImpl JavaDoc;
25 import org.xml.sax.helpers.NamespaceSupport JavaDoc;
26 import org.xml.sax.helpers.XMLFilterImpl JavaDoc;
27 import org.xml.sax.ext.LexicalHandler JavaDoc;
28
29
30 /**
31  * Filter to write an XML document from a SAX event stream.
32  *
33  * <p>This class can be used by itself or as part of a SAX event
34  * stream: it takes as input a series of SAX2 ContentHandler
35  * events and uses the information in those events to write
36  * an XML document. Since this class is a filter, it can also
37  * pass the events on down a filter chain for further processing
38  * (you can use the XMLWriter to take a snapshot of the current
39  * state at any point in a filter chain), and it can be
40  * used directly as a ContentHandler for a SAX2 XMLReader.</p>
41  *
42  * <p>The client creates a document by invoking the methods for
43  * standard SAX2 events, always beginning with the
44  * {@link #startDocument startDocument} method and ending with
45  * the {@link #endDocument endDocument} method. There are convenience
46  * methods provided so that clients to not have to create empty
47  * attribute lists or provide empty strings as parameters; for
48  * example, the method invocation</p>
49  *
50  * <pre>
51  * w.startElement("foo");
52  * </pre>
53  *
54  * <p>is equivalent to the regular SAX2 ContentHandler method</p>
55  *
56  * <pre>
57  * w.startElement("", "foo", "", new AttributesImpl());
58  * </pre>
59  *
60  * <p>Except that it is more efficient because it does not allocate
61  * a new empty attribute list each time. The following code will send
62  * a simple XML document to standard output:</p>
63  *
64  * <pre>
65  * XMLWriter w = new XMLWriter();
66  *
67  * w.startDocument();
68  * w.startElement("greeting");
69  * w.characters("Hello, world!");
70  * w.endElement("greeting");
71  * w.endDocument();
72  * </pre>
73  *
74  * <p>The resulting document will look like this:</p>
75  *
76  * <pre>
77  * &lt;?xml version="1.0" standalone="yes"?>
78  *
79  * &lt;greeting>Hello, world!&lt;/greeting>
80  * </pre>
81  *
82  * <p>In fact, there is an even simpler convenience method,
83  * <var>dataElement</var>, designed for writing elements that
84  * contain only character data, so the code to generate the
85  * document could be shortened to</p>
86  *
87  * <pre>
88  * XMLWriter w = new XMLWriter();
89  *
90  * w.startDocument();
91  * w.dataElement("greeting", "Hello, world!");
92  * w.endDocument();
93  * </pre>
94  *
95  * <h2>Whitespace</h2>
96  *
97  * <p>According to the XML Recommendation, <em>all</em> whitespace
98  * in an XML document is potentially significant to an application,
99  * so this class never adds newlines or indentation. If you
100  * insert three elements in a row, as in</p>
101  *
102  * <pre>
103  * w.dataElement("item", "1");
104  * w.dataElement("item", "2");
105  * w.dataElement("item", "3");
106  * </pre>
107  *
108  * <p>you will end up with</p>
109  *
110  * <pre>
111  * &lt;item>1&lt;/item>&lt;item>3&lt;/item>&lt;item>3&lt;/item>
112  * </pre>
113  *
114  * <p>You need to invoke one of the <var>characters</var> methods
115  * explicitly to add newlines or indentation. Alternatively, you
116  * can use {@link com.megginson.sax.DataWriter DataWriter}, which
117  * is derived from this class -- it is optimized for writing
118  * purely data-oriented (or field-oriented) XML, and does automatic
119  * linebreaks and indentation (but does not support mixed content
120  * properly).</p>
121  *
122  *
123  * <h2>Namespace Support</h2>
124  *
125  * <p>The writer contains extensive support for XML Namespaces, so that
126  * a client application does not have to keep track of prefixes and
127  * supply <var>xmlns</var> attributes. By default, the XML writer will
128  * generate Namespace declarations in the form _NS1, _NS2, etc., wherever
129  * they are needed, as in the following example:</p>
130  *
131  * <pre>
132  * w.startDocument();
133  * w.emptyElement("http://www.foo.com/ns/", "foo");
134  * w.endDocument();
135  * </pre>
136  *
137  * <p>The resulting document will look like this:</p>
138  *
139  * <pre>
140  * &lt;?xml version="1.0" standalone="yes"?>
141  *
142  * &lt;_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
143  * </pre>
144  *
145  * <p>In many cases, document authors will prefer to choose their
146  * own prefixes rather than using the (ugly) default names. The
147  * XML writer allows two methods for selecting prefixes:</p>
148  *
149  * <ol>
150  * <li>the qualified name</li>
151  * <li>the {@link #setPrefix setPrefix} method.</li>
152  * </ol>
153  *
154  * <p>Whenever the XML writer finds a new Namespace URI, it checks
155  * to see if a qualified (prefixed) name is also available; if so
156  * it attempts to use the name's prefix (as long as the prefix is
157  * not already in use for another Namespace URI).</p>
158  *
159  * <p>Before writing a document, the client can also pre-map a prefix
160  * to a Namespace URI with the setPrefix method:</p>
161  *
162  * <pre>
163  * w.setPrefix("http://www.foo.com/ns/", "foo");
164  * w.startDocument();
165  * w.emptyElement("http://www.foo.com/ns/", "foo");
166  * w.endDocument();
167  * </pre>
168  *
169  * <p>The resulting document will look like this:</p>
170  *
171  * <pre>
172  * &lt;?xml version="1.0" standalone="yes"?>
173  *
174  * &lt;foo:foo xmlns:foo="http://www.foo.com/ns/"/>
175  * </pre>
176  *
177  * <p>The default Namespace simply uses an empty string as the prefix:</p>
178  *
179  * <pre>
180  * w.setPrefix("http://www.foo.com/ns/", "");
181  * w.startDocument();
182  * w.emptyElement("http://www.foo.com/ns/", "foo");
183  * w.endDocument();
184  * </pre>
185  *
186  * <p>The resulting document will look like this:</p>
187  *
188  * <pre>
189  * &lt;?xml version="1.0" standalone="yes"?>
190  *
191  * &lt;foo xmlns="http://www.foo.com/ns/"/>
192  * </pre>
193  *
194  * <p>By default, the XML writer will not declare a Namespace until
195  * it is actually used. Sometimes, this approach will create
196  * a large number of Namespace declarations, as in the following
197  * example:</p>
198  *
199  * <pre>
200  * &lt;xml version="1.0" standalone="yes"?>
201  *
202  * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
203  * &lt;rdf:Description about="http://www.foo.com/ids/books/12345">
204  * &lt;dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night&lt;/dc:title>
205  * &lt;dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith&lt;/dc:title>
206  * &lt;dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09&lt;/dc:title>
207  * &lt;/rdf:Description>
208  * &lt;/rdf:RDF>
209  * </pre>
210  *
211  * <p>The "rdf" prefix is declared only once, because the RDF Namespace
212  * is used by the root element and can be inherited by all of its
213  * descendants; the "dc" prefix, on the other hand, is declared three
214  * times, because no higher element uses the Namespace. To solve this
215  * problem, you can instruct the XML writer to predeclare Namespaces
216  * on the root element even if they are not used there:</p>
217  *
218  * <pre>
219  * w.forceNSDecl("http://www.purl.org/dc/");
220  * </pre>
221  *
222  * <p>Now, the "dc" prefix will be declared on the root element even
223  * though it's not needed there, and can be inherited by its
224  * descendants:</p>
225  *
226  * <pre>
227  * &lt;xml version="1.0" standalone="yes"?>
228  *
229  * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
230  * xmlns:dc="http://www.purl.org/dc/">
231  * &lt;rdf:Description about="http://www.foo.com/ids/books/12345">
232  * &lt;dc:title>A Dark Night&lt;/dc:title>
233  * &lt;dc:creator>Jane Smith&lt;/dc:title>
234  * &lt;dc:date>2000-09-09&lt;/dc:title>
235  * &lt;/rdf:Description>
236  * &lt;/rdf:RDF>
237  * </pre>
238  *
239  * <p>This approach is also useful for declaring Namespace prefixes
240  * that be used by qualified names appearing in attribute values or
241  * character data.</p>
242  *
243  * @author David Megginson, david@megginson.com
244  * @version 0.2
245  * @see org.xml.sax.XMLFilter
246  * @see org.xml.sax.ContentHandler
247  */

248 public class XMLWriter extends XMLFilterImpl JavaDoc implements LexicalHandler JavaDoc
249 {
250
251
252     ////////////////////////////////////////////////////////////////////
253
// Constructors.
254
////////////////////////////////////////////////////////////////////
255

256
257     /**
258      * Create a new XML writer.
259      *
260      * <p>Write to standard output.</p>
261      */

262     public XMLWriter ()
263     {
264         init(null);
265     }
266     
267
268     /**
269      * Create a new XML writer.
270      *
271      * <p>Write to the writer provided.</p>
272      *
273      * @param writer The output destination, or null to use standard
274      * output.
275      */

276     public XMLWriter (Writer JavaDoc writer)
277     {
278         init(writer);
279     }
280     
281
282     /**
283      * Create a new XML writer.
284      *
285      * <p>Use the specified XML reader as the parent.</p>
286      *
287      * @param xmlreader The parent in the filter chain, or null
288      * for no parent.
289      */

290     public XMLWriter (XMLReader JavaDoc xmlreader)
291     {
292         super(xmlreader);
293         init(null);
294     }
295     
296
297     /**
298      * Create a new XML writer.
299      *
300      * <p>Use the specified XML reader as the parent, and write
301      * to the specified writer.</p>
302      *
303      * @param xmlreader The parent in the filter chain, or null
304      * for no parent.
305      * @param writer The output destination, or null to use standard
306      * output.
307      */

308     public XMLWriter (XMLReader JavaDoc xmlreader, Writer JavaDoc writer)
309     {
310         super(xmlreader);
311         init(writer);
312     }
313
314
315     /**
316      * Internal initialization method.
317      *
318      * <p>All of the public constructors invoke this method.
319      *
320      * @param writer The output destination, or null to use
321      * standard output.
322      */

323     private void init (Writer JavaDoc writer)
324     {
325         setOutput(writer);
326         nsSupport = new NamespaceSupport JavaDoc();
327         prefixTable = new Hashtable JavaDoc();
328         forcedDeclTable = new Hashtable JavaDoc();
329         doneDeclTable = new Hashtable JavaDoc();
330         outputProperties = new Properties JavaDoc();
331     }
332
333
334
335     ////////////////////////////////////////////////////////////////////
336
// Public methods.
337
////////////////////////////////////////////////////////////////////
338

339
340     /**
341      * Reset the writer.
342      *
343      * <p>This method is especially useful if the writer throws an
344      * exception before it is finished, and you want to reuse the
345      * writer for a new document. It is usually a good idea to
346      * invoke {@link #flush flush} before resetting the writer,
347      * to make sure that no output is lost.</p>
348      *
349      * <p>This method is invoked automatically by the
350      * {@link #startDocument startDocument} method before writing
351      * a new document.</p>
352      *
353      * <p><strong>Note:</strong> this method will <em>not</em>
354      * clear the prefix or URI information in the writer or
355      * the selected output writer.</p>
356      *
357      * @see #flush
358      */

359     public void reset ()
360     {
361         elementLevel = 0;
362         prefixCounter = 0;
363         nsSupport.reset();
364     }
365     
366
367     /**
368      * Flush the output.
369      *
370      * <p>This method flushes the output stream. It is especially useful
371      * when you need to make certain that the entire document has
372      * been written to output but do not want to close the output
373      * stream.</p>
374      *
375      * <p>This method is invoked automatically by the
376      * {@link #endDocument endDocument} method after writing a
377      * document.</p>
378      *
379      * @see #reset
380      */

381     public void flush ()
382         throws IOException JavaDoc
383     {
384         output.flush();
385     }
386     
387
388     /**
389      * Set a new output destination for the document.
390      *
391      * @param writer The output destination, or null to use
392      * standard output.
393      * @return The current output writer.
394      * @see #flush
395      */

396     public void setOutput (Writer JavaDoc writer)
397     {
398         if (writer == null) {
399             output = new OutputStreamWriter JavaDoc(System.out);
400         } else {
401             output = writer;
402         }
403     }
404
405
406     /**
407      * Specify a preferred prefix for a Namespace URI.
408      *
409      * <p>Note that this method does not actually force the Namespace
410      * to be declared; to do that, use the {@link
411      * #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p>
412      *
413      * @param uri The Namespace URI.
414      * @param prefix The preferred prefix, or "" to select
415      * the default Namespace.
416      * @see #getPrefix
417      * @see #forceNSDecl(java.lang.String)
418      * @see #forceNSDecl(java.lang.String,java.lang.String)
419      */

420     public void setPrefix (String JavaDoc uri, String JavaDoc prefix)
421     {
422         prefixTable.put(uri, prefix);
423     }
424     
425
426     /**
427      * Get the current or preferred prefix for a Namespace URI.
428      *
429      * @param uri The Namespace URI.
430      * @return The preferred prefix, or "" for the default Namespace.
431      * @see #setPrefix
432      */

433     public String JavaDoc getPrefix (String JavaDoc uri)
434     {
435         return (String JavaDoc)prefixTable.get(uri);
436     }
437     
438
439     /**
440      * Force a Namespace to be declared on the root element.
441      *
442      * <p>By default, the XMLWriter will declare only the Namespaces
443      * needed for an element; as a result, a Namespace may be
444      * declared many places in a document if it is not used on the
445      * root element.</p>
446      *
447      * <p>This method forces a Namespace to be declared on the root
448      * element even if it is not used there, and reduces the number
449      * of xmlns attributes in the document.</p>
450      *
451      * @param uri The Namespace URI to declare.
452      * @see #forceNSDecl(java.lang.String,java.lang.String)
453      * @see #setPrefix
454      */

455     public void forceNSDecl (String JavaDoc uri)
456     {
457         forcedDeclTable.put(uri, Boolean.TRUE);
458     }
459     
460
461     /**
462      * Force a Namespace declaration with a preferred prefix.
463      *
464      * <p>This is a convenience method that invokes {@link
465      * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String)
466      * forceNSDecl}.</p>
467      *
468      * @param uri The Namespace URI to declare on the root element.
469      * @param prefix The preferred prefix for the Namespace, or ""
470      * for the default Namespace.
471      * @see #setPrefix
472      * @see #forceNSDecl(java.lang.String)
473      */

474     public void forceNSDecl (String JavaDoc uri, String JavaDoc prefix)
475     {
476         setPrefix(uri, prefix);
477         forceNSDecl(uri);
478     }
479     
480
481
482     ////////////////////////////////////////////////////////////////////
483
// Methods from org.xml.sax.ContentHandler.
484
////////////////////////////////////////////////////////////////////
485

486
487     /**
488      * Write the XML declaration at the beginning of the document.
489      *
490      * Pass the event on down the filter chain for further processing.
491      *
492      * @exception org.xml.sax.SAXException If there is an error
493      * writing the XML declaration, or if a handler further down
494      * the filter chain raises an exception.
495      * @see org.xml.sax.ContentHandler#startDocument
496      */

497     public void startDocument ()
498         throws SAXException JavaDoc
499     {
500         reset();
501         if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION, "no")))) {
502             write("<?xml version=\"1.0\"");
503             if (outputEncoding != null && outputEncoding != "") {
504                 write(" encoding=\"");
505                 write(outputEncoding);
506                 write("\"");
507             }
508             write(" standalone=\"yes\"?>\n");
509         }
510         super.startDocument();
511     }
512
513
514     /**
515      * Write a newline at the end of the document.
516      *
517      * Pass the event on down the filter chain for further processing.
518      *
519      * @exception org.xml.sax.SAXException If there is an error
520      * writing the newline, or if a handler further down
521      * the filter chain raises an exception.
522      * @see org.xml.sax.ContentHandler#endDocument
523      */

524     public void endDocument ()
525         throws SAXException JavaDoc
526     {
527         write('\n');
528         super.endDocument();
529         try {
530             flush();
531         } catch (IOException JavaDoc e) {
532             throw new SAXException JavaDoc(e);
533         }
534     }
535     
536
537     /**
538      * Write a start tag.
539      *
540      * Pass the event on down the filter chain for further processing.
541      *
542      * @param uri The Namespace URI, or the empty string if none
543      * is available.
544      * @param localName The element's local (unprefixed) name (required).
545      * @param qName The element's qualified (prefixed) name, or the
546      * empty string is none is available. This method will
547      * use the qName as a template for generating a prefix
548      * if necessary, but it is not guaranteed to use the
549      * same qName.
550      * @param atts The element's attribute list (must not be null).
551      * @exception org.xml.sax.SAXException If there is an error
552      * writing the start tag, or if a handler further down
553      * the filter chain raises an exception.
554      * @see org.xml.sax.ContentHandler#startElement
555      */

556     public void startElement (String JavaDoc uri, String JavaDoc localName,
557                               String JavaDoc qName, Attributes JavaDoc atts)
558         throws SAXException JavaDoc
559     {
560         elementLevel++;
561         nsSupport.pushContext();
562         write('<');
563         writeName(uri, localName, qName, true);
564         writeAttributes(atts);
565         if (elementLevel == 1) {
566             forceNSDecls();
567         }
568         writeNSDecls();
569         write('>');
570 // System.out.println("%%%% startElement [" + qName + "] htmlMode = " + htmlMode);
571
if (htmlMode && (qName.equals("script") || qName.equals("style"))) {
572                 cdataElement = true;
573 // System.out.println("%%%% CDATA element");
574
}
575         super.startElement(uri, localName, qName, atts);
576     }
577
578
579     /**
580      * Write an end tag.
581      *
582      * Pass the event on down the filter chain for further processing.
583      *
584      * @param uri The Namespace URI, or the empty string if none
585      * is available.
586      * @param localName The element's local (unprefixed) name (required).
587      * @param qName The element's qualified (prefixed) name, or the
588      * empty string is none is available. This method will
589      * use the qName as a template for generating a prefix
590      * if necessary, but it is not guaranteed to use the
591      * same qName.
592      * @exception org.xml.sax.SAXException If there is an error
593      * writing the end tag, or if a handler further down
594      * the filter chain raises an exception.
595      * @see org.xml.sax.ContentHandler#endElement
596      */

597     public void endElement (String JavaDoc uri, String JavaDoc localName, String JavaDoc qName)
598         throws SAXException JavaDoc
599     {
600     if (!(htmlMode &&
601             (uri.equals("http://www.w3.org/1999/xhtml") ||
602         uri.equals("")) &&
603             (qName.equals("area") || qName.equals("base") ||
604             qName.equals("basefont") || qName.equals("br") ||
605             qName.equals("col") || qName.equals("frame") ||
606             qName.equals("hr") || qName.equals("img") ||
607             qName.equals("input") || qName.equals("isindex") ||
608             qName.equals("link") || qName.equals("meta") ||
609             qName.equals("param")))) {
610                 write("</");
611                 writeName(uri, localName, qName, true);
612                 write('>');
613             }
614         if (elementLevel == 1) {
615             write('\n');
616         }
617         cdataElement = false;
618         super.endElement(uri, localName, qName);
619         nsSupport.popContext();
620         elementLevel--;
621     }
622     
623
624     /**
625      * Write character data.
626      *
627      * Pass the event on down the filter chain for further processing.
628      *
629      * @param ch The array of characters to write.
630      * @param start The starting position in the array.
631      * @param length The number of characters to write.
632      * @exception org.xml.sax.SAXException If there is an error
633      * writing the characters, or if a handler further down
634      * the filter chain raises an exception.
635      * @see org.xml.sax.ContentHandler#characters
636      */

637     public void characters (char ch[], int start, int len)
638         throws SAXException JavaDoc
639     {
640         if (!cdataElement) {
641           writeEsc(ch, start, len, false);
642           }
643         else {
644           for (int i = start; i < start + len; i++) {
645             write(ch[i]);
646             }
647           }
648         super.characters(ch, start, len);
649     }
650     
651
652     /**
653      * Write ignorable whitespace.
654      *
655      * Pass the event on down the filter chain for further processing.
656      *
657      * @param ch The array of characters to write.
658      * @param start The starting position in the array.
659      * @param length The number of characters to write.
660      * @exception org.xml.sax.SAXException If there is an error
661      * writing the whitespace, or if a handler further down
662      * the filter chain raises an exception.
663      * @see org.xml.sax.ContentHandler#ignorableWhitespace
664      */

665     public void ignorableWhitespace (char ch[], int start, int length)
666         throws SAXException JavaDoc
667     {
668         writeEsc(ch, start, length, false);
669         super.ignorableWhitespace(ch, start, length);
670     }
671     
672
673
674     /**
675      * Write a processing instruction.
676      *
677      * Pass the event on down the filter chain for further processing.
678      *
679      * @param target The PI target.
680      * @param data The PI data.
681      * @exception org.xml.sax.SAXException If there is an error
682      * writing the PI, or if a handler further down
683      * the filter chain raises an exception.
684      * @see org.xml.sax.ContentHandler#processingInstruction
685      */

686     public void processingInstruction (String JavaDoc target, String JavaDoc data)
687         throws SAXException JavaDoc
688     {
689         write("<?");
690         write(target);
691         write(' ');
692         write(data);
693         write("?>");
694         if (elementLevel < 1) {
695             write('\n');
696         }
697         super.processingInstruction(target, data);
698     }
699     
700
701
702     ////////////////////////////////////////////////////////////////////
703
// Additional markup.
704
////////////////////////////////////////////////////////////////////
705

706     /**
707      * Write an empty element.
708      *
709      * This method writes an empty element tag rather than a start tag
710      * followed by an end tag. Both a {@link #startElement
711      * startElement} and an {@link #endElement endElement} event will
712      * be passed on down the filter chain.
713      *
714      * @param uri The element's Namespace URI, or the empty string
715      * if the element has no Namespace or if Namespace
716      * processing is not being performed.
717      * @param localName The element's local name (without prefix). This
718      * parameter must be provided.
719      * @param qName The element's qualified name (with prefix), or
720      * the empty string if none is available. This parameter
721      * is strictly advisory: the writer may or may not use
722      * the prefix attached.
723      * @param atts The element's attribute list.
724      * @exception org.xml.sax.SAXException If there is an error
725      * writing the empty tag, or if a handler further down
726      * the filter chain raises an exception.
727      * @see #startElement
728      * @see #endElement
729      */

730     public void emptyElement (String JavaDoc uri, String JavaDoc localName,
731                               String JavaDoc qName, Attributes JavaDoc atts)
732         throws SAXException JavaDoc
733     {
734         nsSupport.pushContext();
735         write('<');
736         writeName(uri, localName, qName, true);
737         writeAttributes(atts);
738         if (elementLevel == 1) {
739             forceNSDecls();
740         }
741         writeNSDecls();
742         write("/>");
743         super.startElement(uri, localName, qName, atts);
744         super.endElement(uri, localName, qName);
745     }
746
747
748
749     ////////////////////////////////////////////////////////////////////
750
// Convenience methods.
751
////////////////////////////////////////////////////////////////////
752

753
754
755     /**
756      * Start a new element without a qname or attributes.
757      *
758      * <p>This method will provide a default empty attribute
759      * list and an empty string for the qualified name.
760      * It invokes {@link
761      * #startElement(String, String, String, Attributes)}
762      * directly.</p>
763      *
764      * @param uri The element's Namespace URI.
765      * @param localName The element's local name.
766      * @exception org.xml.sax.SAXException If there is an error
767      * writing the start tag, or if a handler further down
768      * the filter chain raises an exception.
769      * @see #startElement(String, String, String, Attributes)
770      */

771     public void startElement (String JavaDoc uri, String JavaDoc localName)
772         throws SAXException JavaDoc
773     {
774         startElement(uri, localName, "", EMPTY_ATTS);
775     }
776
777
778     /**
779      * Start a new element without a qname, attributes or a Namespace URI.
780      *
781      * <p>This method will provide an empty string for the
782      * Namespace URI, and empty string for the qualified name,
783      * and a default empty attribute list. It invokes
784      * #startElement(String, String, String, Attributes)}
785      * directly.</p>
786      *
787      * @param localName The element's local name.
788      * @exception org.xml.sax.SAXException If there is an error
789      * writing the start tag, or if a handler further down
790      * the filter chain raises an exception.
791      * @see #startElement(String, String, String, Attributes)
792      */

793     public void startElement (String JavaDoc localName)
794         throws SAXException JavaDoc
795     {
796         startElement("", localName, "", EMPTY_ATTS);
797     }
798
799
800     /**
801      * End an element without a qname.
802      *
803      * <p>This method will supply an empty string for the qName.
804      * It invokes {@link #endElement(String, String, String)}
805      * directly.</p>
806      *
807      * @param uri The element's Namespace URI.
808      * @param localName The element's local name.
809      * @exception org.xml.sax.SAXException If there is an error
810      * writing the end tag, or if a handler further down
811      * the filter chain raises an exception.
812      * @see #endElement(String, String, String)
813      */

814     public void endElement (String JavaDoc uri, String JavaDoc localName)
815         throws SAXException JavaDoc
816     {
817         endElement(uri, localName, "");
818     }
819
820
821     /**
822      * End an element without a Namespace URI or qname.
823      *
824      * <p>This method will supply an empty string for the qName
825      * and an empty string for the Namespace URI.
826      * It invokes {@link #endElement(String, String, String)}
827      * directly.</p>
828      *
829      * @param localName The element's local name.
830      * @exception org.xml.sax.SAXException If there is an error
831      * writing the end tag, or if a handler further down
832      * the filter chain raises an exception.
833      * @see #endElement(String, String, String)
834      */

835     public void endElement (String JavaDoc localName)
836         throws SAXException JavaDoc
837     {
838         endElement("", localName, "");
839     }
840
841
842     /**
843      * Add an empty element without a qname or attributes.
844      *
845      * <p>This method will supply an empty string for the qname
846      * and an empty attribute list. It invokes
847      * {@link #emptyElement(String, String, String, Attributes)}
848      * directly.</p>
849      *
850      * @param uri The element's Namespace URI.
851      * @param localName The element's local name.
852      * @exception org.xml.sax.SAXException If there is an error
853      * writing the empty tag, or if a handler further down
854      * the filter chain raises an exception.
855      * @see #emptyElement(String, String, String, Attributes)
856      */

857     public void emptyElement (String JavaDoc uri, String JavaDoc localName)
858         throws SAXException JavaDoc
859     {
860         emptyElement(uri, localName, "", EMPTY_ATTS);
861     }
862
863
864     /**
865      * Add an empty element without a Namespace URI, qname or attributes.
866      *
867      * <p>This method will supply an empty string for the qname,
868      * and empty string for the Namespace URI, and an empty
869      * attribute list. It invokes
870      * {@link #emptyElement(String, String, String, Attributes)}
871      * directly.</p>
872      *
873      * @param localName The element's local name.
874      * @exception org.xml.sax.SAXException If there is an error
875      * writing the empty tag, or if a handler further down
876      * the filter chain raises an exception.
877      * @see #emptyElement(String, String, String, Attributes)
878      */

879     public void emptyElement (String JavaDoc localName)
880         throws SAXException JavaDoc
881     {
882         emptyElement("", localName, "", EMPTY_ATTS);
883     }
884
885
886     /**
887      * Write an element with character data content.
888      *
889      * <p>This is a convenience method to write a complete element
890      * with character data content, including the start tag
891      * and end tag.</p>
892      *
893      * <p>This method invokes
894      * {@link #startElement(String, String, String, Attributes)},
895      * followed by
896      * {@link #characters(String)}, followed by
897      * {@link #endElement(String, String, String)}.</p>
898      *
899      * @param uri The element's Namespace URI.
900      * @param localName The element's local name.
901      * @param qName The element's default qualified name.
902      * @param atts The element's attributes.
903      * @param content The character data content.
904      * @exception org.xml.sax.SAXException If there is an error
905      * writing the empty tag, or if a handler further down
906      * the filter chain raises an exception.
907      * @see #startElement(String, String, String, Attributes)
908      * @see #characters(String)
909      * @see #endElement(String, String, String)
910      */

911     public void dataElement (String JavaDoc uri, String JavaDoc localName,
912                              String JavaDoc qName, Attributes JavaDoc atts,
913                              String JavaDoc content)
914         throws SAXException JavaDoc
915     {
916         startElement(uri, localName, qName, atts);
917         characters(content);
918         endElement(uri, localName, qName);
919     }
920
921
922     /**
923      * Write an element with character data content but no attributes.
924      *
925      * <p>This is a convenience method to write a complete element
926      * with character data content, including the start tag
927      * and end tag. This method provides an empty string
928      * for the qname and an empty attribute list.</p>
929      *
930      * <p>This method invokes
931      * {@link #startElement(String, String, String, Attributes)},
932      * followed by
933      * {@link #characters(String)}, followed by
934      * {@link #endElement(String, String, String)}.</p>
935      *
936      * @param uri The element's Namespace URI.
937      * @param localName The element's local name.
938      * @param content The character data content.
939      * @exception org.xml.sax.SAXException If there is an error
940      * writing the empty tag, or if a handler further down
941      * the filter chain raises an exception.
942      * @see #startElement(String, String, String, Attributes)
943      * @see #characters(String)
944      * @see #endElement(String, String, String)
945      */

946     public void dataElement (String JavaDoc uri, String JavaDoc localName, String JavaDoc content)
947         throws SAXException JavaDoc
948     {
949         dataElement(uri, localName, "", EMPTY_ATTS, content);
950     }
951
952
953     /**
954      * Write an element with character data content but no attributes or Namespace URI.
955      *
956      * <p>This is a convenience method to write a complete element
957      * with character data content, including the start tag
958      * and end tag. The method provides an empty string for the
959      * Namespace URI, and empty string for the qualified name,
960      * and an empty attribute list.</p>
961      *
962      * <p>This method invokes
963      * {@link #startElement(String, String, String, Attributes)},
964      * followed by
965      * {@link #characters(String)}, followed by
966      * {@link #endElement(String, String, String)}.</p>
967      *
968      * @param localName The element's local name.
969      * @param content The character data content.
970      * @exception org.xml.sax.SAXException If there is an error
971      * writing the empty tag, or if a handler further down
972      * the filter chain raises an exception.
973      * @see #startElement(String, String, String, Attributes)
974      * @see #characters(String)
975      * @see #endElement(String, String, String)
976      */

977     public void dataElement (String JavaDoc localName, String JavaDoc content)
978         throws SAXException JavaDoc
979     {
980         dataElement("", localName, "", EMPTY_ATTS, content);
981     }
982
983
984     /**
985      * Write a string of character data, with XML escaping.
986      *
987      * <p>This is a convenience method that takes an XML
988      * String, converts it to a character array, then invokes
989      * {@link #characters(char[], int, int)}.</p>
990      *
991      * @param data The character data.
992      * @exception org.xml.sax.SAXException If there is an error
993      * writing the string, or if a handler further down
994      * the filter chain raises an exception.
995      * @see #characters(char[], int, int)
996      */

997     public void characters (String JavaDoc data)
998         throws SAXException JavaDoc
999     {
1000        char ch[] = data.toCharArray();
1001        characters(ch, 0, ch.length);
1002    }
1003
1004
1005
1006    ////////////////////////////////////////////////////////////////////
1007
// Internal methods.
1008
////////////////////////////////////////////////////////////////////
1009

1010
1011    /**
1012     * Force all Namespaces to be declared.
1013     *
1014     * This method is used on the root element to ensure that
1015     * the predeclared Namespaces all appear.
1016     */

1017    private void forceNSDecls ()
1018    {
1019        Enumeration JavaDoc prefixes = forcedDeclTable.keys();
1020        while (prefixes.hasMoreElements()) {
1021            String JavaDoc prefix = (String JavaDoc)prefixes.nextElement();
1022            doPrefix(prefix, null, true);
1023        }
1024    }
1025
1026
1027    /**
1028     * Determine the prefix for an element or attribute name.
1029     *
1030     * TODO: this method probably needs some cleanup.
1031     *
1032     * @param uri The Namespace URI.
1033     * @param qName The qualified name (optional); this will be used
1034     * to indicate the preferred prefix if none is currently
1035     * bound.
1036     * @param isElement true if this is an element name, false
1037     * if it is an attribute name (which cannot use the
1038     * default Namespace).
1039     */

1040    private String JavaDoc doPrefix (String JavaDoc uri, String JavaDoc qName, boolean isElement)
1041    {
1042        String JavaDoc defaultNS = nsSupport.getURI("");
1043        if ("".equals(uri)) {
1044            if (isElement && defaultNS != null)
1045                nsSupport.declarePrefix("", "");
1046            return null;
1047        }
1048        String JavaDoc prefix;
1049        if (isElement && defaultNS != null && uri.equals(defaultNS)) {
1050            prefix = "";
1051        } else {
1052            prefix = nsSupport.getPrefix(uri);
1053        }
1054        if (prefix != null) {
1055            return prefix;
1056        }
1057        prefix = (String JavaDoc) doneDeclTable.get(uri);
1058        if (prefix != null &&
1059            ((!isElement || defaultNS != null) &&
1060             "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
1061            prefix = null;
1062        }
1063        if (prefix == null) {
1064            prefix = (String JavaDoc) prefixTable.get(uri);
1065            if (prefix != null &&
1066                ((!isElement || defaultNS != null) &&
1067                 "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
1068                prefix = null;
1069            }
1070        }
1071        if (prefix == null && qName != null && !"".equals(qName)) {
1072            int i = qName.indexOf(':');
1073            if (i == -1) {
1074                if (isElement && defaultNS == null) {
1075                    prefix = "";
1076                }
1077            } else {
1078                prefix = qName.substring(0, i);
1079            }
1080        }
1081        for (;
1082             prefix == null || nsSupport.getURI(prefix) != null;
1083             prefix = "__NS" + ++prefixCounter)
1084            ;
1085        nsSupport.declarePrefix(prefix, uri);
1086        doneDeclTable.put(uri, prefix);
1087        return prefix;
1088    }
1089    
1090
1091    /**
1092     * Write a raw character.
1093     *
1094     * @param c The character to write.
1095     * @exception org.xml.sax.SAXException If there is an error writing
1096     * the character, this method will throw an IOException
1097     * wrapped in a SAXException.
1098     */

1099    private void write (char c)
1100        throws SAXException JavaDoc
1101    {
1102        try {
1103            output.write(c);
1104        } catch (IOException JavaDoc e) {
1105            throw new SAXException JavaDoc(e);
1106        }
1107    }
1108    
1109
1110    /**
1111     * Write a raw string.
1112     *
1113     * @param s
1114     * @exception org.xml.sax.SAXException If there is an error writing
1115     * the string, this method will throw an IOException
1116     * wrapped in a SAXException
1117     */

1118    private void write (String JavaDoc s)
1119    throws SAXException JavaDoc
1120    {
1121        try {
1122            output.write(s);
1123        } catch (IOException JavaDoc e) {
1124            throw new SAXException JavaDoc(e);
1125        }
1126    }
1127
1128
1129    /**
1130     * Write out an attribute list, escaping values.
1131     *
1132     * The names will have prefixes added to them.
1133     *
1134     * @param atts The attribute list to write.
1135     * @exception org.xml.SAXException If there is an error writing
1136     * the attribute list, this method will throw an
1137     * IOException wrapped in a SAXException.
1138     */

1139    private void writeAttributes (Attributes JavaDoc atts)
1140        throws SAXException JavaDoc
1141    {
1142        int len = atts.getLength();
1143        for (int i = 0; i < len; i++) {
1144            char ch[] = atts.getValue(i).toCharArray();
1145            write(' ');
1146            writeName(atts.getURI(i), atts.getLocalName(i),
1147                      atts.getQName(i), false);
1148            write("=\"");
1149            writeEsc(ch, 0, ch.length, true);
1150            write('"');
1151        }
1152    }
1153
1154
1155    /**
1156     * Write an array of data characters with escaping.
1157     *
1158     * @param ch The array of characters.
1159     * @param start The starting position.
1160     * @param length The number of characters to use.
1161     * @param isAttVal true if this is an attribute value literal.
1162     * @exception org.xml.SAXException If there is an error writing
1163     * the characters, this method will throw an
1164     * IOException wrapped in a SAXException.
1165     */

1166    private void writeEsc (char ch[], int start,
1167                             int length, boolean isAttVal)
1168        throws SAXException JavaDoc
1169    {
1170        for (int i = start; i < start + length; i++) {
1171            switch (ch[i]) {
1172            case '&':
1173                write("&amp;");
1174                break;
1175            case '<':
1176                write("&lt;");
1177                break;
1178            case '>':
1179                write("&gt;");
1180                break;
1181            case '\"':
1182                if (isAttVal) {
1183                    write("&quot;");
1184                } else {
1185                    write('\"');
1186                }
1187                break;
1188            default:
1189                if (!unicodeMode && ch[i] > '\u007f') {
1190                    write("&#");
1191                    write(Integer.toString(ch[i]));
1192                    write(';');
1193                } else {
1194                    write(ch[i]);
1195                }
1196            }
1197        }
1198    }
1199
1200
1201    /**
1202     * Write out the list of Namespace declarations.
1203     *
1204     * @exception org.xml.sax.SAXException This method will throw
1205     * an IOException wrapped in a SAXException if
1206     * there is an error writing the Namespace
1207     * declarations.
1208     */

1209    private void writeNSDecls ()
1210        throws SAXException JavaDoc
1211    {
1212        Enumeration JavaDoc prefixes = nsSupport.getDeclaredPrefixes();
1213        while (prefixes.hasMoreElements()) {
1214            String JavaDoc prefix = (String JavaDoc) prefixes.nextElement();
1215            String JavaDoc uri = nsSupport.getURI(prefix);
1216            if (uri == null) {
1217                uri = "";
1218            }
1219            char ch[] = uri.toCharArray();
1220            write(' ');
1221            if ("".equals(prefix)) {
1222                write("xmlns=\"");
1223            } else {
1224                write("xmlns:");
1225                write(prefix);
1226                write("=\"");
1227            }
1228            writeEsc(ch, 0, ch.length, true);
1229            write('\"');
1230        }
1231    }
1232    
1233
1234    /**
1235     * Write an element or attribute name.
1236     *
1237     * @param uri The Namespace URI.
1238     * @param localName The local name.
1239     * @param qName The prefixed name, if available, or the empty string.
1240     * @param isElement true if this is an element name, false if it
1241     * is an attribute name.
1242     * @exception org.xml.sax.SAXException This method will throw an
1243     * IOException wrapped in a SAXException if there is
1244     * an error writing the name.
1245     */

1246    private void writeName (String JavaDoc uri, String JavaDoc localName,
1247                              String JavaDoc qName, boolean isElement)
1248        throws SAXException JavaDoc
1249    {
1250        String JavaDoc prefix = doPrefix(uri, qName, isElement);
1251        if (prefix != null && !"".equals(prefix)) {
1252            write(prefix);
1253            write(':');
1254        }
1255        if (localName != null && !"".equals(localName)) {
1256            write(localName);
1257        } else {
1258            int i = qName.indexOf(':');
1259            write(qName.substring(i + 1, qName.length()));
1260        }
1261    }
1262
1263
1264
1265    ////////////////////////////////////////////////////////////////////
1266
// Default LexicalHandler implementation
1267
////////////////////////////////////////////////////////////////////
1268

1269    public void comment(char[] ch, int start, int length) throws SAXException JavaDoc
1270    {
1271        write("<!--");
1272        for (int i = start; i < start + length; i++) {
1273                write(ch[i]);
1274                if (ch[i] == '-' && i + 1 <= start + length && ch[i+1] == '-')
1275                        write(' ');
1276                }
1277        write("-->");
1278    }
1279
1280    public void endCDATA() throws SAXException JavaDoc { }
1281    public void endDTD() throws SAXException JavaDoc { }
1282    public void endEntity(String JavaDoc name) throws SAXException JavaDoc { }
1283    public void startCDATA() throws SAXException JavaDoc { }
1284    public void startDTD(String JavaDoc name, String JavaDoc publicid, String JavaDoc systemid) throws SAXException JavaDoc {
1285        if (name == null) return; // can't cope
1286
write("<!DOCTYPE ");
1287        if (systemid == null) systemid = "";
1288        char sysquote = (systemid.indexOf('"') != -1) ? '\'': '"';
1289        write(name);
1290        if (!(publicid == null || "".equals(publicid))) {
1291                char pubquote = (publicid.indexOf('"') != -1) ? '\'': '"';
1292                write(" PUBLIC ");
1293                write(pubquote);
1294                write(publicid);
1295                write(pubquote);
1296                write(' ');
1297                }
1298        else {
1299                write(" SYSTEM ");
1300                }
1301        write(sysquote);
1302        write(systemid);
1303        write(sysquote);
1304        write(">\n");
1305        }
1306
1307    public void startEntity(String JavaDoc name) throws SAXException JavaDoc { }
1308
1309
1310    ////////////////////////////////////////////////////////////////////
1311
// Output properties
1312
////////////////////////////////////////////////////////////////////
1313

1314    public String JavaDoc getOutputProperty(String JavaDoc key) {
1315        return outputProperties.getProperty(key);
1316    }
1317
1318    public void setOutputProperty(String JavaDoc key, String JavaDoc value) {
1319        outputProperties.setProperty(key, value);
1320// System.out.println("%%%% key = [" + key + "] value = [" + value +"]");
1321
if (key.equals(ENCODING)) {
1322            outputEncoding = value;
1323            unicodeMode = value.substring(0, 3).equalsIgnoreCase("utf");
1324// System.out.println("%%%% unicodeMode = " + unicodeMode);
1325
}
1326    if (key.equals(METHOD)) {
1327        htmlMode = value.equals("html");
1328    }
1329// System.out.println("%%%% htmlMode = " + htmlMode);
1330
}
1331    
1332
1333    ////////////////////////////////////////////////////////////////////
1334
// Constants.
1335
////////////////////////////////////////////////////////////////////
1336

1337    private final Attributes JavaDoc EMPTY_ATTS = new AttributesImpl();
1338    public static final String JavaDoc CDATA_SECTION_ELEMENTS =
1339        "cdata-section-elements";
1340    public static final String JavaDoc DOCTYPE_PUBLIC = "doctype-public";
1341    public static final String JavaDoc DOCTYPE_SYSTEM = "doctype-system";
1342    public static final String JavaDoc ENCODING = "encoding";
1343    public static final String JavaDoc INDENT = "indent"; // currently ignored
1344
public static final String JavaDoc MEDIA_TYPE = "media-type"; // currently ignored
1345
public static final String JavaDoc METHOD = "method"; // currently html or xml
1346
public static final String JavaDoc OMIT_XML_DECLARATION = "omit-xml-declaration";
1347    public static final String JavaDoc STANDALONE = "standalone"; // currently ignored
1348
public static final String JavaDoc VERSION = "version";
1349
1350
1351
1352    ////////////////////////////////////////////////////////////////////
1353
// Internal state.
1354
////////////////////////////////////////////////////////////////////
1355

1356    private Hashtable JavaDoc prefixTable;
1357    private Hashtable JavaDoc forcedDeclTable;
1358    private Hashtable JavaDoc doneDeclTable;
1359    private int elementLevel = 0;
1360    private Writer JavaDoc output;
1361    private NamespaceSupport JavaDoc nsSupport;
1362    private int prefixCounter = 0;
1363    private Properties JavaDoc outputProperties;
1364    private boolean unicodeMode = false;
1365    private String JavaDoc outputEncoding = "";
1366    private boolean htmlMode = false;
1367    private boolean cdataElement = false;
1368    
1369}
1370
1371// end of XMLWriter.java
1372
Popular Tags