/** * Copyright (c) 2004-2006 Regents of the University of California. * See "license-prefuse.txt" for licensing terms. */ package prefuse.util.io; import java.io.PrintWriter; import java.util.ArrayList; /** * Utility class for writing XML files. This class provides convenience * methods for creating XML documents, such as starting and ending * tags, and adding content and comments. This class handles correct * XML formatting and will properly escape text to ensure that the * text remains valid XML. * * <p>To use this class, create a new instance with the desired * PrintWriter to write the XML to. Call the {@link #begin()} or * {@link #begin(String, int)} method when ready to start outputting * XML. Then use the provided methods to generate the XML file. * Finally, call either the {@link #finish()} or {@link #finish(String)} * methods to signal the completion of the file.</p> * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class XMLWriter { private PrintWriter m_out; private int m_bias = 0; private int m_tab; private ArrayList m_tagStack = new ArrayList(); /** * Create a new XMLWriter. * @param out the print writer to write the XML to */ public XMLWriter(PrintWriter out) { this(out, 2); } /** * Create a new XMLWriter. * @param out the print writer to write the XML to * @param tabLength the number of spaces to use for each * level of indentation in the XML file */ public XMLWriter(PrintWriter out, int tabLength) { m_out = out; m_tab = 2; } /** * Print <em>unescaped</em> text into the XML file. To print * escaped text, use the {@link #content(String)} method instead. * @param s the text to print. This String will not be escaped. */ public void print(String s) { m_out.print(s); } /** * Print <em>unescaped</em> text into the XML file, followed by * a newline. To print escaped text, use the {@link #content(String)} * method instead. * @param s the text to print. This String will not be escaped. */ public void println(String s) { m_out.print(s); m_out.print("\n"); } /** * Print a newline into the XML file. */ public void println() { m_out.print("\n"); } /** * Begin the XML document. This must be called before any other * formatting methods. This method prints an XML header into * the top of the output stream. */ public void begin() { m_out.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); println(); } /** * Begin the XML document. This must be called before any other * formatting methods. This method prints an XML header into * the top of the output stream, plus additional header text * provided by the client * @param header header text to insert into the document * @param bias the spacing bias to use for all subsequent indenting */ public void begin(String header, int bias) { begin(); m_out.print(header); m_bias = bias; } /** * Print a comment in the XML document. The comment will be printed * according to the current spacing and followed by a newline. * @param comment the comment text */ public void comment(String comment) { spacing(); m_out.print("<!-- "); m_out.print(comment); m_out.print(" -->"); println(); } /** * Internal method for printing a tag with attributes. * @param tag the tag name * @param names the names of the attributes * @param values the values of the attributes * @param nattr the number of attributes * @param close true to close the tag, false to leave it * open and adjust the spacing */ protected void tag(String tag, String[] names, String[] values, int nattr, boolean close) { spacing(); m_out.print('<'); m_out.print(tag); for ( int i=0; i<nattr; ++i ) { m_out.print(' '); m_out.print(names[i]); m_out.print('='); m_out.print('\"'); escapeString(values[i]); m_out.print('\"'); } if ( close ) m_out.print('/'); m_out.print('>'); println(); if ( !close ) { m_tagStack.add(tag); } } /** * Print a closed tag with attributes. The tag will be followed by a * newline. * @param tag the tag name * @param names the names of the attributes * @param values the values of the attributes * @param nattr the number of attributes */ public void tag(String tag, String[] names, String[] values, int nattr) { tag(tag, names, values, nattr, true); } /** * Print a start tag with attributes. The tag will be followed by a * newline, and the indentation level will be increased. * @param tag the tag name * @param names the names of the attributes * @param values the values of the attributes * @param nattr the number of attributes */ public void start(String tag, String[] names, String[] values, int nattr) { tag(tag, names, values, nattr, false); } /** * Internal method for printing a tag with a single attribute. * @param tag the tag name * @param name the name of the attribute * @param value the value of the attribute * @param close true to close the tag, false to leave it * open and adjust the spacing */ protected void tag(String tag, String name, String value, boolean close) { spacing(); m_out.print('<'); m_out.print(tag); m_out.print(' '); m_out.print(name); m_out.print('='); m_out.print('\"'); escapeString(value); m_out.print('\"'); if ( close ) m_out.print('/'); m_out.print('>'); println(); if ( !close ) { m_tagStack.add(tag); } } /** * Print a closed tag with one attribute. The tag will be followed by a * newline. * @param tag the tag name * @param name the name of the attribute * @param value the value of the attribute */ public void tag(String tag, String name, String value) { tag(tag, name, value, true); } /** * Print a start tag with one attribute. The tag will be followed by a * newline, and the indentation level will be increased. * @param tag the tag name * @param name the name of the attribute * @param value the value of the attribute */ public void start(String tag, String name, String value) { tag(tag, name, value, false); } /** * Internal method for printing a tag with attributes. * @param tag the tag name * @param names the names of the attributes * @param values the values of the attributes * @param nattr the number of attributes * @param close true to close the tag, false to leave it * open and adjust the spacing */ protected void tag(String tag, ArrayList names, ArrayList values, int nattr, boolean close) { spacing(); m_out.print('<'); m_out.print(tag); for ( int i=0; i<nattr; ++i ) { m_out.print(' '); m_out.print((String)names.get(i)); m_out.print('='); m_out.print('\"'); escapeString((String)values.get(i)); m_out.print('\"'); } if ( close ) m_out.print('/'); m_out.print('>'); println(); if ( !close ) { m_tagStack.add(tag); } } /** * Print a closed tag with attributes. The tag will be followed by a * newline. * @param tag the tag name * @param names the names of the attributes * @param values the values of the attributes * @param nattr the number of attributes */ public void tag(String tag, ArrayList names, ArrayList values, int nattr) { tag(tag, names, values, nattr, true); } /** * Print a start tag with attributes. The tag will be followed by a * newline, and the indentation level will be increased. * @param tag the tag name * @param names the names of the attributes * @param values the values of the attributes * @param nattr the number of attributes */ public void start(String tag, ArrayList names, ArrayList values, int nattr) { tag(tag, names, values, nattr, false); } /** * Print a start tag without attributes. The tag will be followed by a * newline, and the indentation level will be increased. * @param tag the tag name */ public void start(String tag) { tag(tag, (String[])null, null, 0, false); } /** * Close the most recently opened tag. The tag will be followed by a * newline, and the indentation level will be decreased. */ public void end() { String tag = (String)m_tagStack.remove(m_tagStack.size()-1); spacing(); m_out.print('<'); m_out.print('/'); m_out.print(tag); m_out.print('>'); println(); } /** * Print a new content tag with a single attribute, consisting of an * open tag, content text, and a closing tag, all on one line. * @param tag the tag name * @param name the name of the attribute * @param value the value of the attribute, this text will be escaped * @param content the text content, this text will be escaped */ public void contentTag(String tag, String name, String value, String content) { spacing(); m_out.print('<'); m_out.print(tag); m_out.print(' '); m_out.print(name); m_out.print('='); m_out.print('\"'); escapeString(value); m_out.print('\"'); m_out.print('>'); escapeString(content); m_out.print('<'); m_out.print('/'); m_out.print(tag); m_out.print('>'); println(); } /** * Print a new content tag with no attributes, consisting of an * open tag, content text, and a closing tag, all on one line. * @param tag the tag name * @param content the text content, this text will be escaped */ public void contentTag(String tag, String content) { spacing(); m_out.print('<'); m_out.print(tag); m_out.print('>'); escapeString(content); m_out.print('<'); m_out.print('/'); m_out.print(tag); m_out.print('>'); println(); } /** * Print content text. * @param content the content text, this text will be escaped */ public void content(String content) { escapeString(content); } /** * Finish the XML document. */ public void finish() { m_bias = 0; m_out.flush(); } /** * Finish the XML document, printing the given footer text at the * end of the document. * @param footer the footer text, this will not be escaped */ public void finish(String footer) { m_bias = 0; m_out.print(footer); m_out.flush(); } /** * Print the current spacing (determined by the indentation level) * into the document. This method is used by many of the other * formatting methods, and so should only need to be called in * the case of custom text printing outside the mechanisms * provided by this class. */ public void spacing() { int len = m_bias + m_tagStack.size() * m_tab; for ( int i=0; i<len; ++i ) m_out.print(' '); } // ------------------------------------------------------------------------ // Escape Text // unicode ranges and valid/invalid characters private static final char LOWER_RANGE = 0x20; private static final char UPPER_RANGE = 0x7f; private static final char[] VALID_CHARS = { 0x9, 0xA, 0xD }; private static final char[] INVALID = { '<', '>', '"', '\'', '&' }; private static final String[] VALID = { "<", ">", """, "'", "&" }; /** * Escape a string such that it is safe to use in an XML document. * @param str the string to escape */ protected void escapeString(String str) { if ( str == null ) { m_out.print("null"); return; } int len = str.length(); for (int i = 0; i < len; ++i) { char c = str.charAt(i); if ( (c < LOWER_RANGE && c != VALID_CHARS[0] && c != VALID_CHARS[1] && c != VALID_CHARS[2]) || (c > UPPER_RANGE) ) { // character out of range, escape with character value m_out.print("&#"); m_out.print(Integer.toString(c)); m_out.print(';'); } else { boolean valid = true; // check for invalid characters (e.g., "<", "&", etc) for (int j=INVALID.length-1; j >= 0; --j ) { if ( INVALID[j] == c) { valid = false; m_out.print(VALID[j]); break; } } // if character is valid, don't escape if (valid) { m_out.print(c); } } } } } // end of class XMLWriter