// Copyright 2002-2007, FreeHEP.
package org.freehep.xml.util;
import java.awt.Color;
import java.io.IOException;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Stack;
import org.freehep.util.io.IndentPrintWriter;
/**
* A class that makes it easy to write XML documents.
*
* @author Tony Johnson
* @author Mark Donszelmann
* @version $Id: XMLWriter.java,v 1.3 2008-05-04 12:20:34 murkle Exp $
*/
public class XMLWriter implements XMLTagWriter {
public XMLWriter(Writer w, String indentString, String defaultNameSpace) {
writer = new IndentPrintWriter(w);
writer.setIndentString(indentString);
this.defaultNameSpace = defaultNameSpace;
}
public XMLWriter(Writer w, String indentString) {
this(w, indentString, "");
}
public XMLWriter(Writer w) {
this(w, " "); // By popular demand of Babar
}
/**
* closes the writer
*/
@Override
public void close() throws IOException {
closeDoc();
writer.close();
}
/**
* Opens the document with an xml header
*/
@Override
public void openDoc() {
openDoc("1.0", "", false);
}
/**
* Opens the document with an xml header
*/
@Override
public void openDoc(String version, String encoding, boolean standalone) {
String indentString = writer.getIndentString();
writer.setIndentString(indentString);
closed = false;
if (!XMLCharacterProperties.validVersionNum(version)) {
throw new RuntimeException("Invalid version number: " + version);
}
writer.print("<?xml version=\"");
writer.print(version);
writer.print("\" ");
if ((encoding != null) && (!encoding.equals(""))) {
if (!XMLCharacterProperties.validEncName(encoding)) {
throw new RuntimeException(
"Invalid encoding name: " + encoding);
}
writer.print("encoding=\"");
writer.print(encoding);
writer.print("\" ");
}
if (standalone) {
writer.print("standalone=\"yes\" ");
}
writer.println("?>");
writer.setIndentString(indentString);
}
/**
* Writes a reference to a DTD
*/
@Override
public void referToDTD(String name, String pid, String ref) {
if (dtdName != null) {
throw new RuntimeException("ReferToDTD cannot be called twice");
}
dtdName = name;
writer.println("<!DOCTYPE " + name + " PUBLIC \"" + pid + "\" \"" + ref
+ "\">");
}
/**
* Writes a reference to a DTD
*/
@Override
public void referToDTD(String name, String system) {
if (dtdName != null) {
throw new RuntimeException("ReferToDTD cannot be called twice");
}
dtdName = name;
writer.println("<!DOCTYPE " + name + " SYSTEM \"" + system + "\">");
}
/**
* Closes the document, and checks if you closed all the tags
*/
@Override
public void closeDoc() {
if (!closed) {
if (!openTags.isEmpty()) {
StringBuffer sb = new StringBuffer(
"Not all tags were closed before closing XML document:\n");
while (!openTags.isEmpty()) {
sb.append(" </");
sb.append((String) openTags.pop());
sb.append(">\n");
}
throw new RuntimeException(sb.toString());
}
closed = true;
}
writer.flush();
}
/**
* Print a comment
*/
@Override
public void printComment(String comment) {
if (comment.indexOf("--") >= 0) {
throw new RuntimeException("'--' sequence not allowed in comment");
}
writer.print("<!--");
writer.print(normalizeText(comment));
writer.println("-->");
}
/**
* Prints character data, while escaping < and >
*/
@Override
public void print(String text) {
writer.print(normalizeText(text));
}
/**
* Prints character data, while escaping < and >
*/
public void println(String text) {
print(text);
writer.println();
}
/**
* Prints a new XML tag and increases the identation level
*/
@Override
public void openTag(String namespace, String name) {
if (namespace.equals(defaultNameSpace)) {
openTag(name);
} else {
openTag(namespace + ":" + name);
}
}
/**
* Prints a new XML tag and increases the identation level
*/
@Override
public void openTag(String name) {
checkNameValid(name);
if (openTags.isEmpty() && dtdName != null && !dtdName.equals(name)) {
throw new RuntimeException("First tag: '" + name
+ "' not equal to DTD id: '" + dtdName + "'");
}
writer.print("<" + name);
printAttributes(name.length());
writer.println(">");
writer.indent();
openTags.push(name);
}
/**
* Closes the current XML tag and decreases the indentation level
*/
@Override
public void closeTag() {
if (openTags.isEmpty()) {
writer.close();
throw new RuntimeException("No open tags");
}
Object name = openTags.pop();
writer.outdent();
writer.print("</");
writer.print(name);
writer.println(">");
}
/**
* Prints an empty XML tag.
*/
@Override
public void printTag(String namespace, String name) {
if (namespace.equals(defaultNameSpace)) {
printTag(name);
} else {
printTag(namespace + ":" + name);
}
}
/**
* Prints an empty XML tag.
*/
@Override
public void printTag(String name) {
checkNameValid(name);
writer.print("<" + name);
printAttributes(name.length());
writer.println("/>");
}
/**
* Sets an attribute which will be included in the next tag printed by
* openTag or printTag
*/
@Override
public void setAttribute(String name, String value) {
if ((name != null) && (value != null)) {
attributes.put(name, value);
}
}
@Override
public void setAttribute(String namespace, String name, String value) {
if ((namespace != null) && (name != null)) {
attributes.put(namespace + ":" + name, value);
}
}
@Override
public void setAttribute(String name, byte value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, char value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, long value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, int value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, short value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, boolean value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, float value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, double value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String name, Color value) {
setAttribute(name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, byte value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, char value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, long value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, int value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, short value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, boolean value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, float value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, double value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
@Override
public void setAttribute(String ns, String name, Color value) {
setAttribute(ns + ":" + name, String.valueOf(value));
}
protected void printAttributes(int tagLength) {
int width = tagLength + 1;
boolean extraIndent = false;
Enumeration e = attributes.keys();
while (e.hasMoreElements()) {
String key = e.nextElement().toString();
checkNameValid(key);
String value = normalize(attributes.get(key).toString());
int length = key.length() + value.length() + 3;
if (width > 0 && width + length + 2 * writer.getIndent() > 60) {
width = 0;
writer.println();
if (!extraIndent) {
writer.indent();
extraIndent = true;
}
} else {
width += length;
writer.print(' ');
}
writer.print(key);
writer.print("=\"");
writer.print(value);
writer.print("\"");
}
attributes.clear();
if (extraIndent) {
writer.outdent();
}
}
// /**
// * Prints a DOM node, recursively.
// * No support for a document node
// */
// public void print(Node node)
// {
// if ( node == null ) return;
//
// int type = node.getNodeType();
// switch ( type ) {
// // print document
// case Node.DOCUMENT_NODE:
// throw new RuntimeException("No support for printing nodes of type
// Document");
//
// // print element with attributes
// case Node.ELEMENT_NODE:
// NamedNodeMap attributes = node.getAttributes();
// for ( int i = 0; i < attributes.getLength(); i++ ) {
// Node attr = attributes.item(i);
// setAttribute(attr.getNodeName(), attr.getNodeValue());
// }
// NodeList children = node.getChildNodes();
// if ( children == null ) {
// printTag(node.getNodeName());
// } else {
// openTag(node.getNodeName());
// int len = children.getLength();
// for ( int i = 0; i < len; i++ ) {
// print(children.item(i));
// }
// closeTag();
// }
// break;
//
// // handle entity reference nodes
// case Node.ENTITY_REFERENCE_NODE:
// writer.print('&');
// writer.print(node.getNodeName());
// writer.print(';');
// break;
//
// // print cdata sections
// case Node.CDATA_SECTION_NODE:
// writer.print("<![CDATA[");
// writer.print(node.getNodeValue());
// writer.print("]]>");
// break;
//
// // print text
// case Node.TEXT_NODE:
// print(node.getNodeValue());
// break;
//
// // print processing instruction
// case Node.PROCESSING_INSTRUCTION_NODE:
// writer.print("<?");
// writer.print(node.getNodeName());
// String data = node.getNodeValue();
// if ( data != null && data.length() > 0 ) {
// writer.print(' ');
// writer.print(data);
// }
// writer.print("?>");
// break;
// }
// } // print(Node)
/** Normalizes the given string for an Attribute value */
public static String normalize(String s) {
StringBuffer str = new StringBuffer();
int len = (s != null) ? s.length() : 0;
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
switch (ch) {
case '<': {
str.append("<");
break;
}
case '>': {
str.append(">");
break;
}
case '&': {
str.append("&");
break;
}
case '"': {
str.append(""");
break;
}
case '\r':
case '\n': {
str.append("");
str.append(Integer.toString(ch));
str.append(';');
break;
}
default: {
if (ch > 0x00FF) {
String hex = "0000" + Integer.toHexString(ch);
str.append("");
str.append(hex.substring(hex.length() - 4));
str.append(';');
} else {
str.append(ch);
}
}
}
}
return str.toString();
} // normalize(String):String
/** Normalizes the given string for Text */
public static String normalizeText(String s) {
StringBuffer str = new StringBuffer();
int len = (s != null) ? s.length() : 0;
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
switch (ch) {
case '<': {
str.append("<");
break;
}
case '>': {
str.append(">");
break;
}
case '&': {
str.append("&");
break;
}
default: {
if (ch > 0x007f) {
String hex = "0000" + Integer.toHexString(ch);
str.append("");
str.append(hex.substring(hex.length() - 4));
str.append(';');
} else {
str.append(ch);
}
}
}
}
return str.toString();
}
protected void checkNameValid(String s) {
if (!XMLCharacterProperties.validName(s)) {
throw new RuntimeException("Invalid name: " + s);
}
}
protected boolean closed = true;
private String dtdName = null;
private Hashtable attributes = new Hashtable();
private Stack openTags = new Stack();
protected IndentPrintWriter writer;
protected String defaultNameSpace;
}