KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > dbunit > util > xml > XmlWriter


1 package org.dbunit.util.xml;
2
3 /* ====================================================================
4  * The Apache Software License, Version 1.1
5  *
6  * Copyright (c) 2001 The Apache Software Foundation. All rights
7  * reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution,
22  * if any, must include the following acknowledgment:
23  * "This product includes software developed by the
24  * Apache Software Foundation (http://www.apache.org /)."
25  * Alternately, this acknowledgment may appear in the software itself,
26  * if and wherever such third-party acknowledgments normally appear.
27  *
28  * 4. The names "Apache" and "Apache Software Foundation" and
29  * "Apache Commons" must not be used to endorse or promote products
30  * derived from this software without prior written permission. For
31  * written permission, please contact apache@apache.org.
32  *
33  * 5. Products derived from this software may not be called "Apache",
34  * "Apache Turbine", nor may "Apache" appear in their name, without
35  * prior written permission of the Apache Software Foundation.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals on behalf of the Apache Software Foundation. For more
53  * information on the Apache Software Foundation, please see
54  * <http://www.apache.org />.
55  */

56
57 import java.io.IOException JavaDoc;
58 import java.io.OutputStreamWriter JavaDoc;
59 import java.io.Writer JavaDoc;
60 import java.util.Stack JavaDoc;
61
62 /**
63  * Makes writing XML much much easier.
64  * Improved from
65  * <a HREF="http://builder.com.com/article.jhtml?id=u00220020318yan01.htm&page=1&vf=tt">article</a>
66  *
67  * @author <a HREF="mailto:bayard@apache.org">Henri Yandell</a>
68  * @author <a HREF="mailto:pete@fingertipsoft.com">Peter Cassetta</a>
69  * @version 1.0
70  */

71 public class XmlWriter
72 {
73
74     private Writer JavaDoc out; // underlying writer
75
private String JavaDoc encoding;
76     private Stack JavaDoc stack = new Stack JavaDoc(); // of xml element names
77
private StringBuffer JavaDoc attrs; // current attribute string
78
private boolean empty; // is the current node empty
79
private boolean closed = true; // is the current node closed...
80

81     private boolean pretty = true; // is pretty printing enabled?
82
private boolean wroteText = false; // was text the last thing output?
83
private String JavaDoc indent = " "; // output this to indent one level when pretty printing
84
private String JavaDoc newline = "\n"; // output this to end a line when pretty printing
85

86     /**
87      * Create an XmlWriter on top of an existing java.io.Writer.
88      */

89     public XmlWriter(Writer JavaDoc writer)
90     {
91         this(writer, null);
92     }
93
94     /**
95      * Create an XmlWriter on top of an existing java.io.Writer.
96      */

97     public XmlWriter(Writer JavaDoc writer, String JavaDoc encoding)
98     {
99         setWriter(writer, encoding);
100     }
101
102     /**
103      * Turn pretty printing on or off.
104      * Pretty printing is enabled by default, but it can be turned off
105      * to generate more compact XML.
106      *
107      * @param enable true to enable, false to disable pretty printing.
108      */

109     public void enablePrettyPrint(boolean enable)
110     {
111         this.pretty = enable;
112     }
113
114     /**
115      * Specify the string to prepend to a line for each level of indent.
116      * It is 2 spaces (" ") by default. Some may prefer a single tab ("\t")
117      * or a different number of spaces. Specifying an empty string will turn
118      * off indentation when pretty printing.
119      *
120      * @param indent representing one level of indentation while pretty printing.
121      */

122     public void setIndent(String JavaDoc indent)
123     {
124         this.indent = indent;
125     }
126
127     /**
128      * Specify the string used to terminate each line when pretty printing.
129      * It is a single newline ("\n") by default. Users who need to read
130      * generated XML documents in Windows editors like Notepad may wish to
131      * set this to a carriage return/newline sequence ("\r\n"). Specifying
132      * an empty string will turn off generation of line breaks when pretty
133      * printing.
134      *
135      * @param newline representing the newline sequence when pretty printing.
136      */

137     public void setNewline(String JavaDoc newline)
138     {
139         this.newline = newline;
140     }
141
142     /**
143      * A helper method. It writes out an element which contains only text.
144      *
145      * @param name String name of tag
146      * @param text String of text to go inside the tag
147      */

148     public XmlWriter writeElementWithText(String JavaDoc name, String JavaDoc text) throws IOException JavaDoc
149     {
150         writeElement(name);
151         writeText(text);
152         return endElement();
153     }
154
155     /**
156      * A helper method. It writes out empty entities.
157      *
158      * @param name String name of tag
159      */

160     public XmlWriter writeEmptyElement(String JavaDoc name) throws IOException JavaDoc
161     {
162         writeElement(name);
163         return endElement();
164     }
165
166     /**
167      * Begin to write out an element. Unlike the helper tags, this tag
168      * will need to be ended with the endElement method.
169      *
170      * @param name String name of tag
171      */

172     public XmlWriter writeElement(String JavaDoc name) throws IOException JavaDoc
173     {
174         return openElement(name);
175     }
176
177     /**
178      * Begin to output an element.
179      *
180      * @param name name of element.
181      */

182     private XmlWriter openElement(String JavaDoc name) throws IOException JavaDoc
183     {
184         boolean wasClosed = this.closed;
185         closeOpeningTag();
186         this.closed = false;
187         if (this.pretty)
188         {
189             // ! wasClosed separates adjacent opening tags by a newline.
190
// this.wroteText makes sure an element embedded within the text of
191
// its parent element begins on a new line, indented to the proper
192
// level. This solves only part of the problem of pretty printing
193
// entities which contain both text and child entities.
194
if (!wasClosed || this.wroteText)
195             {
196                 this.out.write(newline);
197             }
198             for (int i = 0; i < this.stack.size(); i++)
199             {
200                 this.out.write(indent); // Indent opening tag to proper level
201
}
202         }
203         this.out.write("<");
204         this.out.write(name);
205         stack.add(name);
206         this.empty = true;
207         this.wroteText = false;
208         return this;
209     }
210
211     // close off the opening tag
212
private void closeOpeningTag() throws IOException JavaDoc
213     {
214         if (!this.closed)
215         {
216             writeAttributes();
217             this.closed = true;
218             this.out.write(">");
219         }
220     }
221
222     // write out all current attributes
223
private void writeAttributes() throws IOException JavaDoc
224     {
225         if (this.attrs != null)
226         {
227             this.out.write(this.attrs.toString());
228             this.attrs.setLength(0);
229             this.empty = false;
230         }
231     }
232
233     /**
234      * Write an attribute out for the current element.
235      * Any xml characters in the value are escaped.
236      * Currently it does not actually throw the exception, but
237      * the api is set that way for future changes.
238      *
239      * @param attr name of attribute.
240      * @param value value of attribute.
241      */

242     public XmlWriter writeAttribute(String JavaDoc attr, String JavaDoc value) throws IOException JavaDoc
243     {
244
245         // maintain api
246
if (false) throw new IOException JavaDoc();
247
248         if (this.attrs == null)
249         {
250             this.attrs = new StringBuffer JavaDoc();
251         }
252         this.attrs.append(" ");
253         this.attrs.append(attr);
254         this.attrs.append("=\"");
255         this.attrs.append(escapeXml(value));
256         this.attrs.append("\"");
257         return this;
258     }
259
260     /**
261      * End the current element. This will throw an exception
262      * if it is called when there is not a currently open
263      * element.
264      */

265     public XmlWriter endElement() throws IOException JavaDoc
266     {
267         if (this.stack.empty())
268         {
269             throw new IOException JavaDoc("Called endElement too many times. ");
270         }
271         String JavaDoc name = (String JavaDoc)this.stack.pop();
272         if (name != null)
273         {
274             if (this.empty)
275             {
276                 writeAttributes();
277                 this.out.write("/>");
278             }
279             else
280             {
281                 if (this.pretty && !this.wroteText)
282                 {
283                     for (int i = 0; i < this.stack.size(); i++)
284                     {
285                         this.out.write(indent); // Indent closing tag to proper level
286
}
287                 }
288                 this.out.write("</");
289                 this.out.write(name);
290                 this.out.write(">");
291             }
292             if (this.pretty)
293                 this.out.write(newline); // Add a newline after the closing tag
294
this.empty = false;
295             this.closed = true;
296             this.wroteText = false;
297         }
298         return this;
299     }
300
301     /**
302      * Close this writer. It does not close the underlying
303      * writer, but does throw an exception if there are
304      * as yet unclosed tags.
305      */

306     public void close() throws IOException JavaDoc
307     {
308         this.out.flush();
309
310         if (!this.stack.empty())
311         {
312             throw new IOException JavaDoc("Tags are not all closed. " +
313                     "Possibly, " + this.stack.pop() + " is unclosed. ");
314         }
315     }
316
317     /**
318      * Output body text. Any xml characters are escaped.
319      */

320     public XmlWriter writeText(String JavaDoc text) throws IOException JavaDoc
321     {
322         closeOpeningTag();
323         this.empty = false;
324         this.wroteText = true;
325         this.out.write(escapeXml(text));
326         return this;
327     }
328
329     /**
330      * Write out a chunk of CDATA. This helper method surrounds the
331      * passed in data with the CDATA tag.
332      *
333      * @param cdata of CDATA text.
334      */

335     public XmlWriter writeCData(String JavaDoc cdata) throws IOException JavaDoc
336     {
337         closeOpeningTag();
338         this.empty = false;
339         this.wroteText = true;
340         this.out.write("<![CDATA[");
341         this.out.write(cdata);
342         this.out.write("]]>");
343         return this;
344     }
345
346     /**
347      * Write out a chunk of comment. This helper method surrounds the
348      * passed in data with the xml comment tag.
349      *
350      * @param comment of text to comment.
351      */

352     public XmlWriter writeComment(String JavaDoc comment) throws IOException JavaDoc
353     {
354         writeChunk("<!-- " + comment + " -->");
355         return this;
356     }
357
358     private void writeChunk(String JavaDoc data) throws IOException JavaDoc
359     {
360         closeOpeningTag();
361         this.empty = false;
362         if (this.pretty && !this.wroteText)
363         {
364             for (int i = 0; i < this.stack.size(); i++)
365             {
366                 this.out.write(indent);
367             }
368         }
369
370         this.out.write(data);
371
372         if (this.pretty)
373         {
374             this.out.write(newline);
375         }
376     }
377
378     // Two example methods. They should output the same XML:
379
// <person name="fred" age="12"><phone>425343</phone><bob/></person>
380
static public void main(String JavaDoc[] args) throws IOException JavaDoc
381     {
382         test1();
383         test2();
384     }
385
386     static public void test1() throws IOException JavaDoc
387     {
388         Writer JavaDoc writer = new java.io.StringWriter JavaDoc();
389         XmlWriter xmlwriter = new XmlWriter(writer);
390         xmlwriter.writeElement("person").writeAttribute("name", "fred").writeAttribute("age", "12").writeElement("phone").writeText("4254343").endElement().writeElement("friends").writeElement("bob").endElement().writeElement("jim").endElement().endElement().endElement();
391         xmlwriter.close();
392         System.err.println(writer.toString());
393     }
394
395     static public void test2() throws IOException JavaDoc
396     {
397         Writer JavaDoc writer = new java.io.StringWriter JavaDoc();
398         XmlWriter xmlwriter = new XmlWriter(writer);
399         xmlwriter.writeComment("Example of XmlWriter running");
400         xmlwriter.writeElement("person");
401         xmlwriter.writeAttribute("name", "fred");
402         xmlwriter.writeAttribute("age", "12");
403         xmlwriter.writeElement("phone");
404         xmlwriter.writeText("4254343");
405         xmlwriter.endElement();
406         xmlwriter.writeComment("Examples of empty tags");
407 // xmlwriter.setDefaultNamespace("test");
408
xmlwriter.writeElement("friends");
409         xmlwriter.writeEmptyElement("bob");
410         xmlwriter.writeEmptyElement("jim");
411         xmlwriter.endElement();
412         xmlwriter.writeElementWithText("foo", "This is an example.");
413         xmlwriter.endElement();
414         xmlwriter.close();
415         System.err.println(writer.toString());
416     }
417
418     ////////////////////////////////////////////////////////////////////////////
419
// Added for DbUnit
420

421     private String JavaDoc escapeXml(String JavaDoc str)
422     {
423         str = replace(str, "&", "&amp;");
424         str = replace(str, "<", "&lt;");
425         str = replace(str, ">", "&gt;");
426         str = replace(str, "\"", "&quot;");
427         str = replace(str, "'", "&apos;");
428         return str;
429     }
430
431     private String JavaDoc replace(String JavaDoc value, String JavaDoc original, String JavaDoc replacement)
432     {
433         StringBuffer JavaDoc buffer = null;
434
435         int startIndex = 0;
436         int lastEndIndex = 0;
437         for (; ;)
438         {
439             startIndex = value.indexOf(original, lastEndIndex);
440             if (startIndex == -1)
441             {
442                 if (buffer != null)
443                 {
444                     buffer.append(value.substring(lastEndIndex));
445                 }
446                 break;
447             }
448
449             if (buffer == null)
450             {
451                 buffer = new StringBuffer JavaDoc((int)(original.length() * 1.5));
452             }
453             buffer.append(value.substring(lastEndIndex, startIndex));
454             buffer.append(replacement);
455             lastEndIndex = startIndex + original.length();
456         }
457
458         return buffer == null ? value : buffer.toString();
459     }
460
461     private void setEncoding(String JavaDoc encoding)
462     {
463         if (encoding == null && out instanceof OutputStreamWriter JavaDoc)
464             encoding = ((OutputStreamWriter JavaDoc)out).getEncoding();
465
466         if (encoding != null)
467         {
468             encoding = encoding.toUpperCase();
469
470             // Use official encoding names where we know them,
471
// avoiding the Java-only names. When using common
472
// encodings where we can easily tell if characters
473
// are out of range, we'll escape out-of-range
474
// characters using character refs for safety.
475

476             // I _think_ these are all the main synonyms for these!
477
if ("UTF8".equals(encoding))
478             {
479                 encoding = "UTF-8";
480             }
481             else if ("US-ASCII".equals(encoding)
482                     || "ASCII".equals(encoding))
483             {
484 // dangerMask = (short)0xff80;
485
encoding = "US-ASCII";
486             }
487             else if ("ISO-8859-1".equals(encoding)
488                     || "8859_1".equals(encoding)
489                     || "ISO8859_1".equals(encoding))
490             {
491 // dangerMask = (short)0xff00;
492
encoding = "ISO-8859-1";
493             }
494             else if ("UNICODE".equals(encoding)
495                     || "UNICODE-BIG".equals(encoding)
496                     || "UNICODE-LITTLE".equals(encoding))
497             {
498                 encoding = "UTF-16";
499
500                 // TODO: UTF-16BE, UTF-16LE ... no BOM; what
501
// release of JDK supports those Unicode names?
502
}
503
504 // if (dangerMask != 0)
505
// stringBuf = new StringBuffer();
506
}
507
508         this.encoding = encoding;
509     }
510
511
512     /**
513      * Resets the handler to write a new text document.
514      *
515      * @param writer XML text is written to this writer.
516      * @param encoding if non-null, and an XML declaration is written,
517      * this is the name that will be used for the character encoding.
518      *
519      * @exception IllegalStateException if the current
520      * document hasn't yet ended (with {@link #endDocument})
521      */

522     final public void setWriter(Writer JavaDoc writer, String JavaDoc encoding)
523     {
524         if (this.out != null)
525             throw new IllegalStateException JavaDoc(
526                     "can't change stream in mid course");
527         this.out = writer;
528         if (this.out != null)
529             setEncoding(encoding);
530 // if (!(this.out instanceof BufferedWriter))
531
// this.out = new BufferedWriter(this.out);
532
}
533
534     public XmlWriter writeDeclaration() throws IOException JavaDoc
535     {
536         if (this.encoding != null)
537         {
538             this.out.write("<?xml version='1.0'");
539             this.out.write(" encoding='" + this.encoding + "'");
540             this.out.write("?>");
541             this.out.write(this.newline);
542         }
543
544         return this;
545     }
546
547     public XmlWriter writeDoctype(String JavaDoc systemId, String JavaDoc publicId) throws IOException JavaDoc
548     {
549         if (systemId != null || publicId != null)
550         {
551             this.out.write("<!DOCTYPE dataset");
552
553             if (systemId != null)
554             {
555                 this.out.write(" SYSTEM \"");
556                 this.out.write(systemId);
557                 this.out.write("\"");
558             }
559
560             if (publicId != null)
561             {
562                 this.out.write(" PUBLIC \"");
563                 this.out.write(publicId);
564                 this.out.write("\"");
565             }
566
567             this.out.write(">");
568             this.out.write(this.newline);
569         }
570
571         return this;
572     }
573
574 }
575
Popular Tags