// Copyright 2002-2007, FreeHEP. package org.freehep.xml.util; import com.google.code.appengine.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 13794 2010-12-15 21:56:05Z turri $ */ 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((String)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 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 '\t': case '\n': { str.append("&#"); str.append(Integer.toString(ch)); str.append(';'); break; } default: { if (ch > 0x00FF) { String hex = "0000"+Integer.toHexString(ch); str.append("&#x"); 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("&#x"); 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; }