XML Writer
/**
* Copyright (c) 2004-2006 Regents of the University of California.
* See "license-prefuse.txt" for licensing terms.
*/
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
Related examples in the same category