1 /* ====================================================================
2 * The Apache Software License, Version 1.1
3 *
4 * Copyright (c) 2001 The Apache Software Foundation. All rights
5 * reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org /)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
25 *
26 * 4. The names "Apache" and "Apache Software Foundation" and
27 * "Apache Commons" must not be used to endorse or promote products
28 * derived from this software without prior written permission. For
29 * written permission, please contact apache@apache.org.
30 *
31 * 5. Products derived from this software may not be called "Apache",
32 * "Apache Turbine", nor may "Apache" appear in their name, without
33 * prior written permission of the Apache Software Foundation.
34 *
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46 * SUCH DAMAGE.
47 * ====================================================================
48 *
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org />.
53 */
54 package org.dbunit.util.xml;
55
56 import java.io.IOException;
57 import java.io.OutputStream;
58 import java.io.OutputStreamWriter;
59 import java.io.UnsupportedEncodingException;
60 import java.io.Writer;
61 import java.util.Stack;
62
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 /**
67 * Makes writing XML much much easier.
68 * Improved from
69 * <a href="http://builder.com.com/article.jhtml?id=u00220020318yan01.htm&page=1&vf=tt">article</a>
70 *
71 * @author <a href="mailto:bayard@apache.org">Henri Yandell</a>
72 * @author <a href="mailto:pete@fingertipsoft.com">Peter Cassetta</a>
73 * @author Last changed by: $Author: gommma $
74 * @version $Revision: 1054 $ $Date: 2009-10-10 00:50:13 +0200 (sab, 10 ott 2009) $
75 * @since 1.0
76 */
77 public class XmlWriter
78 {
79 /**
80 * CDATA start tag: {@value}
81 */
82 public static final String CDATA_START = "<![CDATA[";
83 /**
84 * CDATA end tag: {@value}
85 */
86 public static final String CDATA_END = "]]>";
87
88 /**
89 * Default encoding value which is {@value}
90 */
91 public static final String DEFAULT_ENCODING = "UTF-8";
92
93 /**
94 * Logger for this class
95 */
96 private static final Logger logger = LoggerFactory.getLogger(XmlWriter.class);
97
98 private Writer out; // underlying writer
99 private String encoding; // the encoding to be written into the XML header/metatag
100 private Stack stack = new Stack(); // of xml element names
101 private StringBuffer attrs; // current attribute string
102 private boolean empty; // is the current node empty
103 private boolean closed = true; // is the current node closed...
104
105 private boolean pretty = true; // is pretty printing enabled?
106 /**
107 * was text the last thing output?
108 */
109 private boolean wroteText = false;
110 /**
111 * output this to indent one level when pretty printing
112 */
113 private String indent = " ";
114 /**
115 * output this to end a line when pretty printing
116 */
117 private String newline = "\n";
118
119
120 /**
121 * Create an XmlWriter on top of an existing java.io.Writer.
122 */
123 public XmlWriter(Writer writer)
124 {
125 this(writer, null);
126 }
127
128 /**
129 * Create an XmlWriter on top of an existing java.io.Writer.
130 */
131 public XmlWriter(Writer writer, String encoding)
132 {
133 setWriter(writer, encoding);
134 }
135
136 /**
137 * Create an XmlWriter on top of an existing {@link java.io.OutputStream}.
138 * @param outputStream
139 * @param encoding The encoding to be used for writing to the given output
140 * stream. Can be <code>null</code>. If it is <code>null</code> the
141 * {@link #DEFAULT_ENCODING} is used.
142 * @throws UnsupportedEncodingException
143 * @since 2.4
144 */
145 public XmlWriter(OutputStream outputStream, String encoding)
146 throws UnsupportedEncodingException
147 {
148 if(encoding==null)
149 {
150 encoding = DEFAULT_ENCODING;
151 }
152 OutputStreamWriter writer = new OutputStreamWriter(outputStream, encoding);
153 setWriter(writer, encoding);
154 }
155
156
157 /**
158 * Turn pretty printing on or off.
159 * Pretty printing is enabled by default, but it can be turned off
160 * to generate more compact XML.
161 *
162 * @param enable true to enable, false to disable pretty printing.
163 */
164 public void enablePrettyPrint(boolean enable)
165 {
166 if(logger.isDebugEnabled())
167 logger.debug("enablePrettyPrint(enable={}) - start", String.valueOf(enable));
168
169 this.pretty = enable;
170 }
171
172 /**
173 * Specify the string to prepend to a line for each level of indent.
174 * It is 2 spaces (" ") by default. Some may prefer a single tab ("\t")
175 * or a different number of spaces. Specifying an empty string will turn
176 * off indentation when pretty printing.
177 *
178 * @param indent representing one level of indentation while pretty printing.
179 */
180 public void setIndent(String indent)
181 {
182 logger.debug("setIndent(indent={}) - start", indent);
183
184 this.indent = indent;
185 }
186
187 /**
188 * Specify the string used to terminate each line when pretty printing.
189 * It is a single newline ("\n") by default. Users who need to read
190 * generated XML documents in Windows editors like Notepad may wish to
191 * set this to a carriage return/newline sequence ("\r\n"). Specifying
192 * an empty string will turn off generation of line breaks when pretty
193 * printing.
194 *
195 * @param newline representing the newline sequence when pretty printing.
196 */
197 public void setNewline(String newline)
198 {
199 logger.debug("setNewline(newline={}) - start", newline);
200
201 this.newline = newline;
202 }
203
204 /**
205 * A helper method. It writes out an element which contains only text.
206 *
207 * @param name String name of tag
208 * @param text String of text to go inside the tag
209 */
210 public XmlWriter writeElementWithText(String name, String text) throws IOException
211 {
212 logger.debug("writeElementWithText(name={}, text={}) - start", name, text);
213
214 writeElement(name);
215 writeText(text);
216 return endElement();
217 }
218
219 /**
220 * A helper method. It writes out empty entities.
221 *
222 * @param name String name of tag
223 */
224 public XmlWriter writeEmptyElement(String name) throws IOException
225 {
226 logger.debug("writeEmptyElement(name={}) - start", name);
227
228 writeElement(name);
229 return endElement();
230 }
231
232 /**
233 * Begin to write out an element. Unlike the helper tags, this tag
234 * will need to be ended with the endElement method.
235 *
236 * @param name String name of tag
237 */
238 public XmlWriter writeElement(String name) throws IOException
239 {
240 logger.debug("writeElement(name={}) - start", name);
241
242 return openElement(name);
243 }
244
245 /**
246 * Begin to output an element.
247 *
248 * @param name name of element.
249 */
250 private XmlWriter openElement(String name) throws IOException
251 {
252 logger.debug("openElement(name={}) - start", name);
253
254 boolean wasClosed = this.closed;
255 closeOpeningTag();
256 this.closed = false;
257 if (this.pretty)
258 {
259 // ! wasClosed separates adjacent opening tags by a newline.
260 // this.wroteText makes sure an element embedded within the text of
261 // its parent element begins on a new line, indented to the proper
262 // level. This solves only part of the problem of pretty printing
263 // entities which contain both text and child entities.
264 if (!wasClosed || this.wroteText)
265 {
266 this.out.write(newline);
267 }
268 for (int i = 0; i < this.stack.size(); i++)
269 {
270 this.out.write(indent); // Indent opening tag to proper level
271 }
272 }
273 this.out.write("<");
274 this.out.write(name);
275 stack.add(name);
276 this.empty = true;
277 this.wroteText = false;
278 return this;
279 }
280
281 // close off the opening tag
282 private void closeOpeningTag() throws IOException
283 {
284 logger.debug("closeOpeningTag() - start");
285
286 if (!this.closed)
287 {
288 writeAttributes();
289 this.closed = true;
290 this.out.write(">");
291 }
292 }
293
294 // write out all current attributes
295 private void writeAttributes() throws IOException
296 {
297 logger.debug("writeAttributes() - start");
298
299 if (this.attrs != null)
300 {
301 this.out.write(this.attrs.toString());
302 this.attrs.setLength(0);
303 this.empty = false;
304 }
305 }
306
307 /**
308 * Write an attribute out for the current element.
309 * Any XML characters in the value are escaped.
310 * Currently it does not actually throw the exception, but
311 * the API is set that way for future changes.
312 *
313 * @param attr name of attribute.
314 * @param value value of attribute.
315 * @see #writeAttribute(String, String, boolean)
316 */
317 public XmlWriter writeAttribute(String attr, String value) throws IOException
318 {
319 logger.debug("writeAttribute(attr={}, value={}) - start", attr, value);
320 return this.writeAttribute(attr, value, false);
321 }
322
323 /**
324 * Write an attribute out for the current element.
325 * Any XML characters in the value are escaped.
326 * Currently it does not actually throw the exception, but
327 * the API is set that way for future changes.
328 *
329 * @param attr name of attribute.
330 * @param value value of attribute.
331 * @param literally If the writer should be literally on the given value
332 * which means that meta characters will also be preserved by escaping them.
333 * Mainly preserves newlines and tabs.
334 */
335 public XmlWriter writeAttribute(String attr, String value, boolean literally) throws IOException
336 {
337 if(logger.isDebugEnabled())
338 logger.debug("writeAttribute(attr={}, value={}, literally={}) - start",
339 new Object[] {attr, value, String.valueOf(literally)} );
340
341 if(this.wroteText==true) {
342 throw new IllegalStateException("The text for the current element has already been written. Cannot add attributes afterwards.");
343 }
344 // maintain API
345 if (false) throw new IOException();
346
347 if (this.attrs == null)
348 {
349 this.attrs = new StringBuffer();
350 }
351 this.attrs.append(" ");
352 this.attrs.append(attr);
353 this.attrs.append("=\"");
354 this.attrs.append(escapeXml(value, literally));
355 this.attrs.append("\"");
356 return this;
357 }
358
359 /**
360 * End the current element. This will throw an exception
361 * if it is called when there is not a currently open
362 * element.
363 */
364 public XmlWriter endElement() throws IOException
365 {
366 logger.debug("endElement() - start");
367
368 if (this.stack.empty())
369 {
370 throw new IOException("Called endElement too many times. ");
371 }
372 String name = (String)this.stack.pop();
373 if (name != null)
374 {
375 if (this.empty)
376 {
377 writeAttributes();
378 this.out.write("/>");
379 }
380 else
381 {
382 if (this.pretty && !this.wroteText)
383 {
384 for (int i = 0; i < this.stack.size(); i++)
385 {
386 this.out.write(indent); // Indent closing tag to proper level
387 }
388 }
389 this.out.write("</");
390 this.out.write(name);
391 this.out.write(">");
392 }
393 if (this.pretty)
394 this.out.write(newline); // Add a newline after the closing tag
395 this.empty = false;
396 this.closed = true;
397 this.wroteText = false;
398 }
399 return this;
400 }
401
402 /**
403 * Close this writer. It does not close the underlying
404 * writer, but does throw an exception if there are
405 * as yet unclosed tags.
406 */
407 public void close() throws IOException
408 {
409 logger.debug("close() - start");
410
411 this.out.flush();
412
413 if (!this.stack.empty())
414 {
415 throw new IOException("Tags are not all closed. " +
416 "Possibly, " + this.stack.pop() + " is unclosed. ");
417 }
418 }
419
420 /**
421 * Output body text. Any XML characters are escaped.
422 * @param text The text to be written
423 * @return This writer
424 * @throws IOException
425 * @see #writeText(String, boolean)
426 */
427 public XmlWriter writeText(String text) throws IOException
428 {
429 logger.debug("writeText(text={}) - start", text);
430 return this.writeText(text, false);
431 }
432
433 /**
434 * Output body text. Any XML characters are escaped.
435 * @param text The text to be written
436 * @param literally If the writer should be literally on the given value
437 * which means that meta characters will also be preserved by escaping them.
438 * Mainly preserves newlines and tabs.
439 * @return This writer
440 * @throws IOException
441 */
442 public XmlWriter writeText(String text, boolean literally) throws IOException
443 {
444 if(logger.isDebugEnabled())
445 logger.debug("writeText(text={}, literally={}) - start", text, String.valueOf(literally));
446
447 closeOpeningTag();
448 this.empty = false;
449 this.wroteText = true;
450
451 this.out.write(escapeXml(text, literally));
452 return this;
453 }
454
455 /**
456 * Write out a chunk of CDATA. This helper method surrounds the
457 * passed in data with the CDATA tag.
458 *
459 * @param cdata of CDATA text.
460 */
461 public XmlWriter writeCData(String cdata) throws IOException
462 {
463 logger.debug("writeCData(cdata={}) - start", cdata);
464
465 closeOpeningTag();
466
467 boolean hasAlreadyEnclosingCdata = cdata.startsWith(CDATA_START) && cdata.endsWith(CDATA_END);
468
469 // There may already be CDATA sections inside the data.
470 // But CDATA sections can't be nested - can't have ]]> inside a CDATA section.
471 // (See http://www.w3.org/TR/REC-xml/#NT-CDStart in the W3C specs)
472 // The solutions is to replace any occurrence of "]]>" by "]]]]><![CDATA[>",
473 // so that the top CDATA section is split into many valid CDATA sections (you
474 // can look at the "]]]]>" as if it was an escape sequence for "]]>").
475 if(!hasAlreadyEnclosingCdata) {
476 cdata = cdata.replaceAll(CDATA_END, "]]]]><![CDATA[>");
477 }
478
479 this.empty = false;
480 this.wroteText = true;
481 if(!hasAlreadyEnclosingCdata)
482 this.out.write(CDATA_START);
483 this.out.write(cdata);
484 if(!hasAlreadyEnclosingCdata)
485 this.out.write(CDATA_END);
486 return this;
487 }
488
489 /**
490 * Write out a chunk of comment. This helper method surrounds the
491 * passed in data with the XML comment tag.
492 *
493 * @param comment of text to comment.
494 */
495 public XmlWriter writeComment(String comment) throws IOException
496 {
497 logger.debug("writeComment(comment={}) - start", comment);
498
499 writeChunk("<!-- " + comment + " -->");
500 return this;
501 }
502
503 private void writeChunk(String data) throws IOException
504 {
505 logger.debug("writeChunk(data={}) - start", data);
506
507 closeOpeningTag();
508 this.empty = false;
509 if (this.pretty && !this.wroteText)
510 {
511 for (int i = 0; i < this.stack.size(); i++)
512 {
513 this.out.write(indent);
514 }
515 }
516
517 this.out.write(data);
518
519 if (this.pretty)
520 {
521 this.out.write(newline);
522 }
523 }
524
525 // Two example methods. They should output the same XML:
526 // <person name="fred" age="12"><phone>425343</phone><bob/></person>
527 static public void main(String[] args) throws IOException
528 {
529 logger.debug("main(args={}) - start", args);
530
531 test1();
532 test2();
533 }
534
535 static public void test1() throws IOException
536 {
537 logger.debug("test1() - start");
538
539 Writer writer = new java.io.StringWriter();
540 XmlWriter xmlwriter = new XmlWriter(writer);
541 xmlwriter.writeElement("person").writeAttribute("name", "fred").writeAttribute("age", "12").writeElement("phone").writeText("4254343").endElement().writeElement("friends").writeElement("bob").endElement().writeElement("jim").endElement().endElement().endElement();
542 xmlwriter.close();
543 System.err.println(writer.toString());
544 }
545
546 static public void test2() throws IOException
547 {
548 logger.debug("test2() - start");
549
550 Writer writer = new java.io.StringWriter();
551 XmlWriter xmlwriter = new XmlWriter(writer);
552 xmlwriter.writeComment("Example of XmlWriter running");
553 xmlwriter.writeElement("person");
554 xmlwriter.writeAttribute("name", "fred");
555 xmlwriter.writeAttribute("age", "12");
556 xmlwriter.writeElement("phone");
557 xmlwriter.writeText("4254343");
558 xmlwriter.endElement();
559 xmlwriter.writeComment("Examples of empty tags");
560 // xmlwriter.setDefaultNamespace("test");
561 xmlwriter.writeElement("friends");
562 xmlwriter.writeEmptyElement("bob");
563 xmlwriter.writeEmptyElement("jim");
564 xmlwriter.endElement();
565 xmlwriter.writeElementWithText("foo", "This is an example.");
566 xmlwriter.endElement();
567 xmlwriter.close();
568 System.err.println(writer.toString());
569 }
570
571 ////////////////////////////////////////////////////////////////////////////
572 // Added for DbUnit
573
574 /**
575 * Escapes some meta characters like \n, \r that should be preserved in the XML
576 * so that a reader will not filter out those symbols. This code is modified
577 * from xmlrpc:
578 * https://svn.apache.org/repos/asf/webservices/xmlrpc/branches/XMLRPC_1_2_BRANCH/src/java/org/apache/xmlrpc/XmlWriter.java
579 *
580 * @param str The string to be escaped
581 * @param literally If the writer should be literally on the given value
582 * which means that meta characters will also be preserved by escaping them.
583 * Mainly preserves newlines and carriage returns.
584 * @return The escaped string
585 */
586 private String escapeXml(String str, boolean literally)
587 {
588 logger.debug("escapeXml(str={}, literally={}) - start", str, Boolean.toString(literally));
589
590 char [] block = null;
591 int last = 0;
592 StringBuffer buffer = null;
593 int strLength = str.length();
594 int index = 0;
595
596 for (index=0; index<strLength; index++)
597 {
598 String entity = null;
599 char currentChar = str.charAt(index);
600 switch (currentChar)
601 {
602 case '\t':
603 entity = "	";
604 break;
605 case '\n':
606 if (literally) { entity = "
"; }
607 break;
608 case '\r':
609 if (literally) { entity = "
"; }
610 break;
611 case '&':
612 entity = "&";
613 break;
614 case '<':
615 entity = "<";
616 break;
617 case '>':
618 entity = ">";
619 break;
620 case '\"':
621 entity = """;
622 break;
623 case '\'':
624 entity = "'";
625 break;
626 default:
627 if ((currentChar > 0x7f) || !isValidXmlChar(currentChar))
628 {
629 entity = "&#" + String.valueOf((int) currentChar) + ";";
630 }
631 break;
632 }
633
634 // If we found something to substitute, then copy over previous
635 // data then do the substitution.
636 if (entity != null)
637 {
638 if (block == null)
639 {
640 block = str.toCharArray();
641 }
642 if (buffer == null)
643 {
644 buffer = new StringBuffer();
645 }
646 buffer.append(block, last, index - last);
647 buffer.append(entity);
648 last = index + 1;
649 }
650 }
651
652 // nothing found, just return source
653 if (last == 0)
654 {
655 return str;
656 }
657
658 if (last < strLength)
659 {
660 if (block == null)
661 {
662 block = str.toCharArray();
663 }
664 if (buffer == null)
665 {
666 buffer = new StringBuffer();
667 }
668 buffer.append(block, last, index - last);
669 }
670
671 return buffer.toString();
672 }
673
674 /**
675 * Section 2.2 of the XML spec describes which Unicode code points
676 * are valid in XML:
677 *
678 * <blockquote><code>#x9 | #xA | #xD | [#x20-#xD7FF] |
679 * [#xE000-#xFFFD] | [#x10000-#x10FFFF]</code></blockquote>
680 *
681 * Code points outside this set must be entity encoded to be
682 * represented in XML.
683 *
684 * @param c The character to inspect.
685 * @return Whether the specified character is valid in XML.
686 */
687 private static final boolean isValidXmlChar(char c)
688 {
689 switch (c)
690 {
691 case 0x9:
692 case 0xa: // line feed, '\n'
693 case 0xd: // carriage return, '\r'
694 return true;
695
696 default:
697 return ( (0x20 <= c && c <= 0xd7ff) ||
698 (0xe000 <= c && c <= 0xfffd) ||
699 (0x10000 <= c && c <= 0x10ffff) );
700 }
701 }
702
703 private String replace(String value, String original, String replacement)
704 {
705 if(logger.isDebugEnabled())
706 logger.debug("replace(value=" + value + ", original=" + original + ", replacement=" + replacement
707 + ") - start");
708
709 StringBuffer buffer = null;
710
711 int startIndex = 0;
712 int lastEndIndex = 0;
713 for (; ;)
714 {
715 startIndex = value.indexOf(original, lastEndIndex);
716 if (startIndex == -1)
717 {
718 if (buffer != null)
719 {
720 buffer.append(value.substring(lastEndIndex));
721 }
722 break;
723 }
724
725 if (buffer == null)
726 {
727 buffer = new StringBuffer((int)(original.length() * 1.5));
728 }
729 buffer.append(value.substring(lastEndIndex, startIndex));
730 buffer.append(replacement);
731 lastEndIndex = startIndex + original.length();
732 }
733
734 return buffer == null ? value : buffer.toString();
735 }
736
737 private void setEncoding(String encoding)
738 {
739 logger.debug("setEncoding(encoding={}) - start", encoding);
740
741 if (encoding == null && out instanceof OutputStreamWriter)
742 encoding = ((OutputStreamWriter)out).getEncoding();
743
744 if (encoding != null)
745 {
746 encoding = encoding.toUpperCase();
747
748 // Use official encoding names where we know them,
749 // avoiding the Java-only names. When using common
750 // encodings where we can easily tell if characters
751 // are out of range, we'll escape out-of-range
752 // characters using character refs for safety.
753
754 // I _think_ these are all the main synonyms for these!
755 if ("UTF8".equals(encoding))
756 {
757 encoding = "UTF-8";
758 }
759 else if ("US-ASCII".equals(encoding)
760 || "ASCII".equals(encoding))
761 {
762 // dangerMask = (short)0xff80;
763 encoding = "US-ASCII";
764 }
765 else if ("ISO-8859-1".equals(encoding)
766 || "8859_1".equals(encoding)
767 || "ISO8859_1".equals(encoding))
768 {
769 // dangerMask = (short)0xff00;
770 encoding = "ISO-8859-1";
771 }
772 else if ("UNICODE".equals(encoding)
773 || "UNICODE-BIG".equals(encoding)
774 || "UNICODE-LITTLE".equals(encoding))
775 {
776 encoding = "UTF-16";
777
778 // TODO: UTF-16BE, UTF-16LE ... no BOM; what
779 // release of JDK supports those Unicode names?
780 }
781
782 // if (dangerMask != 0)
783 // stringBuf = new StringBuffer();
784 }
785
786 this.encoding = encoding;
787 }
788
789
790 /**
791 * Resets the handler to write a new text document.
792 *
793 * @param writer XML text is written to this writer.
794 * @param encoding if non-null, and an XML declaration is written,
795 * this is the name that will be used for the character encoding.
796 *
797 * @exception IllegalStateException if the current
798 * document hasn't yet ended (i.e. the output stream {@link #out} is not null)
799 */
800 final public void setWriter(Writer writer, String encoding)
801 {
802 logger.debug("setWriter(writer={}, encoding={}) - start", writer, encoding);
803
804 if (this.out != null)
805 throw new IllegalStateException(
806 "can't change stream in mid course");
807 this.out = writer;
808 if (this.out != null)
809 setEncoding(encoding);
810 // if (!(this.out instanceof BufferedWriter))
811 // this.out = new BufferedWriter(this.out);
812 }
813
814 public XmlWriter writeDeclaration() throws IOException
815 {
816 logger.debug("writeDeclaration() - start");
817
818 if (this.encoding != null)
819 {
820 this.out.write("<?xml version='1.0'");
821 this.out.write(" encoding='" + this.encoding + "'");
822 this.out.write("?>");
823 this.out.write(this.newline);
824 }
825
826 return this;
827 }
828
829 public XmlWriter writeDoctype(String systemId, String publicId) throws IOException
830 {
831 logger.debug("writeDoctype(systemId={}, publicId={}) - start", systemId, publicId);
832
833 if (systemId != null || publicId != null)
834 {
835 this.out.write("<!DOCTYPE dataset");
836
837 if (systemId != null)
838 {
839 this.out.write(" SYSTEM \"");
840 this.out.write(systemId);
841 this.out.write("\"");
842 }
843
844 if (publicId != null)
845 {
846 this.out.write(" PUBLIC \"");
847 this.out.write(publicId);
848 this.out.write("\"");
849 }
850
851 this.out.write(">");
852 this.out.write(this.newline);
853 }
854
855 return this;
856 }
857
858 }