blob: cd811d9e936005f6947583de7c93c0b9090414d7 [file] [log] [blame]
mike ritter6934ac12009-09-22 14:38:51 -07001/**
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
31package org.jheer;
32
33//import java.io.PrintWriter;
34import java.io.FileWriter;
35import java.io.IOException;
36import 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
57public 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) {
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) {
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
393 public void content(String content) throws IOException {
394 escapeString(content);
395 }
396
397 /**
398 * Finish the XML document.
399 */
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 */
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 */
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 */
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