KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > webharvest > utils > XMLWriter


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

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

280 public class XMLWriter extends XMLFilterImpl JavaDoc implements LexicalHandler JavaDoc {
281
282     // //////////////////////////////////////////////////////////////////
283
// Constructors.
284
// //////////////////////////////////////////////////////////////////
285

286     /**
287      * Create a new XML writer.
288      *
289      * <p>
290      * Write to standard output.
291      * </p>
292      */

293     public XMLWriter() {
294         init(null);
295     }
296
297     /**
298      * Create a new XML writer.
299      *
300      * <p>
301      * Write to the writer provided.
302      * </p>
303      *
304      * @param writer
305      * The output destination, or null to use standard output.
306      */

307     public XMLWriter(Writer JavaDoc writer) {
308         init(writer);
309     }
310
311     /**
312      * Create a new XML writer.
313      *
314      * <p>
315      * Use the specified XML reader as the parent.
316      * </p>
317      *
318      * @param xmlreader
319      * The parent in the filter chain, or null for no parent.
320      */

321     public XMLWriter(XMLReader JavaDoc xmlreader) {
322         super(xmlreader);
323         init(null);
324     }
325
326     /**
327      * Create a new XML writer.
328      *
329      * <p>
330      * Use the specified XML reader as the parent, and write to the specified
331      * writer.
332      * </p>
333      *
334      * @param xmlreader
335      * The parent in the filter chain, or null for no parent.
336      * @param writer
337      * The output destination, or null to use standard output.
338      */

339     public XMLWriter(XMLReader JavaDoc xmlreader, Writer JavaDoc writer) {
340         super(xmlreader);
341         init(writer);
342     }
343
344     /**
345      * Internal initialization method.
346      *
347      * <p>
348      * All of the public constructors invoke this method.
349      *
350      * @param writer
351      * The output destination, or null to use standard output.
352      */

353     private void init(Writer JavaDoc writer) {
354         setOutput(writer);
355         nsSupport = new NamespaceSupport JavaDoc();
356         prefixTable = new Hashtable JavaDoc();
357         forcedDeclTable = new Hashtable JavaDoc();
358         doneDeclTable = new Hashtable JavaDoc();
359         outputProperties = new Properties JavaDoc();
360     }
361
362     // //////////////////////////////////////////////////////////////////
363
// Public methods.
364
// //////////////////////////////////////////////////////////////////
365

366     /**
367      * Reset the writer.
368      *
369      * <p>
370      * This method is especially useful if the writer throws an exception before
371      * it is finished, and you want to reuse the writer for a new document. It
372      * is usually a good idea to invoke {@link #flush flush} before resetting
373      * the writer, to make sure that no output is lost.
374      * </p>
375      *
376      * <p>
377      * This method is invoked automatically by the
378      * {@link #startDocument startDocument} method before writing a new
379      * document.
380      * </p>
381      *
382      * <p>
383      * <strong>Note:</strong> this method will <em>not</em> clear the prefix
384      * or URI information in the writer or the selected output writer.
385      * </p>
386      *
387      * @see #flush
388      */

389     public void reset() {
390         elementLevel = 0;
391         prefixCounter = 0;
392         nsSupport.reset();
393     }
394
395     /**
396      * Flush the output.
397      *
398      * <p>
399      * This method flushes the output stream. It is especially useful when you
400      * need to make certain that the entire document has been written to output
401      * but do not want to close the output stream.
402      * </p>
403      *
404      * <p>
405      * This method is invoked automatically by the
406      * {@link #endDocument endDocument} method after writing a document.
407      * </p>
408      *
409      * @see #reset
410      */

411     public void flush() throws IOException JavaDoc {
412         output.flush();
413     }
414
415     /**
416      * Set a new output destination for the document.
417      *
418      * @param writer
419      * The output destination, or null to use standard output.
420      * @return The current output writer.
421      * @see #flush
422      */

423     public void setOutput(Writer JavaDoc writer) {
424         if (writer == null) {
425             output = new OutputStreamWriter JavaDoc(System.out);
426         } else {
427             output = writer;
428         }
429     }
430
431     /**
432      * Specify a preferred prefix for a Namespace URI.
433      *
434      * <p>
435      * Note that this method does not actually force the Namespace to be
436      * declared; to do that, use the {@link #forceNSDecl(java.lang.String)
437      * forceNSDecl} method as well.
438      * </p>
439      *
440      * @param uri
441      * The Namespace URI.
442      * @param prefix
443      * The preferred prefix, or "" to select the default Namespace.
444      * @see #getPrefix
445      * @see #forceNSDecl(java.lang.String)
446      * @see #forceNSDecl(java.lang.String,java.lang.String)
447      */

448     public void setPrefix(String JavaDoc uri, String JavaDoc prefix) {
449         prefixTable.put(uri, prefix);
450     }
451
452     /**
453      * Get the current or preferred prefix for a Namespace URI.
454      *
455      * @param uri
456      * The Namespace URI.
457      * @return The preferred prefix, or "" for the default Namespace.
458      * @see #setPrefix
459      */

460     public String JavaDoc getPrefix(String JavaDoc uri) {
461         return (String JavaDoc) prefixTable.get(uri);
462     }
463
464     /**
465      * Force a Namespace to be declared on the root element.
466      *
467      * <p>
468      * By default, the XMLWriter will declare only the Namespaces needed for an
469      * element; as a result, a Namespace may be declared many places in a
470      * document if it is not used on the root element.
471      * </p>
472      *
473      * <p>
474      * This method forces a Namespace to be declared on the root element even if
475      * it is not used there, and reduces the number of xmlns attributes in the
476      * document.
477      * </p>
478      *
479      * @param uri
480      * The Namespace URI to declare.
481      * @see #forceNSDecl(java.lang.String,java.lang.String)
482      * @see #setPrefix
483      */

484     public void forceNSDecl(String JavaDoc uri) {
485         forcedDeclTable.put(uri, Boolean.TRUE);
486     }
487
488     /**
489      * Force a Namespace declaration with a preferred prefix.
490      *
491      * <p>
492      * This is a convenience method that invokes {@link #setPrefix setPrefix}
493      * then {@link #forceNSDecl(java.lang.String) forceNSDecl}.
494      * </p>
495      *
496      * @param uri
497      * The Namespace URI to declare on the root element.
498      * @param prefix
499      * The preferred prefix for the Namespace, or "" for the default
500      * Namespace.
501      * @see #setPrefix
502      * @see #forceNSDecl(java.lang.String)
503      */

504     public void forceNSDecl(String JavaDoc uri, String JavaDoc prefix) {
505         setPrefix(uri, prefix);
506         forceNSDecl(uri);
507     }
508
509     // //////////////////////////////////////////////////////////////////
510
// Methods from org.xml.sax.ContentHandler.
511
// //////////////////////////////////////////////////////////////////
512

513     /**
514      * Write the XML declaration at the beginning of the document.
515      *
516      * Pass the event on down the filter chain for further processing.
517      *
518      * @exception org.xml.sax.SAXException
519      * If there is an error writing the XML declaration, or if a
520      * handler further down the filter chain raises an exception.
521      * @see org.xml.sax.ContentHandler#startDocument
522      */

523     public void startDocument() throws SAXException JavaDoc {
524         reset();
525         if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION,
526                 "no"))))
527             write("<?xml version=\"1.0\" standalone=\"yes\"?>\n\n");
528         super.startDocument();
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
537      * If there is an error writing the newline, or if a handler
538      * further down the filter chain raises an exception.
539      * @see org.xml.sax.ContentHandler#endDocument
540      */

541     public void endDocument() throws SAXException JavaDoc {
542         write('\n');
543         super.endDocument();
544         try {
545             flush();
546         } catch (IOException JavaDoc e) {
547             throw new SAXException JavaDoc(e);
548         }
549     }
550
551     /**
552      * Write a start tag.
553      *
554      * Pass the event on down the filter chain for further processing.
555      *
556      * @param uri
557      * The Namespace URI, or the empty string if none is available.
558      * @param localName
559      * The element's local (unprefixed) name (required).
560      * @param qName
561      * The element's qualified (prefixed) name, or the empty string
562      * is none is available. This method will use the qName as a
563      * template for generating a prefix if necessary, but it is not
564      * guaranteed to use the same qName.
565      * @param atts
566      * The element's attribute list (must not be null).
567      * @exception org.xml.sax.SAXException
568      * If there is an error writing the start tag, or if a
569      * handler further down the filter chain raises an exception.
570      * @see org.xml.sax.ContentHandler#startElement
571      */

572     public void startElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName,
573             Attributes JavaDoc atts) throws SAXException JavaDoc {
574         elementLevel++;
575         nsSupport.pushContext();
576         write('<');
577         writeName(uri, localName, qName, true);
578         writeAttributes(atts);
579         if (elementLevel == 1) {
580             forceNSDecls();
581         }
582         writeNSDecls();
583         write('>');
584         if ("html".equals(outputProperties.getProperty(METHOD, "xml"))
585                 && (localName.equals("script") || localName.equals("style"))) {
586             cdataElement = true;
587         }
588         super.startElement(uri, localName, qName, atts);
589     }
590
591     /**
592      * Write an end tag.
593      *
594      * Pass the event on down the filter chain for further processing.
595      *
596      * @param uri
597      * The Namespace URI, or the empty string if none is available.
598      * @param localName
599      * The element's local (unprefixed) name (required).
600      * @param qName
601      * The element's qualified (prefixed) name, or the empty string
602      * is none is available. This method will use the qName as a
603      * template for generating a prefix if necessary, but it is not
604      * guaranteed to use the same qName.
605      * @exception org.xml.sax.SAXException
606      * If there is an error writing the end tag, or if a handler
607      * further down the filter chain raises an exception.
608      * @see org.xml.sax.ContentHandler#endElement
609      */

610     public void endElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName)
611             throws SAXException JavaDoc {
612         if (!("html".equals(outputProperties.getProperty(METHOD, "xml"))
613                 && uri.equals("http://www.w3.org/1999/xhtml") && (localName
614                 .equals("area")
615                 || localName.equals("base")
616                 || localName.equals("basefont")
617                 || localName.equals("br")
618                 || localName.equals("col")
619                 || localName.equals("frame")
620                 || localName.equals("hr")
621                 || localName.equals("img")
622                 || localName.equals("input")
623                 || localName.equals("isindex")
624                 || localName.equals("link")
625                 || localName.equals("meta") || localName.equals("param")))) {
626             write("</");
627             writeName(uri, localName, qName, true);
628             write('>');
629         }
630         if (elementLevel == 1) {
631             write('\n');
632         }
633         cdataElement = false;
634         super.endElement(uri, localName, qName);
635         nsSupport.popContext();
636         elementLevel--;
637     }
638
639     /**
640      * Write character data.
641      *
642      * Pass the event on down the filter chain for further processing.
643      *
644      * @param ch
645      * The array of characters to write.
646      * @param start
647      * The starting position in the array.
648      * @param length
649      * The number of characters to write.
650      * @exception org.xml.sax.SAXException
651      * If there is an error writing the characters, or if a
652      * handler further down the filter chain raises an exception.
653      * @see org.xml.sax.ContentHandler#characters
654      */

655     public void characters(char ch[], int start, int len) throws SAXException JavaDoc {
656         if (!cdataElement) {
657             writeEsc(ch, start, len, false);
658         } else {
659             for (int i = start; i < start + len; i++) {
660                 write(ch[i]);
661             }
662         }
663         super.characters(ch, start, len);
664     }
665
666     /**
667      * Write ignorable whitespace.
668      *
669      * Pass the event on down the filter chain for further processing.
670      *
671      * @param ch
672      * The array of characters to write.
673      * @param start
674      * The starting position in the array.
675      * @param length
676      * The number of characters to write.
677      * @exception org.xml.sax.SAXException
678      * If there is an error writing the whitespace, or if a
679      * handler further down the filter chain raises an exception.
680      * @see org.xml.sax.ContentHandler#ignorableWhitespace
681      */

682     public void ignorableWhitespace(char ch[], int start, int length)
683             throws SAXException JavaDoc {
684         writeEsc(ch, start, length, false);
685         super.ignorableWhitespace(ch, start, length);
686     }
687
688     /**
689      * Write a processing instruction.
690      *
691      * Pass the event on down the filter chain for further processing.
692      *
693      * @param target
694      * The PI target.
695      * @param data
696      * The PI data.
697      * @exception org.xml.sax.SAXException
698      * If there is an error writing the PI, or if a handler
699      * further down the filter chain raises an exception.
700      * @see org.xml.sax.ContentHandler#processingInstruction
701      */

702     public void processingInstruction(String JavaDoc target, String JavaDoc data)
703             throws SAXException JavaDoc {
704         write("<?");
705         write(target);
706         write(' ');
707         write(data);
708         write("?>");
709         if (elementLevel < 1) {
710             write('\n');
711         }
712         super.processingInstruction(target, data);
713     }
714
715     // //////////////////////////////////////////////////////////////////
716
// Additional markup.
717
// //////////////////////////////////////////////////////////////////
718

719     /**
720      * Write an empty element.
721      *
722      * This method writes an empty element tag rather than a start tag followed
723      * by an end tag. Both a {@link #startElement startElement} and an
724      * {@link #endElement endElement} event will be passed on down the filter
725      * chain.
726      *
727      * @param uri
728      * The element's Namespace URI, or the empty string if the
729      * element has no Namespace or if Namespace processing is not
730      * being performed.
731      * @param localName
732      * The element's local name (without prefix). This parameter must
733      * be provided.
734      * @param qName
735      * The element's qualified name (with prefix), or the empty
736      * string if none is available. This parameter is strictly
737      * advisory: the writer may or may not use the prefix attached.
738      * @param atts
739      * The element's attribute list.
740      * @exception org.xml.sax.SAXException
741      * If there is an error writing the empty tag, or if a
742      * handler further down the filter chain raises an exception.
743      * @see #startElement
744      * @see #endElement
745      */

746     public void emptyElement(String JavaDoc uri, String JavaDoc localName, String JavaDoc qName,
747             Attributes JavaDoc atts) throws SAXException JavaDoc {
748         nsSupport.pushContext();
749         write('<');
750         writeName(uri, localName, qName, true);
751         writeAttributes(atts);
752         if (elementLevel == 1) {
753             forceNSDecls();
754         }
755         writeNSDecls();
756         write("/>");
757         super.startElement(uri, localName, qName, atts);
758         super.endElement(uri, localName, qName);
759     }
760
761     // //////////////////////////////////////////////////////////////////
762
// Convenience methods.
763
// //////////////////////////////////////////////////////////////////
764

765     /**
766      * Start a new element without a qname or attributes.
767      *
768      * <p>
769      * This method will provide a default empty attribute list and an empty
770      * string for the qualified name. It invokes {@link #startElement(String,
771      * String, String, Attributes)} directly.
772      * </p>
773      *
774      * @param uri
775      * The element's Namespace URI.
776      * @param localName
777      * The element's local name.
778      * @exception org.xml.sax.SAXException
779      * If there is an error writing the start tag, or if a
780      * handler further down the filter chain raises an exception.
781      * @see #startElement(String, String, String, Attributes)
782      */

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

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

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

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

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

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

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

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

992     public void dataElement(String JavaDoc localName, String JavaDoc content)
993             throws SAXException JavaDoc {
994         dataElement("", localName, "", EMPTY_ATTS, content);
995     }
996
997     /**
998      * Write a string of character data, with XML escaping.
999      *
1000     * <p>
1001     * This is a convenience method that takes an XML String, converts it to a
1002     * character array, then invokes {@link #characters(char[], int, int)}.
1003     * </p>
1004     *
1005     * @param data
1006     * The character data.
1007     * @exception org.xml.sax.SAXException
1008     * If there is an error writing the string, or if a handler
1009     * further down the filter chain raises an exception.
1010     * @see #characters(char[], int, int)
1011     */

1012    public void characters(String JavaDoc data) throws SAXException JavaDoc {
1013        char ch[] = data.toCharArray();
1014        characters(ch, 0, ch.length);
1015    }
1016
1017    // //////////////////////////////////////////////////////////////////
1018
// Internal methods.
1019
// //////////////////////////////////////////////////////////////////
1020

1021    /**
1022     * Force all Namespaces to be declared.
1023     *
1024     * This method is used on the root element to ensure that the predeclared
1025     * Namespaces all appear.
1026     */

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

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

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

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

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

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

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

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

1261    public void comment(char[] ch, int start, int length) throws SAXException JavaDoc {
1262        write("<!--");
1263        for (int i = start; i < start + length; i++) {
1264            write(ch[i]);
1265            if (ch[i] == '-' && i + 1 <= start + length && ch[i + 1] == '-')
1266                write(' ');
1267        }
1268        write("-->");
1269    }
1270
1271    public void endCDATA() throws SAXException JavaDoc {
1272    }
1273
1274    public void endDTD() throws SAXException JavaDoc {
1275    }
1276
1277    public void endEntity(String JavaDoc name) throws SAXException JavaDoc {
1278    }
1279
1280    public void startCDATA() throws SAXException JavaDoc {
1281    }
1282
1283    public void startDTD(String JavaDoc name, String JavaDoc publicId, String JavaDoc systemId)
1284            throws SAXException JavaDoc {
1285    }
1286
1287    public void startEntity(String JavaDoc name) throws SAXException JavaDoc {
1288    }
1289
1290    // //////////////////////////////////////////////////////////////////
1291
// Output properties
1292
// //////////////////////////////////////////////////////////////////
1293

1294    public String JavaDoc getOutputProperty(String JavaDoc key) {
1295        return outputProperties.getProperty(key);
1296    }
1297
1298    public void setOutputProperty(String JavaDoc key, String JavaDoc value) {
1299        outputProperties.setProperty(key, value);
1300    }
1301
1302    // //////////////////////////////////////////////////////////////////
1303
// Constants.
1304
// //////////////////////////////////////////////////////////////////
1305

1306    private final Attributes JavaDoc EMPTY_ATTS = new AttributesImpl JavaDoc();
1307
1308    public static final String JavaDoc CDATA_SECTION_ELEMENTS = "cdata-section-elements";
1309
1310    public static final String JavaDoc DOCTYPE_PUBLIC = "doctype-public";
1311
1312    public static final String JavaDoc DOCTYPE_SYSTEM = "doctype-system";
1313
1314    public static final String JavaDoc ENCODING = "encoding";
1315
1316    public static final String JavaDoc INDENT = "indent";
1317
1318    public static final String JavaDoc MEDIA_TYPE = "media-type";
1319
1320    public static final String JavaDoc METHOD = "method";
1321
1322    public static final String JavaDoc OMIT_XML_DECLARATION = "omit-xml-declaration";
1323
1324    public static final String JavaDoc STANDALONE = "standalone";
1325
1326    public static final String JavaDoc VERSION = "version";
1327
1328    // //////////////////////////////////////////////////////////////////
1329
// Internal state.
1330
// //////////////////////////////////////////////////////////////////
1331

1332    private Hashtable JavaDoc prefixTable;
1333
1334    private Hashtable JavaDoc forcedDeclTable;
1335
1336    private Hashtable JavaDoc doneDeclTable;
1337
1338    private int elementLevel = 0;
1339
1340    private Writer JavaDoc output;
1341
1342    private NamespaceSupport JavaDoc nsSupport;
1343
1344    private int prefixCounter = 0;
1345
1346    private Properties JavaDoc outputProperties;
1347
1348    private boolean cdataElement = false;
1349
1350}
1351
1352// end of XMLWriter.java
1353
Popular Tags