1 /**
2  * Copyright (c) 2004-2006 Regents of the University of California.
3   All rights reserved.
4 
5   Redistribution and use in source and binary forms, with or without
6   modification, are permitted provided that the following conditions
7   are met:
8 
9   1. Redistributions of source code must retain the above copyright
10   notice, this list of conditions and the following disclaimer.
11 
12   2. Redistributions in binary form must reproduce the above copyright
13   notice and this list of conditions.
14 
15   3. The name of the University may not be used to endorse or promote products
16   derived from this software without specific prior written permission.
17 
18   THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
19   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21   ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28   SUCH DAMAGE.
29  */
30 
31 package org.jheer;
32 
33 //import java.io.PrintWriter;
34 import java.io.FileWriter;
35 import java.io.IOException;
36 import java.util.ArrayList;
37 
38 /**
39  * Utility class for writing XML files. This class provides convenience
40  * methods for creating XML documents, such as starting and ending
41  * tags, and adding content and comments. This class handles correct
42  * XML formatting and will properly escape text to ensure that the
43  * text remains valid XML.
44  *
45  * <p>To use this class, create a new instance with the desired
46  * [Print]FileWriter to write the XML to. Call the {@link #begin()} or
47  * {@link #begin(String, int)} method when ready to start outputting
48  * XML. Then use the provided methods to generate the XML file.
49  * Finally, call either the {@link #finish()} or {@link #finish(String)}
50  * methods to signal the completion of the file.</p>
51  *
52  * @author <a href="http://jheer.org">jeffrey heer</a>
53  *
54  * Modified to take a FileWriter and now throws IOException.
55  */
56 
57 public class XMLWriter {
58 
59 //    private PrintWriter m_out;
60     private FileWriter m_out;
61     private int m_bias = 0;
62     private int m_tab;
63     private ArrayList m_tagStack = new ArrayList();
64 
65     /**
66      * Create a new XMLWriter.
67      * @param out the  FileWriter to write the XML to
68      */
69 //    public XMLWriter(PrintWriter out) {
XMLWriter(FileWriter out)70     public XMLWriter(FileWriter out) {
71         this(out, 2);
72     }
73 
74     /**
75      * Create a new XMLWriter.
76      * @param out the FileWriter to write the XML to
77      * @param tabLength the number of spaces to use for each
78      *  level of indentation in the XML file
79      */
80 //    public XMLWriter(PrintWriter out, int tabLength) {
XMLWriter(FileWriter out, int tabLength)81     public XMLWriter(FileWriter out, int tabLength) {
82         m_out = out;
83         m_tab = 2;
84     }
85 
86     /**
87      * Write <em>unescaped</em> text into the XML file. To write
88      * escaped text, use the {@link #content(String)} method instead.
89      * @param s the text to write. This String will not be escaped.
90      */
write(String s)91     public void write(String s) throws IOException {
92         m_out.write(s);
93     }
94 
95     /**
96      * Write <em>unescaped</em> text into the XML file, followed by
97      * a newline. To write escaped text, use the {@link #content(String)}
98      * method instead.
99      * @param s the text to write. This String will not be escaped.
100      */
writeln(String s)101     public void writeln(String s) throws IOException {
102         m_out.write(s);
103         m_out.write("\n");
104     }
105 
106     /**
107      * Write a newline into the XML file.
108      */
writeln()109     public void writeln() throws IOException {
110         m_out.write("\n");
111     }
112 
113     /**
114      * Begin the XML document. This must be called before any other
115      * formatting methods. This method writes an XML header into
116      * the top of the output stream.
117      */
begin()118     public void begin() throws IOException {
119         m_out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
120         writeln();
121     }
122 
123     /**
124      * Begin the XML document. This must be called before any other
125      * formatting methods. This method writes an XML header into
126      * the top of the output stream, plus additional header text
127      * provided by the client
128      * @param header header text to insert into the document
129      * @param bias the spacing bias to use for all subsequent indenting
130      */
begin(String header, int bias)131     public void begin(String header, int bias) throws IOException {
132         begin();
133         m_out.write(header);
134         m_bias = bias;
135     }
136 
137     /**
138      * Write a comment in the XML document. The comment will be written
139      * according to the current spacing and followed by a newline.
140      * @param comment the comment text
141      */
comment(String comment)142     public void comment(String comment) throws IOException {
143         spacing();
144         m_out.write("<!-- ");
145         m_out.write(comment);
146         m_out.write(" -->");
147         writeln();
148     }
149 
150     /**
151      * Internal method for writing a tag with attributes.
152      * @param tag the tag name
153      * @param names the names of the attributes
154      * @param values the values of the attributes
155      * @param nattr the number of attributes
156      * @param close true to close the tag, false to leave it
157      * open and adjust the spacing
158      */
tag(String tag, String[] names, String[] values, int nattr, boolean close)159     protected void tag(String tag, String[] names, String[] values,
160             int nattr, boolean close) throws IOException
161     {
162         spacing();
163         m_out.write('<');
164         m_out.write(tag);
165         for ( int i=0; i<nattr; ++i ) {
166             m_out.write(' ');
167             m_out.write(names[i]);
168             m_out.write('=');
169             m_out.write('\"');
170             escapeString(values[i]);
171             m_out.write('\"');
172         }
173         if ( close ) m_out.write('/');
174         m_out.write('>');
175         writeln();
176 
177         if ( !close ) {
178             m_tagStack.add(tag);
179         }
180     }
181 
182     /**
183      * Write a closed tag with attributes. The tag will be followed by a
184      * newline.
185      * @param tag the tag name
186      * @param names the names of the attributes
187      * @param values the values of the attributes
188      * @param nattr the number of attributes
189      */
tag(String tag, String[] names, String[] values, int nattr)190     public void tag(String tag, String[] names, String[] values, int nattr) throws IOException
191     {
192         tag(tag, names, values, nattr, true);
193     }
194 
195     /**
196      * Write a start tag with attributes. The tag will be followed by a
197      * newline, and the indentation level will be increased.
198      * @param tag the tag name
199      * @param names the names of the attributes
200      * @param values the values of the attributes
201      * @param nattr the number of attributes
202      */
start(String tag, String[] names, String[] values, int nattr)203     public void start(String tag, String[] names, String[] values, int nattr) throws IOException
204     {
205         tag(tag, names, values, nattr, false);
206     }
207 
208     /**
209      * Write a new attribut to an existing tag.  The attribute will be followed by a newline.
210      * @param name the name of the attribute
211      * @param value the value of the attribute
212      */
addAttribute(String name, String value)213      public void addAttribute(String name, String value) throws IOException {
214         spacing();
215         m_out.write(name);
216         m_out.write('=');
217         m_out.write('\"');
218         escapeString(value);
219         m_out.write('\"');
220         writeln();
221      }
222 
223      /**
224      * Internal method for writing a tag with a single attribute.
225      * @param tag the tag name
226      * @param name the name of the attribute
227      * @param value the value of the attribute
228      * @param close true to close the tag, false to leave it
229      * open and adjust the spacing
230      */
tag(String tag, String name, String value, boolean close)231     protected void tag(String tag, String name, String value, boolean close) throws IOException {
232         spacing();
233         m_out.write('<');
234         m_out.write(tag);
235         m_out.write(' ');
236         m_out.write(name);
237         m_out.write('=');
238         m_out.write('\"');
239         escapeString(value);
240         m_out.write('\"');
241         if ( close ) m_out.write('/');
242         m_out.write('>');
243         writeln();
244 
245         if ( !close ) {
246             m_tagStack.add(tag);
247         }
248     }
249 
250     /**
251      * Write a closed tag with one attribute. The tag will be followed by a
252      * newline.
253      * @param tag the tag name
254      * @param name the name of the attribute
255      * @param value the value of the attribute
256      */
tag(String tag, String name, String value)257     public void tag(String tag, String name, String value) throws IOException
258     {
259         tag(tag, name, value, true);
260     }
261 
262     /**
263      * Write a start tag with one attribute. The tag will be followed by a
264      * newline, and the indentation level will be increased.
265      * @param tag the tag name
266      * @param name the name of the attribute
267      * @param value the value of the attribute
268      */
start(String tag, String name, String value)269     public void start(String tag, String name, String value) throws IOException
270     {
271         tag(tag, name, value, false);
272     }
273 
274     /**
275      * Internal method for writing a tag with attributes.
276      * @param tag the tag name
277      * @param names the names of the attributes
278      * @param values the values of the attributes
279      * @param nattr the number of attributes
280      * @param close true to close the tag, false to leave it
281      * open and adjust the spacing
282      */
tag(String tag, ArrayList names, ArrayList values, int nattr, boolean close)283     protected void tag(String tag, ArrayList names, ArrayList values,
284             int nattr, boolean close) throws IOException
285     {
286         spacing();
287         m_out.write('<');
288         m_out.write(tag);
289         for ( int i=0; i<nattr; ++i ) {
290             m_out.write(' ');
291             m_out.write((String)names.get(i));
292             m_out.write('=');
293             m_out.write('\"');
294             escapeString((String)values.get(i));
295             m_out.write('\"');
296         }
297         if ( close ) m_out.write('/');
298         m_out.write('>');
299         writeln();
300 
301         if ( !close ) {
302             m_tagStack.add(tag);
303         }
304     }
305 
306     /**
307      * Write a closed tag with attributes. The tag will be followed by a
308      * newline.
309      * @param tag the tag name
310      * @param names the names of the attributes
311      * @param values the values of the attributes
312      * @param nattr the number of attributes
313      */
tag(String tag, ArrayList names, ArrayList values, int nattr)314     public void tag(String tag, ArrayList names, ArrayList values, int nattr) throws IOException
315     {
316         tag(tag, names, values, nattr, true);
317     }
318 
319     /**
320      * Write a start tag with attributes. The tag will be followed by a
321      * newline, and the indentation level will be increased.
322      * @param tag the tag name
323      * @param names the names of the attributes
324      * @param values the values of the attributes
325      * @param nattr the number of attributes
326      */
start(String tag, ArrayList names, ArrayList values, int nattr)327     public void start(String tag, ArrayList names, ArrayList values, int nattr) throws IOException
328     {
329         tag(tag, names, values, nattr, false);
330     }
331 
332     /**
333      * Write a start tag without attributes. The tag will be followed by a
334      * newline, and the indentation level will be increased.
335      * @param tag the tag name
336      */
start(String tag)337     public void start(String tag) throws IOException {
338         tag(tag, (String[])null, null, 0, false);
339     }
340 
341     /**
342      * Close the most recently opened tag. The tag will be followed by a
343      * newline, and the indentation level will be decreased.
344      */
end()345     public void end() throws IOException {
346         String tag = (String)m_tagStack.remove(m_tagStack.size()-1);
347         spacing();
348         m_out.write('<');
349         m_out.write('/');
350         m_out.write(tag);
351         m_out.write('>');
352         writeln();
353     }
354 
355     /**
356      * Write a new content tag with a single attribute, consisting of an
357      * open tag, content text, and a closing tag, all on one line.
358      * @param tag the tag name
359      * @param name the name of the attribute
360      * @param value the value of the attribute, this text will be escaped
361      * @param content the text content, this text will be escaped
362      */
contentTag(String tag, String name, String value, String content)363     public void contentTag(String tag, String name, String value, String content) throws IOException
364     {
365         spacing();
366         m_out.write('<'); m_out.write(tag); m_out.write(' ');
367         m_out.write(name); m_out.write('=');
368         m_out.write('\"'); escapeString(value); m_out.write('\"');
369         m_out.write('>');
370         escapeString(content);
371         m_out.write('<'); m_out.write('/'); m_out.write(tag); m_out.write('>');
372         writeln();
373     }
374 
375     /**
376      * Write a new content tag with no attributes, consisting of an
377      * open tag, content text, and a closing tag, all on one line.
378      * @param tag the tag name
379      * @param content the text content, this text will be escaped
380      */
contentTag(String tag, String content)381     public void contentTag(String tag, String content) throws IOException {
382         spacing();
383         m_out.write('<'); m_out.write(tag); m_out.write('>');
384         escapeString(content);
385         m_out.write('<'); m_out.write('/'); m_out.write(tag); m_out.write('>');
386         writeln();
387     }
388 
389     /**
390      * Write content text.
391      * @param content the content text, this text will be escaped
392      */
content(String content)393     public void content(String content) throws IOException {
394         escapeString(content);
395     }
396 
397     /**
398      * Finish the XML document.
399      */
finish()400     public void finish() throws IOException {
401         m_bias = 0;
402         m_out.flush();
403     }
404 
405     /**
406      * Finish the XML document, writing the given footer text at the
407      * end of the document.
408      * @param footer the footer text, this will not be escaped
409      */
finish(String footer)410     public void finish(String footer) throws IOException {
411         m_bias = 0;
412         m_out.write(footer);
413         m_out.flush();
414     }
415 
416     /**
417      * Write the current spacing (determined by the indentation level)
418      * into the document. This method is used by many of the other
419      * formatting methods, and so should only need to be called in
420      * the case of custom text writing outside the mechanisms
421      * provided by this class.
422      */
spacing()423     public void spacing() throws IOException {
424         int len = m_bias + m_tagStack.size() * m_tab;
425         for ( int i=0; i<len; ++i )
426             m_out.write(' ');
427     }
428 
429     // ------------------------------------------------------------------------
430     // Escape Text
431 
432     // unicode ranges and valid/invalid characters
433     private static final char   LOWER_RANGE = 0x20;
434     private static final char   UPPER_RANGE = 0x7f;
435     private static final char[] VALID_CHARS = { 0x9, 0xA, 0xD };
436 
437     private static final char[] INVALID = { '<', '>', '"', '\'', '&' };
438     private static final String[] VALID =
439         { "&lt;", "&gt;", "&quot;", "&apos;", "&amp;" };
440 
441     /**
442      * Escape a string such that it is safe to use in an XML document.
443      * @param str the string to escape
444      */
escapeString(String str)445     protected void escapeString(String str) throws IOException {
446         if ( str == null ) {
447             m_out.write("null");
448             return;
449         }
450 
451         int len = str.length();
452         for (int i = 0; i < len; ++i) {
453             char c = str.charAt(i);
454 
455             if ( (c < LOWER_RANGE     && c != VALID_CHARS[0] &&
456                   c != VALID_CHARS[1] && c != VALID_CHARS[2])
457                  || (c > UPPER_RANGE) )
458             {
459                 // character out of range, escape with character value
460                 m_out.write("&#");
461                 m_out.write(Integer.toString(c));
462                 m_out.write(';');
463             } else {
464                 boolean valid = true;
465                 // check for invalid characters (e.g., "<", "&", etc)
466                 for (int j=INVALID.length-1; j >= 0; --j )
467                 {
468                     if ( INVALID[j] == c) {
469                         valid = false;
470                         m_out.write(VALID[j]);
471                         break;
472                     }
473                 }
474                 // if character is valid, don't escape
475                 if (valid) {
476                     m_out.write(c);
477                 }
478             }
479         }
480     }
481 
482 } // end of class XMLWriter
483 
484