| /* | ||
| * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved. | ||
| * | ||
| * This software is open source. | ||
| * See the bottom of this file for the licence. | ||
| * | ||
| * $Id: XMLWriter.java,v 1.77 2004/09/24 12:25:36 maartenc Exp $ | ||
| */ | ||
| package org.dom4j.io; | ||
| import java.io.BufferedWriter; | ||
| import java.io.IOException; | ||
| import java.io.OutputStream; | ||
| import java.io.OutputStreamWriter; | ||
| import java.io.UnsupportedEncodingException; | ||
| import java.io.Writer; | ||
| import java.util.HashMap; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.StringTokenizer; | ||
| import org.dom4j.Attribute; | ||
| import org.dom4j.CDATA; | ||
| import org.dom4j.Comment; | ||
| import org.dom4j.Document; | ||
| import org.dom4j.DocumentType; | ||
| import org.dom4j.Element; | ||
| import org.dom4j.Entity; | ||
| import org.dom4j.Namespace; | ||
| import org.dom4j.Node; | ||
| import org.dom4j.ProcessingInstruction; | ||
| import org.dom4j.Text; | ||
| import org.dom4j.tree.NamespaceStack; | ||
| import org.xml.sax.Attributes; | ||
| import org.xml.sax.InputSource; | ||
| import org.xml.sax.Locator; | ||
| import org.xml.sax.SAXException; | ||
| import org.xml.sax.SAXNotRecognizedException; | ||
| import org.xml.sax.SAXNotSupportedException; | ||
| import org.xml.sax.XMLReader; | ||
| import org.xml.sax.ext.LexicalHandler; | ||
| import org.xml.sax.helpers.XMLFilterImpl; | ||
| /**<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 org.xml.sax.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.77 $ | ||
| */ | ||
| public class XMLWriter extends XMLFilterImpl implements LexicalHandler { | ||
| private static final String PAD_TEXT = " "; | ||
| protected static final String[] LEXICAL_HANDLER_NAMES = { | ||
| "http://xml.org/sax/properties/lexical-handler", | ||
| "http://xml.org/sax/handlers/LexicalHandler" | ||
| }; | ||
| protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat(); | ||
| /** Should entityRefs by resolved when writing ? */ | ||
| 4172x | private boolean resolveEntityRefs = true; | |
| /** Stores the last type of node written so algorithms can refer to the | ||
| * previous node type */ | ||
| protected int lastOutputNodeType; | ||
| /** Stores the xml:space attribute value of preserve for whitespace flag */ | ||
| 4172x | protected boolean preserve=false; | |
| /** The Writer used to output to */ | ||
| protected Writer writer; | ||
| /** The Stack of namespaceStack written so far */ | ||
| 4172x | private NamespaceStack namespaceStack = new NamespaceStack(); | |
| /** The format used by this writer */ | ||
| private OutputFormat format; | ||
| /** whether we should escape text */ | ||
| 4172x | private boolean escapeText = true; | |
| /** The initial number of indentations (so you can print a whole | ||
| document indented, if you like) **/ | ||
| 4172x | private int indentLevel = 0; | |
| /** buffer used when escaping strings */ | ||
| 4172x | private StringBuffer buffer = new StringBuffer(); | |
| /** whether we have added characters before from the same chunk of characters */ | ||
| 4172x | private boolean charactersAdded = false; | |
| private char lastChar; | ||
| /** Whether a flush should occur after writing a document */ | ||
| private boolean autoFlush; | ||
| /** Lexical handler we should delegate to */ | ||
| private LexicalHandler lexicalHandler; | ||
| /** Whether comments should appear inside DTD declarations - defaults to false */ | ||
| private boolean showCommentsInDTDs; | ||
| /** Is the writer curerntly inside a DTD definition? */ | ||
| private boolean inDTD; | ||
| /** The namespaces used for the current element when consuming SAX events */ | ||
| private Map namespacesMap; | ||
| /** | ||
| * what is the maximum allowed character code | ||
| * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit) | ||
| * or -1 to not escape any characters (other than the special XML characters like < > &) | ||
| */ | ||
| private int maximumAllowedCharacter; | ||
| public XMLWriter(Writer writer) { | ||
| 10x | this( writer, DEFAULT_FORMAT ); | |
| 10x | } | |
| 4172x | public XMLWriter(Writer writer, OutputFormat format) { | |
| 4172x | this.writer = writer; | |
| 4172x | this.format = format; | |
| 4172x | namespaceStack.push(Namespace.NO_NAMESPACE); | |
| 4172x | } | |
| 2x | public XMLWriter() { | |
| 2x | this.format = DEFAULT_FORMAT; | |
| 2x | this.writer = new BufferedWriter( new OutputStreamWriter( System.out ) ); | |
| 2x | this.autoFlush = true; | |
| 2x | namespaceStack.push(Namespace.NO_NAMESPACE); | |
| 2x | } | |
| 4x | public XMLWriter(OutputStream out) throws UnsupportedEncodingException { | |
| 4x | this.format = DEFAULT_FORMAT; | |
| 4x | this.writer = createWriter(out, format.getEncoding()); | |
| 4x | this.autoFlush = true; | |
| 4x | namespaceStack.push(Namespace.NO_NAMESPACE); | |
| 4x | } | |
| 60x | public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException { | |
| 60x | this.format = format; | |
| 60x | this.writer = createWriter(out, format.getEncoding()); | |
| 60x | this.autoFlush = true; | |
| 60x | namespaceStack.push(Namespace.NO_NAMESPACE); | |
| 60x | } | |
| 10x | public XMLWriter(OutputFormat format) throws UnsupportedEncodingException { | |
| 10x | this.format = format; | |
| 10x | this.writer = createWriter( System.out, format.getEncoding() ); | |
| 10x | this.autoFlush = true; | |
| 10x | namespaceStack.push(Namespace.NO_NAMESPACE); | |
| 10x | } | |
| public void setWriter(Writer writer) { | ||
| 2x | this.writer = writer; | |
| 2x | this.autoFlush = false; | |
| 2x | } | |
| public void setOutputStream(OutputStream out) throws UnsupportedEncodingException { | ||
| 10x | this.writer = createWriter(out, format.getEncoding()); | |
| 10x | this.autoFlush = true; | |
| 10x | } | |
| /** | ||
| * @return true if text thats output should be escaped. | ||
| * This is enabled by default. It could be disabled if | ||
| * the output format is textual, like in XSLT where we can have | ||
| * xml, html or text output. | ||
| */ | ||
| public boolean isEscapeText() { | ||
| 0x | return escapeText; | |
| } | ||
| /** | ||
| * Sets whether text output should be escaped or not. | ||
| * This is enabled by default. It could be disabled if | ||
| * the output format is textual, like in XSLT where we can have | ||
| * xml, html or text output. | ||
| */ | ||
| public void setEscapeText(boolean escapeText) { | ||
| 2x | this.escapeText = escapeText; | |
| 2x | } | |
| /** 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) { | ||
| 0x | this.indentLevel = indentLevel; | |
| 0x | } | |
| /** | ||
| * Returns the maximum allowed character code that should be allowed | ||
| * unescaped which defaults to 127 in US-ASCII (7 bit) or | ||
| * 255 in ISO-* (8 bit). | ||
| */ | ||
| public int getMaximumAllowedCharacter() { | ||
| 2/2 60942x | if (maximumAllowedCharacter == 0) { | |
| 4218x | maximumAllowedCharacter = defaultMaximumAllowedCharacter(); | |
| } | ||
| 60942x | return maximumAllowedCharacter; | |
| } | ||
| /** | ||
| * Sets the maximum allowed character code that should be allowed | ||
| * unescaped | ||
| * such as 127 in US-ASCII (7 bit) or 255 in ISO-* (8 bit) | ||
| * or -1 to not escape any characters (other than the special XML characters like < > &) | ||
| * | ||
| * If this is not explicitly set then it is defaulted from the encoding. | ||
| * | ||
| * @param maximumAllowedCharacter The maximumAllowedCharacter to set | ||
| */ | ||
| public void setMaximumAllowedCharacter(int maximumAllowedCharacter) { | ||
| 2x | this.maximumAllowedCharacter = maximumAllowedCharacter; | |
| 2x | } | |
| /** Flushes the underlying Writer */ | ||
| public void flush() throws IOException { | ||
| 76x | writer.flush(); | |
| 76x | } | |
| /** Closes the underlying Writer */ | ||
| public void close() throws IOException { | ||
| 34x | writer.close(); | |
| 34x | } | |
| /** Writes the new line text to the underlying Writer */ | ||
| public void println() throws IOException { | ||
| 116x | writer.write( format.getLineSeparator() ); | |
| 116x | } | |
| /** Writes the given {@link Attribute}. | ||
| * | ||
| * @param attribute <code>Attribute</code> to output. | ||
| */ | ||
| public void write(Attribute attribute) throws IOException { | ||
| 0x | writeAttribute(attribute); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** <p>This will print the <code>Document</code> to the current Writer.</p> | ||
| * | ||
| * <p> Warning: using your own Writer 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> | ||
| * | ||
| * <p>Note: as with all Writers, you may need to flush() yours | ||
| * after this method returns.</p> | ||
| * | ||
| * @param doc <code>Document</code> to format. | ||
| * @throws <code>IOException</code> - if there's any problem writing. | ||
| **/ | ||
| public void write(Document doc) throws IOException { | ||
| 112x | writeDeclaration(); | |
| 2/2 112x | if (doc.getDocType() != null) { | |
| 2x | indent(); | |
| 2x | writeDocType(doc.getDocType()); | |
| } | ||
| 2/2 224x | for ( int i = 0, size = doc.nodeCount(); i < size; i++ ) { | |
| 112x | Node node = doc.node(i); | |
| 112x | writeNode( node ); | |
| } | ||
| 112x | writePrintln(); | |
| 2/2 112x | if ( autoFlush ) { | |
| 72x | flush(); | |
| } | ||
| 112x | } | |
| /** <p>Writes the <code>{@link Element}</code>, including | ||
| * its <code>{@link Attribute}</code>s, and its value, and all | ||
| * its content (child nodes) to the current Writer.</p> | ||
| * | ||
| * @param element <code>Element</code> to output. | ||
| */ | ||
| public void write(Element element) throws IOException { | ||
| 4126x | writeElement(element); | |
| 2/2 4126x | if ( autoFlush ) { | |
| 4x | flush(); | |
| } | ||
| 4126x | } | |
| /** Writes the given {@link CDATA}. | ||
| * | ||
| * @param cdata <code>CDATA</code> to output. | ||
| */ | ||
| public void write(CDATA cdata) throws IOException { | ||
| 0x | writeCDATA( cdata.getText() ); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** Writes the given {@link Comment}. | ||
| * | ||
| * @param comment <code>Comment</code> to output. | ||
| */ | ||
| public void write(Comment comment) throws IOException { | ||
| 0x | writeComment( comment.getText() ); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** Writes the given {@link DocumentType}. | ||
| * | ||
| * @param docType <code>DocumentType</code> to output. | ||
| */ | ||
| public void write(DocumentType docType) throws IOException { | ||
| 0x | writeDocType(docType); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** Writes the given {@link Entity}. | ||
| * | ||
| * @param entity <code>Entity</code> to output. | ||
| */ | ||
| public void write(Entity entity) throws IOException { | ||
| 0x | writeEntity( entity ); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** Writes the given {@link Namespace}. | ||
| * | ||
| * @param namespace <code>Namespace</code> to output. | ||
| */ | ||
| public void write(Namespace namespace) throws IOException { | ||
| 0x | writeNamespace(namespace); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** Writes the given {@link ProcessingInstruction}. | ||
| * | ||
| * @param processingInstruction <code>ProcessingInstruction</code> to output. | ||
| */ | ||
| public void write(ProcessingInstruction processingInstruction) throws IOException { | ||
| 0x | writeProcessingInstruction(processingInstruction); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** <p>Print out a {@link String}, Perfoms | ||
| * the necessary entity escaping and whitespace stripping.</p> | ||
| * | ||
| * @param text is the text to output | ||
| */ | ||
| public void write(String text) throws IOException { | ||
| 0x | writeString(text); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** Writes the given {@link Text}. | ||
| * | ||
| * @param text <code>Text</code> to output. | ||
| */ | ||
| public void write(Text text) throws IOException { | ||
| 0x | writeString(text.getText()); | |
| 0/2 0x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 0x | } | |
| /** Writes the given {@link Node}. | ||
| * | ||
| * @param node <code>Node</code> to output. | ||
| */ | ||
| public void write(Node node) throws IOException { | ||
| 2x | writeNode(node); | |
| 1/2 2x | if ( autoFlush ) { | |
| 0x | flush(); | |
| } | ||
| 2x | } | |
| /** Writes the given object which should be a String, a Node or a List | ||
| * of Nodes. | ||
| * | ||
| * @param object is the object to output. | ||
| */ | ||
| public void write(Object object) throws IOException { | ||
| 1/2 2x | if (object instanceof Node) { | |
| 2x | write((Node) object); | |
| } | ||
| 0/2 0x | else if (object instanceof String) { | |
| 0x | write((String) object); | |
| } | ||
| 0/2 0x | else if (object instanceof List) { | |
| 0x | List list = (List) object; | |
| 0/2 0x | for ( int i = 0, size = list.size(); i < size; i++ ) { | |
| 0x | write( list.get(i) ); | |
| } | ||
| } | ||
| 0/2 0x | else if (object != null) { | |
| 0x | throw new IOException( "Invalid object: " + object ); | |
| } | ||
| 2x | } | |
| /** <p>Writes the opening tag of an {@link Element}, | ||
| * including its {@link Attribute}s | ||
| * but without its content.</p> | ||
| * | ||
| * @param element <code>Element</code> to output. | ||
| */ | ||
| public void writeOpen(Element element) throws IOException { | ||
| 0x | writer.write("<"); | |
| 0x | writer.write( element.getQualifiedName() ); | |
| 0x | writeAttributes(element); | |
| 0x | writer.write(">"); | |
| 0x | } | |
| /** <p>Writes the closing tag of an {@link Element}</p> | ||
| * | ||
| * @param element <code>Element</code> to output. | ||
| */ | ||
| public void writeClose(Element element) throws IOException { | ||
| 0x | writeClose( element.getQualifiedName() ); | |
| 0x | } | |
| // XMLFilterImpl methods | ||
| //------------------------------------------------------------------------- | ||
| public void parse(InputSource source) throws IOException, SAXException { | ||
| 0x | installLexicalHandler(); | |
| 0x | super.parse(source); | |
| 0x | } | |
| public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { | ||
| 0/2 0x | for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { | |
| 0/2 0x | if (LEXICAL_HANDLER_NAMES[i].equals(name)) { | |
| 0x | setLexicalHandler((LexicalHandler) value); | |
| 0x | return; | |
| } | ||
| } | ||
| 0x | super.setProperty(name, value); | |
| 0x | } | |
| public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { | ||
| 0/2 0x | for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { | |
| 0/2 0x | if (LEXICAL_HANDLER_NAMES[i].equals(name)) { | |
| 0x | return getLexicalHandler(); | |