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 { "<", ">", """, "'", "&" }; 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