// Copyright 2002-2007, FreeHEP.
package org.freehep.graphicsbase.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;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* A class that makes it easy to write XML documents.
*
* @author Tony Johnson
* @author Mark Donszelmann
* @version $Id: XMLWriter.java 10527 2007-02-13 17:49:08Z duns $
*/
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
*/
public void close() throws IOException
{
closeDoc();
writer.close();
}
/**
* Opens the document with an xml header
*/
public void openDoc()
{
openDoc("1.0", "", false);
}
/**
* Opens the document with an xml header
*/
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
*/
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
*/
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
*/
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(openTags.pop());
sb.append(">\n");
}
throw new RuntimeException(sb.toString());
}
closed = true;
}
writer.flush();
}
/**
* Print a comment
*/
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 >
*/
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
*/
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
*/
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
*/
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.
*/
public void printTag(String namespace, String name)
{
if (namespace.equals(defaultNameSpace)) {
printTag(name);
} else {
printTag(namespace+":"+name);
}
}
/**
* Prints an empty XML tag.
*/
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
*/
public void setAttribute(String name, String value)
{
if ((name != null) && (value != null)) {
attributes.put(name,value);
}
}
public void setAttribute(String namespace, String name, String value)
{
if ((namespace != null) && (name != null)) {
attributes.put(namespace+":"+name, value);
}
}
public void setAttribute(String name, byte value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, char value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, long value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, int value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, short value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, boolean value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, float value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, double value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String name, Color value) { setAttribute(name, String.valueOf(value)); }
public void setAttribute(String ns, String name, byte value) { setAttribute(ns+":"+name, String.valueOf(value)); }
public void setAttribute(String ns, String name, char value) { setAttribute(ns+":"+name, String.valueOf(value)); }
public void setAttribute(String ns, String name, long value) { setAttribute(ns+":"+name, String.valueOf(value)); }
public void setAttribute(String ns, String name, int value) { setAttribute(ns+":"+name, String.valueOf(value)); }
public void setAttribute(String ns, String name, short value) { setAttribute(ns+":"+name, String.valueOf(value)); }
public void setAttribute(String ns, String name, boolean value) { setAttribute(ns+":"+name, String.valueOf(value)); }
public void setAttribute(String ns, String name, float value) { setAttribute(ns+":"+name, String.valueOf(value)); }
public void setAttribute(String ns, String name, double value) { setAttribute(ns+":"+name, String.valueOf(value)); }
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<String> 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<String, String> attributes = new Hashtable<String, String>();
private Stack<String> openTags = new Stack<String>();
protected IndentPrintWriter writer;
protected String defaultNameSpace;
}