KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > znerd > xmlenc > XMLOutputter


1 /*
2  * $Id: XMLOutputter.java,v 1.113 2004/03/17 21:23:03 znerd Exp $
3  */

4 package org.znerd.xmlenc;
5
6 import java.io.IOException JavaDoc;
7 import java.io.UnsupportedEncodingException JavaDoc;
8 import java.io.Writer JavaDoc;
9
10 /**
11  * Stream-based XML outputter. Instances of this class are able to write XML
12  * output to {@link Writer Writers}.
13  *
14  * <h3>Standards compliance</h3>
15  *
16  * This class is intended to produce output that conforms to the
17  * <a HREF="http://www.w3.org/TR/2000/REC-xml-20001006">XML 1.0
18  * Specification</a>. However, not all applicable restrictions are validated.
19  * For example, it is currently not checked if names contain characters that
20  * are invalid within a <em>Name</em> production.
21  *
22  * <p />Furthermore, not all possible XML documents can be produced. The
23  * following limitations apply:
24  *
25  * <ul>
26  * <li>The name of the applicable encoding is always printed in the XML
27  * declaration, even though it may not be necessary.</li>
28  * <li>The <code>standalone</code> attribute is not supported in the XML
29  * declaration.</li>
30  * <li>Internal DTD subsets are not supported.</li>
31  * <li>Spacing is fixed, whitespace is always kept to the minimum.</li>
32  * </ul>
33  *
34  * <h3>Supported encodings</h3>
35  *
36  * The following encodings are supported:
37  *
38  * <ul>
39  * <li>UTF-8</li>
40  * <li>UTF-16</li>
41  * <li>ISO-10646-UCS-2</li>
42  * <li>ISO-10646-UCS-4</li>
43  * <li>ISO-10646-UTF-1</li>
44  * <li>US-ASCII (also known as ASCII)</li>
45  * <li>ISO-8859-<em>n</em>, where <em>n</em> is the part number</li>
46  * </ul>
47  *
48  * <h3>Multi-threading</h3>
49  *
50  * This class is <em>not</em> thread-safe.
51  *
52  * <h3>Exceptions</h3>
53  *
54  * Note that all methods check the state first and then check the
55  * arguments. This means that if the state is incorrect and the arguments are
56  * incorrect, then an {@link IllegalStateException} will be thrown.
57  *
58  * <p />If any of the writing methods generates an {@link IOException}, then
59  * the state will be set to {@link #ERROR_STATE} and no more output can be
60  * performed.
61  *
62  * <h3>Performance hints</h3>
63  *
64  * It is usually a good idea to let <code>XMLOutputter</code> instances
65  * write to buffered {@link Writer Writers}. This typically improves
66  * performance on large documents or relatively slow or blocking output
67  * streams.
68  *
69  * <p />Instances of this class can be cached in a pool to reduce object
70  * creations. Call {@link #reset()} (with no arguments) when storing an
71  * instance in the pool. Use {@link #reset(Writer,String)} (with 2 arguments)
72  * to re-initialize the instance after fetching it from the pool.
73  *
74  * @version $Revision: 1.113 $ $Date: 2004/03/17 21:23:03 $
75  * @author Ernst de Haan (<a HREF="mailto:znerd@FreeBSD.org">znerd@FreeBSD.org</a>)
76  * @author Jochen Schwoerer (j.schwoerer [at] web.de)
77  *
78  * @since xmlenc 0.19
79  */

80 public class XMLOutputter
81 extends Object JavaDoc
82 implements StatefulXMLEventListener {
83
84    //-------------------------------------------------------------------------
85
// Class functions
86
//-------------------------------------------------------------------------
87

88    //-------------------------------------------------------------------------
89
// Class fields
90
//-------------------------------------------------------------------------
91

92    /**
93     * Default indentation. This is the empty string, <code>""</code>, since by
94     * default no indentation is performed.
95     */

96    public static final String JavaDoc DEFAULT_INDENTATION = "";
97
98
99    //-------------------------------------------------------------------------
100
// Constructor
101
//-------------------------------------------------------------------------
102

103    /**
104     * Constructs a new <code>XMLOutputter</code>. This sets the state to
105     * {@link #UNINITIALIZED}.
106     */

107    public XMLOutputter() {
108       _elementStack = new String JavaDoc[16];
109       _quotationMark = '"';
110    }
111
112    /**
113     * Constructs a new <code>XMLOutputter</code> for the specified
114     * <code>Writer</code> and encoding. This sets the state to
115     * {@link #BEFORE_XML_DECLARATION}.
116     *
117     * <p />The encoding will be stored exactly as passed, leaving the case
118     * intact.
119     *
120     * @param out
121     * the output stream to write to, not <code>null</code>.
122     *
123     * @param encoding
124     * the encoding, not <code>null</code>.
125     *
126     * @throws IllegalStateException
127     * if <code>getState() != {@link #UNINITIALIZED} &amp;&amp;
128     * getState() != {@link #AFTER_ROOT_ELEMENT} &amp;&amp;
129     * getState() != {@link #ERROR_STATE}</code>.
130     *
131     * @throws IllegalArgumentException
132     * if <code>out == null || encoding == null</code>.
133     *
134     * @throws UnsupportedEncodingException
135     * if the specified encoding is not supported.
136     */

137    public XMLOutputter(Writer JavaDoc out, String JavaDoc encoding)
138    throws IllegalStateException JavaDoc,
139           IllegalArgumentException JavaDoc,
140           UnsupportedEncodingException JavaDoc {
141
142       this();
143
144       // Initialize
145
reset(out, encoding);
146    }
147
148
149    /**
150     * Constructs a new <code>XMLOutputter</code> for the specified
151     * <code>Writer</code> and <code>encoder</code>. This sets the state to
152     * {@link #BEFORE_XML_DECLARATION}.
153     *
154     * @param out
155     * the output stream to write to, not <code>null</code>.
156     *
157     * @param encoder
158     * the encoder, not <code>null</code>.
159     *
160     * @throws IllegalStateException
161     * if <code>getState() != {@link #UNINITIALIZED} &amp;&amp;
162     * getState() != {@link #AFTER_ROOT_ELEMENT} &amp;&amp;
163     * getState() != {@link #ERROR_STATE}</code>.
164     *
165     * @throws IllegalArgumentException
166     * if <code>out == null || encoder == null</code>.
167     *
168     * @throws UnsupportedEncodingException
169     * if the specified encoding is not supported.
170     */

171    public XMLOutputter(Writer JavaDoc out, XMLEncoder encoder)
172    throws IllegalStateException JavaDoc,
173           IllegalArgumentException JavaDoc,
174           UnsupportedEncodingException JavaDoc {
175
176       this();
177
178       // Initialize
179
reset(out, encoder);
180    }
181
182    //-------------------------------------------------------------------------
183
// Fields
184
//-------------------------------------------------------------------------
185

186    /**
187     * The output stream this outputter will write to.
188     *
189     * <p>This field is initialized by the constructor. It can never be
190     * <code>null</code>.
191     *
192     * <p />The value of this field is returned by {@link #getWriter()}.
193     */

194    private Writer JavaDoc _out;
195
196    /**
197     * The encoder used to actually encode character streams.
198     */

199    private XMLEncoder _encoder;
200
201    /**
202     * The state of this outputter.
203     */

204    private XMLEventListenerState _state = UNINITIALIZED;
205
206    /**
207     * Stack of open elements.
208     *
209     * <p>This field is initialized by the constructor. It can never be
210     * <code>null</code>.
211     *
212     * @since xmlenc 0.22
213     */

214    private String JavaDoc[] _elementStack;
215
216    /**
217     * The size of the element stack. The actual capacity is
218     * {@link #_elementStack}<code>.length</code>.
219     *
220     * @since xmlenc 0.22
221     */

222    private int _elementStackSize;
223
224    /**
225     * The current quotation mark.
226     *
227     * <p />The value of this field can be set using
228     * {@link #setQuotationMark(char)} and can be retrieved
229     * using {@link #getQuotationMark()}.
230     */

231    private char _quotationMark;
232
233    /**
234     * Flag that indicates if ampersands should be escaped.
235     */

236    private boolean _escapeAmpersands = true;
237
238    /**
239     * The line break that is currently in use. Should never become
240     * <code>null</code>.
241     */

242    private LineBreak _lineBreak = LineBreak.NONE;
243
244    /**
245     * The line break as a char array. Should never become <code>null</code>,
246     * but can be a zero-length array.
247     */

248    private char[] _lineBreakChars = _lineBreak._lineBreakChars;
249
250    /**
251     * The currently used indentation string. Can never become
252     * <code>null</code>.
253     */

254    private String JavaDoc _indentation;
255
256
257    //-------------------------------------------------------------------------
258
// Methods
259
//-------------------------------------------------------------------------
260

261    /**
262     * Checks all invariants. This check should be performed at the end of
263     * every method that changes the internal state of this object.
264     *
265     * @throws Error
266     * if the state of this <code>XMLOutputter</code> is invalid.
267     */

268    private final void checkInvariants()
269    throws Error JavaDoc {
270       // TODO
271
}
272
273    /**
274     * Writes the indentation to the output stream.
275     *
276     * @throws IOException
277     * if an I/O error occurs.
278     */

279    private final void writeIndentation()
280    throws IOException JavaDoc {
281
282       // Write indentation only if there is any
283
if (_indentation.length() != 0) {
284          int count = _elementStackSize - 1;
285          for (int i = 0; i < count; i++) {
286             _out.write(_indentation);
287          }
288       }
289    }
290
291    /**
292     * Returns the output stream this outputter uses.
293     *
294     * @return
295     * the output stream of this encoding, only <code>null</code> if and
296     * only if the state is {@link #UNINITIALIZED}.
297     */

298    public final Writer JavaDoc getWriter() {
299       return _out;
300    }
301
302    /**
303     * Returns the encoding of this outputter.
304     *
305     * @return
306     * the encoding used by this outputter, only <code>null</code> if and
307     * only if the state is {@link #UNINITIALIZED}.
308     */

309    public final String JavaDoc getEncoding() {
310       if (_encoder == null) {
311          return null;
312       } else {
313          return _encoder.getEncoding();
314       }
315    }
316
317    /**
318     * Resets this <code>XMLOutputter</code>. The <code>Writer</code> and the
319     * encoding will be set to <code>null</code>, the element stack will be
320     * cleared, the state will be set to {@link #UNINITIALIZED}, the line break
321     * will be set to {@link LineBreak#NONE} and the indentation will be set to
322     * {@link #DEFAULT_INDENTATION} (an empty string).
323     */

324    public void reset() {
325       _out = null;
326       _encoder = null;
327       _elementStackSize = 0;
328       _state = UNINITIALIZED;
329       _lineBreak = LineBreak.NONE;
330       _lineBreakChars = _lineBreak._lineBreakChars;
331       _indentation = DEFAULT_INDENTATION;
332
333       // State has changed, check
334
checkInvariants();
335    }
336
337    /**
338     * Resets this <code>XMLOutputter</code> and configures it for the
339     * specified output stream. This sets the state to
340     * {@link #BEFORE_XML_DECLARATION} and clears the stack of open elements.
341     *
342     * @param out
343     * the new output stream, cannot be <code>null</code>.
344     *
345     * @throws IllegalArgumentException
346     * if <code>out == null</code>.
347     */

348    private final void reset(Writer JavaDoc out)
349    throws IllegalArgumentException JavaDoc {
350
351       // Check preconditions
352
if (out == null) {
353          throw new IllegalArgumentException JavaDoc("out == null");
354       }
355
356       // Reset the fields
357
_out = out;
358       _state = BEFORE_XML_DECLARATION;
359       _elementStackSize = 0;
360       _lineBreak = LineBreak.NONE;
361       _lineBreakChars = _lineBreak._lineBreakChars;
362       _indentation = DEFAULT_INDENTATION;
363
364       // State has changed, check
365
checkInvariants();
366    }
367
368    /**
369     * Resets this <code>XMLOutputter</code> and configures it for the
370     * specified output stream and encoding. This resets the state to
371     * {@link #BEFORE_XML_DECLARATION} and clears the stack of open elements.
372     *
373     * @param out
374     * the output stream to write to, not <code>null</code>.
375     *
376     * @param encoding
377     * the encoding, not <code>null</code>.
378     *
379     * @throws IllegalArgumentException
380     * if <code>out == null || encoding == null</code>.
381     *
382     * @throws UnsupportedEncodingException
383     * if the specified encoding is not supported.
384     */

385    public final void reset(Writer JavaDoc out, String JavaDoc encoding)
386    throws IllegalArgumentException JavaDoc,
387           UnsupportedEncodingException JavaDoc {
388
389       // Check arguments
390
if (encoding == null) {
391          throw new IllegalArgumentException JavaDoc("encoding == null");
392       }
393
394       reset(out);
395
396       // Store the fields
397
_encoder = XMLEncoder.getEncoder(encoding);
398
399       // State has changed, check
400
checkInvariants();
401    }
402
403    /**
404     * Resets this <code>XMLOutputter</code> and configures it for the
405     * specified output stream and encoder. This resets the state to
406     * {@link #BEFORE_XML_DECLARATION} and clears the stack of open elements.
407     *
408     * @param out
409     * the output stream to write to, not <code>null</code>.
410     *
411     * @param encoder
412     * the encoder, not <code>null</code>.
413     *
414     * @throws IllegalArgumentException
415     * if <code>out == null || encoder == null</code>.
416     *
417     * @throws UnsupportedEncodingException
418     * if the specified encoding is not supported.
419     */

420    public final void reset(Writer JavaDoc out, XMLEncoder encoder)
421    throws IllegalArgumentException JavaDoc,
422           UnsupportedEncodingException JavaDoc {
423
424       // Check arguments
425
if (encoder == null) {
426          throw new IllegalArgumentException JavaDoc("encoder == null");
427       }
428
429       reset(out);
430
431       // Store the fields
432
_encoder = encoder;
433
434       // State has changed, check
435
checkInvariants();
436    }
437
438    /**
439     * Sets the state of this outputter. Normally, it is not necessary to call
440     * this method.
441     *
442     * <p />Calling this method with {@link #UNINITIALIZED} as the state is
443     * equivalent to calling {@link #reset()}.
444     *
445     * <p />Caution: This method can be used to let this class generate invalid
446     * XML.
447     *
448     * @param newState
449     * the new state, not <code>null</code>.
450     *
451     * @param newElementStack
452     * the new element stack, if <code>newState == START_TAG_OPEN
453     * || newState == WITHIN_ELEMENT</code> then it should be
454     * non-<code>null</code> and containing no <code>null</code> elements,
455     * otherwise it must be <code>null</code>.
456     *
457     * @throws IllegalArgumentException
458     * if <code>newState == null
459     * || (newState == {@link #START_TAG_OPEN} &amp;&amp; newElementStack == null)
460     * || (newState == {@link #WITHIN_ELEMENT} &amp;&amp; newElementStack == null)
461     * || (newState != {@link #START_TAG_OPEN} &amp;&amp; newState != {@link #WITHIN_ELEMENT} &amp;&amp; newElementStack != null)
462     * || newElementStack[<i>n</i>] == null</code> (where <code>0 &lt;= <i>n</i> &lt; newElementStack.length</code>).
463     *
464     * @since xmlenc 0.22
465     */

466    public final void setState(XMLEventListenerState newState, String JavaDoc[] newElementStack)
467    throws IllegalArgumentException JavaDoc {
468
469       // Check arguments
470
if (newState == null) {
471          throw new IllegalArgumentException JavaDoc("newState == null");
472       } else if (newState == START_TAG_OPEN && newElementStack == null) {
473          throw new IllegalArgumentException JavaDoc("newState == START_TAG_OPEN && newElementStack == null");
474       } else if (newState == WITHIN_ELEMENT && newElementStack == null) {
475          throw new IllegalArgumentException JavaDoc("newState == WITHIN_ELEMENT && newElementStack == null");
476       } else if (newState != START_TAG_OPEN && newState != WITHIN_ELEMENT && newElementStack != null) {
477          throw new IllegalArgumentException JavaDoc("newState != START_TAG_OPEN && newState != WITHIN_ELEMENT && newElementStack != null");
478       }
479
480       if (newElementStack != null) {
481          for (int i = 0; i < newElementStack.length; i++) {
482             if (newElementStack[i] == null) {
483                throw new IllegalArgumentException JavaDoc("newElementStack[" + i + "] == null");
484             }
485          }
486
487          if (newElementStack.length > _elementStack.length) {
488             try {
489                _elementStack = new String JavaDoc[newElementStack.length + 16];
490             } catch (OutOfMemoryError JavaDoc error) {
491                _elementStack = new String JavaDoc[newElementStack.length];
492             }
493          }
494          System.arraycopy(newElementStack, 0, _elementStack, 0, newElementStack.length);
495       }
496
497       if (newState == UNINITIALIZED) {
498          reset();
499       } else {
500          _state = newState;
501          _elementStackSize = newElementStack == null ? 0 : newElementStack.length;
502       }
503
504       // State has changed, check
505
checkInvariants();
506    }
507
508    /**
509     * Returns the current state of this outputter.
510     *
511     * @return
512     * the current state, cannot be <code>null</code>.
513     */

514    public final XMLEventListenerState getState() {
515       return _state;
516    }
517
518    /**
519     * Checks if escaping is currently enabled. If escaping is enabled, then
520     * all ampersand characters (<code>'&amp;'</code>) are replaced by the
521     * character entity reference <code>"&amp;amp;"</code>. This affects
522     * PCDATA string printing ({@link #pcdata(String)} and
523     * {@link #pcdata(char[],int,int)}) and attribute value printing
524     * ({@link #attribute(String,String)}).
525     *
526     * @return
527     * <code>true</code> if escaping is enabled, <code>false</code>
528     * otherwise.
529     */

530    public final boolean isEscaping() {
531       return _escapeAmpersands;
532    }
533
534    /**
535     * Sets if ampersands should be escaped. This affects PCDATA string
536     * printing ({@link #pcdata(String)} and
537     * {@link #pcdata(char[],int,int)}) and attribute value printing
538     * ({@link #attribute(String,String)}).
539     *
540     * <p />If ampersands are not escaped, then entity references can be
541     * printed.
542     *
543     * @param escapeAmpersands
544     * <code>true</code> if ampersands should be escaped, <code>false</code>
545     * otherwise.
546     *
547     * @since xmlenc 0.24
548     */

549    public final void setEscaping(boolean escapeAmpersands) {
550       _escapeAmpersands = escapeAmpersands;
551
552       // State has changed, check
553
checkInvariants();
554    }
555
556    /**
557     * Returns a copy of the element stack. The returned array will be a new
558     * array. The size of the array will be equal to the element stack size
559     * (see {@link #getElementStackSize()}.
560     *
561     * @return
562     * a newly constructed array that contains all the element types
563     * currently on the element stack, or <code>null</code> if there are no
564     * elements on the stack.
565     *
566     * @since xmlenc 0.22
567     */

568    public final String JavaDoc[] getElementStack() {
569       if (_elementStackSize == 0) {
570          return null;
571       } else {
572          String JavaDoc[] newStack = new String JavaDoc[_elementStackSize];
573          System.arraycopy(_elementStack, 0, newStack, 0, _elementStackSize);
574          return newStack;
575       }
576    }
577
578    /**
579     * Returns the current depth of open elements.
580     *
581     * @return
582     * the open element depth, always &gt;= 0.
583     *
584     * @since xmlenc 0.22
585     */

586    public final int getElementStackSize() {
587       return _elementStackSize;
588    }
589
590    /**
591     * Returns the current capacity for the stack of open elements.
592     *
593     * @return
594     * the open element stack capacity, always &gt;=
595     * {@link #getElementStackSize()}.
596     *
597     * @since xmlenc 0.28
598     */

599    public final int getElementStackCapacity() {
600       return _elementStack.length;
601    }
602
603    /**
604     * Sets the capacity for the stack of open elements. The new capacity must
605     * at least allow the stack to contain the current open elements.
606     *
607     * @param newCapacity
608     * the new capacity, &gt;= {@link #getElementStackSize()}.
609     *
610     * @throws IllegalArgumentException
611     * if <code>newCapacity &lt; {@link #getElementStackSize()}</code>.
612     *
613     * @throws OutOfMemoryError
614     * if a new array cannot be allocated; this object will still be usable,
615     * but the capacity will remain unchanged.
616     */

617    public final void setElementStackCapacity(int newCapacity)
618    throws IllegalArgumentException JavaDoc, OutOfMemoryError JavaDoc {
619
620       // Check argument
621
if (newCapacity < _elementStack.length) {
622          throw new IllegalArgumentException JavaDoc("newCapacity < getElementStackSize()");
623       }
624
625       int currentCapacity = _elementStack.length;
626
627       // Short-circuit if possible
628
if (currentCapacity == newCapacity) {
629          return;
630       }
631
632       String JavaDoc[] newStack = new String JavaDoc[newCapacity];
633       System.arraycopy(_elementStack, 0, newStack, 0, _elementStackSize);
634       _elementStack = newStack;
635
636       // State has changed, check
637
checkInvariants();
638    }
639
640    /**
641     * Sets the quotation mark character to use. This character is printed
642     * before and after an attribute value. It can be either the single or the
643     * double quote character.
644     *
645     * <p />The default quotation mark character is <code>'"'</code>.
646     *
647     * @param c
648     * the character to put around attribute values, either
649     * <code>'\''</code> or <code>'"'</code>.
650     *
651     * @throws IllegalArgumentException
652     * if <code>c != '\'' &amp;&amp; c != '"'</code>.
653     */

654    public final void setQuotationMark(char c)
655    throws IllegalArgumentException JavaDoc {
656
657       // Accept apostrophe and quote
658
if (c == '\'' || c == '"') {
659          _quotationMark = c;
660
661       // Deny any other character
662
} else {
663          throw new IllegalArgumentException JavaDoc("c != '\\'' && c != '\"'");
664       }
665
666       // State has changed, check
667
checkInvariants();
668    }
669
670    /**
671     * Gets the quotation mark character. This character is used to mark the
672     * start and end of an attribute value.
673     *
674     * <p />The default quotation mark character is <code>'"'</code>.
675     *
676     * @return
677     * the character to put around attribute values, either
678     * <code>'\''</code> or <code>'"'</code>.
679     */

680    public final char getQuotationMark() {
681       return _quotationMark;
682    }
683
684    /**
685     * Sets the type of line break to use.
686     *
687     * @param lineBreak
688     * the line break to use; specifying <code>null</code> as the argument
689     * is equivalent to specifying {@link LineBreak#NONE}.
690     */

691    public final void setLineBreak(LineBreak lineBreak) {
692       _lineBreak = lineBreak != null
693                  ? lineBreak
694                  : LineBreak.NONE;
695       _lineBreakChars = _lineBreak._lineBreakChars;
696
697       // State has changed, check
698
checkInvariants();
699    }
700
701    /**
702     * Returns the currently used line break.
703     *
704     * @return
705     * the currently used line break, never <code>null</code>.
706     */

707    public final LineBreak getLineBreak() {
708       return _lineBreak;
709    }
710   
711    /**
712     * Sets the string to be used for indentation.
713     *
714     * @param indentation
715     * the character string used for indentation, or <code>null</code> if
716     * {@link #DEFAULT_INDENTATION the default indentation} should be used.
717     */

718    public final void setIndentation(String JavaDoc indentation) {
719       _indentation = indentation != null
720                    ? indentation
721                    : DEFAULT_INDENTATION;
722
723       // State has changed, check
724
checkInvariants();
725    }
726
727    /**
728     * Returns the string currently used for indentation.
729     *
730     * @return
731     * the character string used for indentation, never <code>null</code>.
732     */

733    public final String JavaDoc getIndentation() {
734       return _indentation;
735    }
736
737    /**
738     * Closes an open start tag.
739     *
740     * @throws IOException
741     * if an I/O error occurs.
742     */

743    private void closeStartTag()
744    throws IOException JavaDoc {
745       _out.write('>');
746    }
747
748    /**
749     * Writes the XML declaration. This method always prints the name of the
750     * encoding. The case of the encoding is as it was specified during
751     * initialization (or re-initialization).
752     *
753     * <p />If the encoding is set to <code>"ISO-8859-1"</code>, then this
754     * method will produce the following output:
755     *
756     * <blockquote><code>&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;</code></blockquote>
757     *
758     * @throws IllegalStateException
759     * if <code>getState() != BEFORE_XML_DECLARATION</code>.
760     *
761     * @throws IOException
762     * if an I/O error occurs; this will set the state to
763     * {@link #ERROR_STATE}.
764     */

765    public final void declaration() throws IllegalStateException JavaDoc, IOException JavaDoc {
766
767       // Check state
768
if (_state != BEFORE_XML_DECLARATION) {
769          throw new IllegalStateException JavaDoc("getState() == " + _state);
770       }
771
772       // Temporarily set the state to ERROR_STATE, unless an exception is
773
// thrown in the write methods, it will be reset to a valid state
774
_state = ERROR_STATE;
775
776       // Write the output
777
_encoder.declaration(_out);
778
779       // Write the line break
780
_out.write(_lineBreakChars);
781
782       // Change the state
783
_state = BEFORE_DTD_DECLARATION;
784
785       // State has changed, check
786
checkInvariants();
787    }
788
789    /**
790     * Writes a document type declaration.
791     *
792     * <p />An external subset can be specified using either a
793     * <em>system identifier</em> (alone), or using both a
794     * <em>public identifier</em> and a <em>system identifier</em>. It can
795     * never be specified using a <em>public identifier</em> alone.
796     *
797     * <p />For example, for XHTML 1.0 the public identifier is:
798     *
799     * <blockquote><code>-//W3C//DTD XHTML 1.0 Transitional//EN</code></blockquote>
800     *
801     * <p />while the system identifier is:
802     *
803     * <blockquote><code>http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</code></blockquote>
804     *
805     * <p />The output is typically similar to this:
806     *
807     * <blockquote><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;</code></blockquote>
808     *
809     * or alternatively, if only the <em>system identifier</em> is specified:
810     *
811     * <blockquote><code>&lt;!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;</code></blockquote>
812     *
813     * @param name
814     * the name of the document type, not <code>null</code>.
815     *
816     * @param publicID
817     * the public identifier, can be <code>null</code>.
818     *
819     * @param systemID
820     * the system identifier, can be <code>null</code>, but otherwise
821     * it should be a properly formatted URL, see
822     * <a HREF="http://www.w3.org/TR/2000/REC-xml-20001006#sec-external-ent">section 4.2.2 External Entities</a>
823     * in the XML 1.0 Specification.
824     *
825     * @throws IllegalStateException
826     * if <code>getState() != {@link #BEFORE_XML_DECLARATION} &amp;&amp;
827     * getState() != {@link #BEFORE_DTD_DECLARATION}</code>.
828     *
829     * @throws IllegalArgumentException
830     * if <code>name == null ||
831     * (publicID != null &amp;&amp; systemID == null)</code>.
832     *
833     * @throws InvalidXMLException
834     * if the specified name does not match the
835     * <a HREF="http://www.w3.org/TR/REC-xml#NT-Name"><em>Name</em> production</a>
836     * (see {@link XMLChecker#checkName(String)}).
837     *
838     * @throws IOException
839     * if an I/O error occurs; this will set the state to
840     * {@link #ERROR_STATE}.
841     */

842    public final void dtd(String JavaDoc name, String JavaDoc publicID, String JavaDoc systemID)
843    throws IllegalStateException JavaDoc,
844           IllegalArgumentException JavaDoc,
845           InvalidXMLException,
846           IOException JavaDoc {
847
848       // Check state
849
if (_state != BEFORE_XML_DECLARATION
850        && _state != BEFORE_DTD_DECLARATION) {
851          throw new IllegalStateException JavaDoc("getState() == " + _state);
852       }
853
854       // Check arguments
855
if (name == null) {
856          throw new IllegalArgumentException JavaDoc("name == null");
857       } else if (publicID != null && systemID == null) {
858          throw new IllegalArgumentException JavaDoc("Found public identifier, but no system identifier.");
859       }
860
861       // Check productions
862
XMLChecker.checkName(name);
863       // TODO: More
864

865       // Temporarily set the state to ERROR_STATE, unless an exception is
866
// thrown in the write methods, it will be reset to a valid state
867
_state = ERROR_STATE;
868
869       // Write the DTD reference
870
_out.write("<!DOCTYPE ");
871       _out.write(name);
872       if (publicID != null) {
873          _out.write(" PUBLIC \"");
874          _out.write(publicID);
875          _out.write('"');
876          _out.write(' ');
877          _out.write('"');
878          _out.write(systemID);
879          _out.write('"');
880       } else if (systemID != null) {
881          _out.write(" SYSTEM \"");
882          _out.write(systemID);
883          _out.write('"');
884       }
885       closeStartTag();
886
887       // Change the state
888
_state = BEFORE_ROOT_ELEMENT;
889
890       // State has changed, check
891
checkInvariants();
892    }
893
894    /**
895     * Writes an element start tag. The element type name will be stored in the
896     * internal element stack. If necessary, the capacity of this stack will be
897     * extended.
898     *
899     * @param type
900     * the type of the tag to start, not <code>null</code>.
901     *
902     * @throws IllegalStateException
903     * if <code>getState() != {@link #BEFORE_XML_DECLARATION} &amp;&amp;
904     * getState() != {@link #BEFORE_DTD_DECLARATION} &amp;&amp;
905     * getState() != {@link #BEFORE_ROOT_ELEMENT} &amp;&amp;
906     * getState() != {@link #START_TAG_OPEN} &amp;&amp;
907     * getState() != {@link #WITHIN_ELEMENT}</code>.
908     *
909     * @throws IllegalArgumentException
910     * if <code>type == null</code>.
911     *
912     * @throws IOException
913     * if an I/O error occurs; this will set the state to
914     * {@link #ERROR_STATE}.
915     */

916    public final void startTag(String JavaDoc type)
917    throws IllegalStateException JavaDoc, IllegalArgumentException JavaDoc, IOException JavaDoc {
918
919       // Check state
920
if (_state != BEFORE_XML_DECLARATION &&
921           _state != BEFORE_DTD_DECLARATION &&
922           _state != BEFORE_ROOT_ELEMENT &&
923           _state != START_TAG_OPEN &&
924           _state != WITHIN_ELEMENT) {
925          throw new IllegalStateException JavaDoc("getState() == " + _state);
926
927       // Check arguments
928
} else if (type == null) {
929          throw new IllegalArgumentException JavaDoc("type == null");
930       }
931
932       boolean startTagOpen = _state == START_TAG_OPEN;
933
934       // Temporarily set the state to ERROR_STATE, unless an exception is
935
// thrown in the write methods, it will be reset to a valid state
936
_state = ERROR_STATE;
937
938       // Increase the stack size with 100% if necessary
939
if (_elementStackSize == _elementStack.length) {
940          String JavaDoc[] newStack;
941          try {
942             newStack = new String JavaDoc[(_elementStackSize + 1) * 2];
943          } catch (OutOfMemoryError JavaDoc error) {
944             newStack = new String JavaDoc[_elementStackSize + 1];
945          }
946          System.arraycopy(_elementStack, 0, newStack, 0, _elementStackSize);
947          _elementStack = newStack;
948       }
949
950       // Store the element type name on the stack
951
_elementStack[_elementStackSize] = type;
952       _elementStackSize++;
953
954       // Close start tag if necessary and then write a raw '<'
955
// followed by the type
956
if (startTagOpen) {
957          _out.write('>');
958       }
959       
960       // Write the line break and the indentation
961
_out.write(_lineBreakChars);
962       writeIndentation();
963
964       _out.write('<');
965
966       // Escape the element name, if necessary
967
_out.write(type);
968
969       // Change the state
970
_state = START_TAG_OPEN;
971
972       // State has changed, check
973
checkInvariants();
974    }
975
976    /**
977     * Adds an attribute to the current element. There must currently be an
978     * open element.
979     *
980     * <p />The attribute value is surrounded by single quotes.
981     *
982     * @param name
983     * the name of the attribute, not <code>null</code>.
984     *
985     * @param value
986     * the value of the attribute, not <code>null</code>.
987     *
988     * @throws IllegalStateException
989     * if <code>getState() != {@link #START_TAG_OPEN}</code>.
990     *
991     * @throws IllegalArgumentException
992     * if <code>name == null || value == null</code>.
993     *
994     * @throws IOException
995     * if an I/O error occurs; this will set the state to
996     * {@link #ERROR_STATE}.
997     */

998    public final void attribute(String JavaDoc name, String JavaDoc value)
999    throws IllegalStateException JavaDoc, IllegalArgumentException JavaDoc, IOException JavaDoc {
1000
1001      // Check state
1002
if (getState() != START_TAG_OPEN) {
1003         throw new IllegalStateException JavaDoc("getState() == " + _state);
1004
1005      // Check arguments
1006
} else if (name == null || value == null) {
1007         if (name == null && value == null) {
1008            throw new IllegalArgumentException JavaDoc("name == null && value == null");
1009         } else if (name == null) {
1010            throw new IllegalArgumentException JavaDoc("name == null");
1011         } else {
1012            throw new IllegalArgumentException JavaDoc("value == null");
1013         }
1014      }
1015
1016      // Temporarily set the state to ERROR_STATE, unless an exception is
1017
// thrown in the write methods, it will be reset to a valid state
1018
_state = ERROR_STATE;
1019
1020      // Write output
1021
_encoder.attribute(_out, name, value, _quotationMark, _escapeAmpersands);
1022
1023      // Reset the state
1024
_state = START_TAG_O