Java tutorial
/* * $Id: ExchangerXMLWriter.java,v 1.4 2004/05/28 09:14:00 edankert Exp $ * * Copyright (C) 2002-2004, Cladonia Ltd. All rights reserved. * * This software is the proprietary information of Cladonia Ltd. * Use is subject to license terms. */ package com.cladonia.xml; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.List; import org.dom4j.Attribute; import org.dom4j.CDATA; import org.dom4j.DocumentType; import org.dom4j.Element; import org.dom4j.Entity; import org.dom4j.Namespace; import org.dom4j.Node; import org.dom4j.Text; import org.dom4j.dtd.ExternalEntityDecl; import org.dom4j.dtd.InternalEntityDecl; import org.dom4j.io.XMLWriter; import org.dom4j.tree.NamespaceStack; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /**<p><code>XMLWriter</code> takes a DOM4J tree and formats it to a * stream as XML. * It can also take SAX events too so can be used by SAX clients as this object * implements the {@link ContentHandler} and {@link LexicalHandler} interfaces. * as well. This formatter performs typical document * formatting. The XML declaration and processing instructions are * always on their own lines. An {@link OutputFormat} object can be * used to define how whitespace is handled when printing and allows various * configuration options, such as to allow suppression of the XML declaration, * the encoding declaration or whether empty documents are collapsed.</p> * * <p> There are <code>write(...)</code> methods to print any of the * standard DOM4J classes, including <code>Document</code> and * <code>Element</code>, to either a <code>Writer</code> or an * <code>OutputStream</code>. Warning: using your own * <code>Writer</code> may cause the writer's preferred character * encoding to be ignored. If you use encodings other than UTF8, we * recommend using the method that takes an OutputStream instead. * </p> * * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> * @author Joseph Bowbeer * @version $Revision: 1.4 $ */ public class ExchangerXMLWriter extends XMLWriter { private static final boolean DEBUG = false; private ExchangerOutputFormat format = null; private Element prevElement = null; /** The Stack of namespaceStack written so far */ private NamespaceStack namespaceStack = new NamespaceStack(); private int indentLevel = 0; private boolean escapeEntities = false; private static String indentString = null; private static boolean attributeOnNewLine = false; private static int lineLength = -1; private boolean wrapText = false; private boolean elementFound = false; public ExchangerXMLWriter(OutputStream out, ExchangerOutputFormat format) throws UnsupportedEncodingException { super(out, format); namespaceStack.push(Namespace.NO_NAMESPACE); this.format = format; format.setNewlines(true); format.setIndent(""); } public ExchangerXMLWriter(ExchangerOutputFormat format) throws UnsupportedEncodingException { super(format); namespaceStack.push(Namespace.NO_NAMESPACE); this.format = format; format.setNewlines(true); format.setIndent(""); } public ExchangerXMLWriter(Writer writer, ExchangerOutputFormat format) throws UnsupportedEncodingException { super(format); setWriter(new PositionedWriter(this, writer)); namespaceStack.push(Namespace.NO_NAMESPACE); this.format = format; format.setNewlines(true); format.setIndent(""); } public void setEscapeEntities(boolean escapeEntities) { this.escapeEntities = escapeEntities; } public boolean isEscapeEntities() { return escapeEntities; } public static void setAttributeOnNewLine(boolean enabled) { attributeOnNewLine = enabled; } /** * Sets the string to indent with. * * @sets the indent string. */ public static void setIndentString(String string) { indentString = string; } /** * <p> * This will write the declaration to the given Writer. * Assumes XML version 1.0 since we don't directly know. * </p> */ protected void writeDeclaration() throws IOException { String encoding = format.getEncoding(); String version = format.getVersion(); String standalone = format.getStandalone(); // Only print of declaration is not suppressed if (!format.isSuppressDeclaration()) { // Assume 1.0 version writer.write("<?xml version=\"" + version + "\""); if (!format.isOmitEncoding()) { writer.write(" encoding=\"" + encoding + "\""); } if (!format.isOmitStandalone()) { writer.write(" standalone=\"" + standalone + "\""); } writer.write("?>"); println(); } } /** * Get an OutputStreamWriter, use preferred encoding. */ protected Writer createWriter(OutputStream outStream, String encoding) throws UnsupportedEncodingException { return new PositionedWriter(this, super.createWriter(outStream, XMLUtilities.mapXMLEncodingToJava(encoding))); } public static void setMaxLineLength(int length) { lineLength = length; } public static int getMaxLineLength() { return lineLength; } public boolean isWrapText() { return wrapText; } /** Set the initial indentation level. This can be used to output * a document (or, more likely, an element) starting at a given * indent level, so it's not always flush against the left margin. * Default: 0 * * @param indentLevel the number of indents to start with */ public void setIndentLevel(int indentLevel) { this.indentLevel = indentLevel; } public int getIndentLevel() { return indentLevel; } public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException { try { writePrintln(); indent(); writer.write("<"); writer.write(qName); writeNamespaces(); writeAttributes(attributes); writer.write(">"); ++indentLevel; lastOutputNodeType = Node.ELEMENT_NODE; super.startElement(namespaceURI, localName, qName, attributes); } catch (IOException e) { handleException(e); } } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { try { --indentLevel; if (lastOutputNodeType == Node.ELEMENT_NODE) { writePrintln(); indent(); } // XXXX: need to determine this using a stack and checking for // content / children boolean hadContent = true; if (hadContent) { writeClose(qName); } else { writeEmptyElementClose(qName); } lastOutputNodeType = Node.ELEMENT_NODE; super.endElement(namespaceURI, localName, qName); } catch (IOException e) { handleException(e); } } protected boolean isElementFound() throws IOException { return elementFound; } protected void setElementFound() throws IOException { if (!elementFound) { writePrintln(); format.setNewlines(false); } elementFound = true; } // Implementation methods //------------------------------------------------------------------------- protected void writeElement(Element element) throws IOException { int prev = lastOutputNodeType; setElementFound(); if (element instanceof XElement) { XElement e = (XElement) element; if (e.isParsed()) { format.setNewlines(false); format.setIndent(""); writeXElement(e); } else { format.setNewlines(true); format.setIndent(indentString); writeXElement(e); XElement p = (XElement) e.getParent(); // The parent has set the formatting so the // parent has to undo the settings as well if (p != null && p.isParsed()) { format.setNewlines(false); format.setIndent(""); } } prevElement = element; } } // this is the same as super.writeElement... protected void writeXElement(XElement element) throws IOException { int size = element.nodeCount(); String qualifiedName = element.getQualifiedName(); writePrintln(); indent(); element.setElementStartPosition(((PositionedWriter) writer).getPosition()); // System.out.print( "["+((PositionedWriter)writer).getPosition()+"]"); writer.write("<"); writer.write(qualifiedName); int previouslyDeclaredNamespaces = namespaceStack.size(); Namespace ns = element.getNamespace(); if (isNamespaceDeclaration(ns)) { namespaceStack.push(ns); writeNamespace(ns); } // Print out additional namespace declarations boolean textOnly = true; for (int i = 0; i < size; i++) { Node node = element.node(i); if (node instanceof Namespace) { Namespace additional = (Namespace) node; if (isNamespaceDeclaration(additional)) { namespaceStack.push(additional); writeNamespace(additional); } } else if (node instanceof Element) { textOnly = false; } } writeAttributes(element); lastOutputNodeType = Node.ELEMENT_NODE; if (size <= 0) { writeEmptyElementClose(qualifiedName); } else { writer.write(">"); element.setContentStartPosition(((PositionedWriter) writer).getPosition()); if (textOnly) { // we have at least one text node so lets assume // that its non-empty writeElementContent(element); } else { XElement[] elements = element.getElements(); boolean allNotParsed = true; for (int i = 0; i < elements.length; i++) { if (elements[i].isParsed()) { allNotParsed = false; break; } } // we know it's not null or empty from above ++indentLevel; writeElementContent(element); --indentLevel; // All Children were not parsed so the end-tag has been newly created // ED: This could still be a previous element ending though! if (allNotParsed) { format.setNewlines(true); format.setIndent(indentString); } writePrintln(); indent(); if (allNotParsed) { // reset format.setNewlines(false); format.setIndent(""); } } element.setContentEndPosition(((PositionedWriter) writer).getPosition()); writer.write("</"); writer.write(qualifiedName); writer.write(">"); } element.setElementEndPosition(((PositionedWriter) writer).getPosition()); // System.out.print( "["+((PositionedWriter)writer).getPosition()+"]"); // remove declared namespaceStack from stack while (namespaceStack.size() > previouslyDeclaredNamespaces) { namespaceStack.pop(); } lastOutputNodeType = Node.ELEMENT_NODE; } /** Outputs the content of the given element. If whitespace trimming is * enabled then all adjacent text nodes are appended together before * the whitespace trimming occurs to avoid problems with multiple * text nodes being created due to text content that spans parser buffers * in a SAX parser. */ protected void writeElementContent(Element element) throws IOException { CDATA lastCDATANode = null; StringBuffer cdataBuffer = null; if (format.isTrimText()) { // concatenate adjacent text nodes together // so that whitespace trimming works properly Text lastTextNode = null; StringBuffer textBuffer = null; for (int i = 0, size = element.nodeCount(); i < size; i++) { Node node = element.node(i); if (node instanceof Text) { if (lastCDATANode != null) { if (cdataBuffer != null) { writeCDATA(cdataBuffer.toString()); cdataBuffer = null; } else { writeCDATA(lastCDATANode.getText()); } lastCDATANode = null; } if (lastTextNode == null) { lastTextNode = (Text) node; } else { if (textBuffer == null) { textBuffer = new StringBuffer(lastTextNode.getText()); } textBuffer.append(((Text) node).getText()); } } else if (node instanceof CDATA) { if (lastTextNode != null) { if (textBuffer != null) { writeString(textBuffer.toString()); textBuffer = null; } else { writeString(lastTextNode.getText()); } lastTextNode = null; } if (lastCDATANode == null) { lastCDATANode = (CDATA) node; } else { if (cdataBuffer == null) { cdataBuffer = new StringBuffer(lastCDATANode.getText()); } cdataBuffer.append(((CDATA) node).getText()); } } else { if (lastCDATANode != null) { if (cdataBuffer != null) { writeCDATA(cdataBuffer.toString()); cdataBuffer = null; } else { writeCDATA(lastCDATANode.getText()); } lastCDATANode = null; } if (lastTextNode != null) { if (textBuffer != null) { writeString(textBuffer.toString()); textBuffer = null; } else { writeString(lastTextNode.getText()); } lastTextNode = null; } writeNode(node); } } if (lastCDATANode != null) { if (cdataBuffer != null) { writeCDATA(cdataBuffer.toString()); cdataBuffer = null; } else { writeCDATA(lastCDATANode.getText()); } lastCDATANode = null; } if (lastTextNode != null) { if (textBuffer != null) { writeString(textBuffer.toString()); textBuffer = null; } else { writeString(lastTextNode.getText()); } lastTextNode = null; } } else { for (int i = 0, size = element.nodeCount(); i < size; i++) { Node node = element.node(i); if (node instanceof CDATA) { if (lastCDATANode == null) { lastCDATANode = (CDATA) node; } else { if (cdataBuffer == null) { cdataBuffer = new StringBuffer(lastCDATANode.getText()); } cdataBuffer.append(((CDATA) node).getText()); } } else { if (lastCDATANode != null) { if (cdataBuffer != null) { writeCDATA(cdataBuffer.toString()); cdataBuffer = null; } else { writeCDATA(lastCDATANode.getText()); } lastCDATANode = null; } writeNode(node); } } if (lastCDATANode != null) { if (cdataBuffer != null) { writeCDATA(cdataBuffer.toString()); cdataBuffer = null; } else { writeCDATA(lastCDATANode.getText()); } lastCDATANode = null; } } } protected void writeDocType(DocumentType docType) throws IOException { if (DEBUG) System.out.println("ExchangerXMLWriter.writeDocType( " + docType + ")"); if (docType != null) { List list = docType.getInternalDeclarations(); if (list != null) { for (int i = 0; i < list.size(); i++) { Object internalDecl = list.get(i); if (internalDecl instanceof InternalEntityDecl) { InternalEntityDecl decl = (InternalEntityDecl) internalDecl; // System.out.println( "<!ENTITY "+decl.getName()+" \""+decl.getValue()+"\">"); String value = decl.getValue(); StringBuffer result = new StringBuffer("&"); int index = value.indexOf("&"); if (index != -1 && value.length() > 5) { if (index > 0) { result.append(value.substring(0, index + 1)); } result.append(value.substring(index + 5, value.length())); decl.setValue(result.toString()); } } else if (internalDecl instanceof ExternalEntityDecl) { ExternalEntityDecl decl = (ExternalEntityDecl) internalDecl; // System.out.println( "<!ENTITY "+decl.getName()+" \""+decl.getPublicID()+"\" \""+decl.getSystemID()+"\">"); // System.out.println( internalDecl.getClass()); // System.out.println( internalDecl.toString()); } } } docType.write(writer); writer.write(format.getLineSeparator()); writer.write(format.getLineSeparator()); } } protected void writeEntity(Entity entity) throws IOException { String name = entity.getName(); if (DEBUG) System.out.println("ExchangerXMLWriter.writeEntity( " + name + ")"); if (!name.equals("amp")) { // Should never happen. writeEntityRef(entity.getName()); } else { writer.write("&"); } } protected void writeEntityRef(String name) throws IOException { if (DEBUG) System.out.println("ExchangerXMLWriter.writeEntityRef( " + name + ")"); // if ( !name.equals( "amp")) { // Should never happen. // writeEntityRef( entity.getName()); // } else { // writer.write( "&"); // } } protected String escapeElementEntities(String text) { if (DEBUG) System.out.println("ExchangerXMLWriter.escapeElementEntities( " + text + ")"); if (escapeEntities) { return super.escapeElementEntities(text); } else { // No automatic entity escaping! return text; } } protected String escapeAttributeEntities(String text) { if (DEBUG) System.out.println("ExchangerXMLWriter.escapeAttributeEntities( " + text + ")"); if (escapeEntities) { return super.escapeAttributeEntities(text); } else { // No automatic entity escaping! return text; } } protected void writeAttribute(Attribute attribute) throws IOException { StringBuffer buffer = new StringBuffer(); XAttribute a = (XAttribute) attribute; buffer.append(attribute.getQualifiedName()); buffer.append("="); String value = attribute.getValue(); boolean hasQuote = value.indexOf("\"") != -1; if (hasQuote) { buffer.append("'"); buffer.append(value); buffer.append("'"); } else { buffer.append("\""); buffer.append(value); buffer.append("\""); } String attr = buffer.toString(); // System.out.println( "Wrap ["+isWrapText()+", "+getMaxLineLength()+", "+((PositionedWriter)writer).getColumn()+", "+attr.length()+"]"); if (getMaxLineLength() != -1 && (((PositionedWriter) writer).getColumn() + attr.length() + 1) >= getMaxLineLength()) { // System.out.println( "Wrapping ... ["+attr+"]"); ++indentLevel; format.setNewlines(true); format.setIndent(indentString); writePrintln(); indent(); --indentLevel; format.setNewlines(false); format.setIndent(""); } else { writer.write(" "); } a.setAttributeStartPosition(((PositionedWriter) writer).getPosition()); writer.write(attr); a.setAttributeEndPosition(((PositionedWriter) writer).getPosition()); lastOutputNodeType = Node.ATTRIBUTE_NODE; } protected void writeAttributes(Element element) throws IOException { // I do not yet handle the case where the same prefix maps to // two different URIs. For attributes on the same element // this is illegal; but as yet we don't throw an exception // if someone tries to do this for (int i = 0, size = element.attributeCount(); i < size; i++) { Attribute attribute = element.attribute(i); Namespace ns = attribute.getNamespace(); if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) { String prefix = ns.getPrefix(); String uri = namespaceStack.getURI(prefix); if (!ns.getURI().equals(uri)) { // output a new namespace declaration writeNamespace(ns); namespaceStack.push(ns); } } writeAttribute(attribute); } } protected void writeNamespace(Namespace namespace) throws IOException { if (namespace != null) { writeNamespace(namespace.getPrefix(), namespace.getURI()); } } protected void writeNamespace(String prefix, String uri) throws IOException { StringBuffer buffer = new StringBuffer(); if (prefix != null && prefix.length() > 0) { buffer.append("xmlns:"); buffer.append(prefix); buffer.append("=\""); } else { buffer.append("xmlns=\""); } buffer.append(uri); buffer.append("\""); String namespace = buffer.toString(); // System.out.println( "Namespace Wrap ["+isWrapText()+", "+getMaxLineLength()+", "+((PositionedWriter)writer).getColumn()+", "+namespace.length()+"]"); if (getMaxLineLength() != -1 && (((PositionedWriter) writer).getColumn() + namespace.length() + 1) >= getMaxLineLength()) { // System.out.println( "Namespace Wrapping ... ["+namespace+"]"); ++indentLevel; format.setNewlines(true); format.setIndent(indentString); writePrintln(); indent(); --indentLevel; format.setNewlines(false); format.setIndent(""); } else { writer.write(" "); } writer.write(namespace); } /** * Writes the SAX namepsaces */ // protected void writeNamespaces() throws IOException { // System.out.println( "writeNamespaces"); // if ( namespacesMap != null ) { // for ( Iterator iter = namespacesMap.entrySet().iterator(); iter.hasNext(); ) { // Map.Entry entry = (Map.Entry) iter.next(); // String prefix = (String) entry.getKey(); // String uri = (String) entry.getValue(); // // writeNamespace( prefix, uri); // } // // namespacesMap = null; // } // } protected void indent() throws IOException { String indent = format.getIndent(); if (indent != null && indent.length() > 0) { for (int i = 0; i < indentLevel; i++) { writer.write(indent); } } } protected boolean isNamespaceDeclaration(Namespace ns) { if (ns != null && ns != Namespace.XML_NAMESPACE) { String uri = ns.getURI(); if (uri != null) { if (!namespaceStack.contains(ns)) { return true; } } } return false; } private static class PositionedWriter extends Writer { private ExchangerXMLWriter parent = null; private int pos = 0; private int column = 0; private Writer out; public PositionedWriter(ExchangerXMLWriter parent, Writer out) { this.out = out; this.parent = parent; } public int getPosition() { return pos; } public int getColumn() { return column; } public void close() throws IOException { out.close(); } public void flush() throws IOException { out.flush(); } public void write(char[] cbuf, int off, int len) throws IOException { for (int i = off; i < (off + len); i++) { write(cbuf[i]); } } public void write(int c) throws IOException { column++; if ((char) c == '\n') { column = 0; } out.write(c); // System.out.print( "["+((char)c)+"]"); pos++; } public void write(String s, int off, int len) throws IOException { write(s.toCharArray(), off, len); } public void write(char[] buf) throws IOException { write(buf, 0, buf.length); } public void write(String s) throws IOException { write(s.toCharArray(), 0, s.length()); } } }