package org.jdom.output; import java.io.*; import java.util.*; import org.jdom.*; /** *

XMLOutputter takes a JDOM tree and formats it to a * stream as XML. The outputter can manage many styles of document * formatting, from untouched to pretty printed. The default constructor * creates an outputter to output the document exactly as created. * Constructor parameters control the indent amount and whether new lines * are printed between elements. The other parameters are configurable * through the set* methods. * The XML declaration is always printed on its own line. Empty elements * are by default printed as <empty/> but that can be configured. * Text-only contents are printed as <tag>content</tag> on a * single line.

* *

For compact machine-readable output create a default * XMLOutputter and call setTextNormalize(true) to normalize any whitespace * that was preserved from the source.

* *

For pretty output, set the indent to " ", set the new lines feature * to true, and set text trimming to true.

* *

There are output(...) methods to print any of the * standard JDOM classes, including Document and * Element, to either a Writer or an * OutputStream. Warning: When outputting to a Writer, make * sure the writer's encoding matches the encoding setting in the * XMLOutputter. This ensures the encoding in which the content is written * (controlled by the Writer configuration) matches the encoding placed in * the document's XML declaration (controlled by the XMLOutputter). Because a * Writer cannot be queried for its encoding, the information must be passed * to the XMLOutputter manually in its constructor or via the setEncoding() * method. The default XMLOutputter encoding is UTF-8. *

* *

The methods outputString(...) are for convenience * only; for top performance you should call output(...) * and pass in your own Writer or * OutputStream to if possible.

* * @author Brett McLaughlin * @author Jason Hunter * @author Jason Reid * @author Wolfgang Werner * @author Elliotte Rusty Harold * @author David & Will (from Post Tool Design) * @author Dan Schaffer * @author Alex Chaffee (alex@jguru.com) * @author Philip Nelson * @version 1.0 */ public class XMLOutputter implements Cloneable { private static final String CVS_ID = "@(#) $RCSfile: XMLOutputter.java,v $ $Revision: 1.64 $ $Date: 2001/08/09 21:10:55 $ $Name: $"; /** standard value to indent by, if we are indenting **/ protected static final String STANDARD_INDENT = " "; /** standard string with which to end a line **/ protected static final String STANDARD_LINE_SEPARATOR = "\r\n"; /** Whether or not to output the XML declaration * - default is false */ private boolean omitDeclaration = false; /** The encoding format */ private String encoding = "UTF-8"; /** Whether or not to output the encoding in the XML declaration * - default is false */ private boolean omitEncoding = false; /** The default indent is no spaces (as original document) */ private String indent = null; /** Whether or not to expand empty elements to * <tagName></tagName> - default is false */ private boolean expandEmptyElements = false; /** The default new line flag, set to do new lines only as in * original document */ private boolean newlines = false; /** New line separator */ private String lineSeparator = STANDARD_LINE_SEPARATOR; /** Should we preserve whitespace or not in text nodes */ private boolean textNormalize = false; /** * Our own null subclass of NamespaceStack. This plays a little * trick with Java access protection. We want subclasses of * XMLOutputter to be able to override protected methods that * declare a NamespaceStack parameter, but we don't want to * declare the parent NamespaceStack class as public. **/ protected class NamespaceStack extends org.jdom.output.NamespaceStack { } /** *

* This will create an XMLOutputter with * no additional whitespace (indent or new lines) added; * the whitespace from the element text content is fully preserved. *

*/ public XMLOutputter() { } /** *

* This will create an XMLOutputter with * the given indent added but no new lines added; * all whitespace from the element text content is included as well. *

* * @param indent the indent string, usually some number of spaces */ public XMLOutputter(String indent) { this.indent = indent; } /** *

* This will create an XMLOutputter with * the given indent that prints newlines only if newlines is * true; * all whitespace from the element text content is included as well. *

* * @param indent the indent String, usually some number * of spaces * @param newlines true indicates new lines should be * printed, else new lines are ignored (compacted). */ public XMLOutputter(String indent, boolean newlines) { this.indent = indent; this.newlines = newlines; } /** *

* This will create an XMLOutputter with * the given indent and new lines printing only if newlines is * true, and encoding format encoding. *

* * @param indent the indent String, usually some number * of spaces * @param newlines true indicates new lines should be * printed, else new lines are ignored (compacted). * @param encoding set encoding format. Use XML-style names like * "UTF-8" or "ISO-8859-1" or "US-ASCII" */ public XMLOutputter(String indent, boolean newlines, String encoding) { this.indent = indent; this.newlines = newlines; this.encoding = encoding; } /** *

This will create an XMLOutputter with all the * options as set in the given XMLOutputter. Note * that XMLOutputter two = (XMLOutputter)one.clone(); * would work equally well.

* * @param that the XMLOutputter to clone **/ public XMLOutputter(XMLOutputter that) { this.omitDeclaration = that.omitDeclaration; this.omitEncoding = that.omitEncoding; this.indent = that.indent; this.expandEmptyElements = that.expandEmptyElements; this.newlines = that.newlines; this.encoding = that.encoding; this.lineSeparator = that.lineSeparator; this.textNormalize = that.textNormalize; } /** * Returns a copy of this XMLOutputter. **/ public Object clone() { // Implementation notes: Since all state of an XMLOutputter is // embodied in simple private instance variables, Object.clone // can be used. Note that since Object.clone is totally // broken, we must catch an exception that will never be // thrown. // (See try { return super.clone(); } catch (java.lang.CloneNotSupportedException e) { // even though this should never ever happen, it's still // possible to fool Java into throwing a // CloneNotSupportedException. If that happens, we // shouldn't swallow it. throw new RuntimeException(e.toString()); } } /** * Factory for making new NamespaceStack objects. The NamespaceStack * created is actually an inner class extending the package protected * NamespaceStack, as a way to make NamespaceStack "friendly" toward * subclassers. **/ protected NamespaceStack createNamespaceStack() { // actually returns a XMLOutputter.NamespaceStack (see below) return new NamespaceStack(); } private boolean endsWithWhite(String s) { return (s.length() > 0 && s.charAt(s.length() - 1) <= ' '); } /** *

* This will take the pre-defined entities in XML 1.0 and * convert their character representation to the appropriate * entity reference, suitable for XML attributes. It does * no converstion for ' because it's not necessary as the outputter * writes attributes surrounded by double-quotes. *

* * @param st String input to escape. * @return String with escaped content. */ protected String escapeAttributeEntities(String st) { StringBuffer buff = new StringBuffer(); char[] block = st.toCharArray(); String stEntity = null; int i, last; for (i=0, last=0; i < block.length; i++) { switch(block[i]) { case '<' : stEntity = "<"; break; case '>' : stEntity = ">"; break; /* case '\'' : stEntity = "'"; break; */ case '\"' : stEntity = """; break; case '&' : stEntity = "&"; break; default : /* no-op */ ; } if (stEntity != null) { buff.append(block, last, i - last); buff.append(stEntity); stEntity = null; last = i + 1; } } if(last < block.length) { buff.append(block, last, i - last); } return buff.toString(); } /** *

* This will take the three pre-defined entities in XML 1.0 * (used specifically in XML elements) and * convert their character representation to the appropriate * entity reference, suitable for XML element. *

* * @param st String input to escape. * @return String with escaped content. */ protected String escapeElementEntities(String st) { StringBuffer buff = new StringBuffer(); char[] block = st.toCharArray(); String stEntity = null; int i, last; for (i=0, last=0; i < block.length; i++) { switch(block[i]) { case '<' : stEntity = "<"; break; case '>' : stEntity = ">"; break; case '&' : stEntity = "&"; break; default : /* no-op */ ; } if (stEntity != null) { buff.append(block, last, i - last); buff.append(stEntity); stEntity = null; last = i + 1; } } if (last < block.length) { buff.append(block, last, i - last); } return buff.toString(); } // returns the text of the element, taking "textNormalize" into account private String getElementText(Element element) { return textNormalize ? element.getTextNormalize() : element.getText(); } /** *

* This will print the proper indent characters for the given indent level. *

* * @param out Writer to write to * @param level int indentation level */ protected void indent(Writer out, int level) throws IOException { if (indent != null && !indent.equals("")) { for (int i = 0; i < level; i++) { out.write(indent); } } } // returns true if this is an empty element (meaning no content, or only // whitespace content with textNormalize true) private boolean isEmpty(Element element) { // Calculate if the content is empty // We can handle "" string same as empty (CDATA has no effect here) List eltContent = element.getContent(); // An element with absolutely no content is empty if (eltContent.size() == 0) { return true; } // An element with only string content, whose string content // adds up to nothing, is empty if (isStringOnly(eltContent)) { String elementText = getElementText(element); if (elementText == null || elementText.equals("")) { return true; } } // anything else is not empty return false; } // Return true if the element's content list consists only of // String or CDATA nodes (or is empty) private boolean isStringOnly(List eltContent) { // Calculate if the contents are String/CDATA only Iterator itr = eltContent.iterator(); while (itr.hasNext()) { Object o = itr.next(); if (!(o instanceof String) && !(o instanceof CDATA)) { return false; } } return true; } // true if string is all whitespace (space, tab, cr, lf only) private boolean isWhitespace(String s) { char[] c = s.toCharArray(); for (int i=0; i * This will print a new line only if the newlines flag was set to true *

* * @param out Writer to write to */ protected void maybePrintln(Writer out) throws IOException { maybePrintln(out, 0); } /** *

* This will print a new line only if the newlines flag was set to * true, and then print indents (only if indent is non-null) *

* * @param out Writer to write to * @param indentLevel current indent level (number of tabs) */ protected void maybePrintln(Writer out, int indentLevel) throws IOException { if (newlines) { out.write(lineSeparator); } indent(out, indentLevel); } /** *

*

Print out a {@link java.lang.String}. Perfoms * the necessary entity escaping and whitespace stripping.

*

* * @param string String to output. * @param out OutputStream to write to. **/ public void output(String string, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(string, writer); // output() flushes } // * * * * * String * * * * * /** *

Print out a {@link java.lang.String}. Perfoms * the necessary entity escaping and whitespace stripping.

* * @param string String to output. * @param out Writer to write to. **/ public void output(String string, Writer out) throws IOException { printString(string, out); out.flush(); } /** *

* Print out a {@link CDATA} *

* * @param cdata CDATA to output. * @param out OutputStream to write to. **/ public void output(CDATA cdata, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(cdata, writer); // output() flushes } // * * * * * CDATA * * * * * /** *

* Print out a {@link CDATA} *

* * @param cdata CDATA to output. * @param out Writer to write to. **/ public void output(CDATA cdata, Writer out) throws IOException { printCDATA(cdata, out); out.flush(); } /** *

* Print out a {@link Comment} *

* * @param comment Comment to output. * @param out OutputStream to write to. **/ public void output(Comment comment, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(comment, writer); // output() flushes } // * * * * * Comment * * * * * /** *

* Print out a {@link Comment} *

* * @param comment Comment to output. * @param out Writer to write to. **/ public void output(Comment comment, Writer out) throws IOException { printComment(comment, out); out.flush(); } /** *

* Print out a {@link DocType} *

* * @param doctype DocType to output. * @param out OutputStream to write to. **/ public void output(DocType doctype, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(doctype, writer); // output() flushes } /** *

* Print out a {@link DocType} *

* * @param doctype DocType to output. * @param out Writer to write to. **/ public void output(DocType doctype, Writer out) throws IOException { printDocType(doctype, out); out.flush(); } /** *

* This will print the Document to the given output stream. * The characters are printed using the encoding specified in the * constructor, or a default of UTF-8. *

* * @param doc Document to format. * @param out OutputStream to write to. * @throws IOException - if there's any problem writing. */ public void output(Document doc, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(doc, writer); // output() flushes } /** *

This will print the Document to the given * Writer. *

* *

Warning: using your own Writer may cause the outputter's * preferred character encoding to be ignored. If you use * encodings other than UTF-8, we recommend using the method that * takes an OutputStream instead.

* * @param doc Document to format. * @param out Writer to write to. * @throws IOException - if there's any problem writing. **/ public void output(Document doc, Writer out) throws IOException { printDeclaration(doc, out, encoding); if (doc.getDocType() != null) { printDocType(doc.getDocType(), out); } // Print out root element, as well as any root level // comments and processing instructions, // starting with no indentation Iterator i = doc.getContent().iterator(); while (i.hasNext()) { Object obj = i.next(); if (obj instanceof Element) { printElement(doc.getRootElement(), out, 0, createNamespaceStack()); } else if (obj instanceof Comment) { printComment((Comment) obj, out); } else if (obj instanceof ProcessingInstruction) { printProcessingInstruction((ProcessingInstruction) obj, out); } maybePrintln(out); } // Output final line separator out.write(lineSeparator); out.flush(); } /** *

* Print out an {@link Element}, including * its {@link Attribute}s, and its value, and all * contained (child) elements etc. *

* * @param element Element to output. * @param out Writer to write to. **/ public void output(Element element, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(element, writer); // output() flushes } // * * * * * Element * * * * * /** *

* Print out an {@link Element}, including * its {@link Attribute}s, and its value, and all * contained (child) elements etc. *

* * @param element Element to output. * @param out Writer to write to. **/ public void output(Element element, Writer out) throws IOException { // If this is the root element we could pre-initialize the // namespace stack with the namespaces printElement(element, out, 0, createNamespaceStack()); out.flush(); } /** *

* Print out an {@link EntityRef}. *

* * @param entity EntityRef to output. * @param out OutputStream to write to. **/ public void output(EntityRef entity, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(entity, writer); // output() flushes } // * * * * * EntityRef * * * * * /** *

Print out an {@link EntityRef}. *

* * @param entity EntityRef to output. * @param out Writer to write to. **/ public void output(EntityRef entity, Writer out) throws IOException { printEntityRef(entity, out); out.flush(); } /** *

* Print out a {@link ProcessingInstruction} *

* * @param processingInstruction ProcessingInstruction * to output. * @param out OutputStream to write to. **/ public void output(ProcessingInstruction pi, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(pi, writer); // output() flushes } // * * * * * ProcessingInstruction * * * * * /** *

* Print out a {@link ProcessingInstruction} *

* * @param element ProcessingInstruction to output. * @param out Writer to write to. **/ public void output(ProcessingInstruction pi, Writer out) throws IOException { printProcessingInstruction(pi, out); out.flush(); } /** *

*

Print out a {@link Text}. Perfoms * the necessary entity escaping and whitespace stripping.

*

* * @param text Text to output. * @param out OutputStream to write to. **/ public void output(Text text, OutputStream out) throws IOException { Writer writer = makeWriter(out); output(text, writer); // output() flushes } // * * * * * Text * * * * * /** *

Print out a {@link Text}. Perfoms * the necessary entity escaping and whitespace stripping.

* * @param text Text to output. * @param out Writer to write to. **/ public void output(Text text, Writer out) throws IOException { printString(text.getValue(), out); out.flush(); } /** *

This will handle printing out an {@link * Element}'s content only, not including its tag, and * attributes. This can be useful for printing the content of an * element that contains HTML, like "<description>JDOM is * <b>fun>!</description>".

* * @param element Element to output. * @param out OutputStream to write to. **/ public void outputElementContent(Element element, OutputStream out) throws IOException { Writer writer = makeWriter(out); outputElementContent(element, writer); // output() flushes } /** *

This will handle printing out an {@link * Element}'s content only, not including its tag, and * attributes. This can be useful for printing the content of an * element that contains HTML, like "<description>JDOM is * <b>fun>!</description>".

* * @param element Element to output. * @param out Writer to write to. **/ public void outputElementContent(Element element, Writer out) throws IOException { List eltContent = element.getContent(); printElementContent(element, out, 0, createNamespaceStack(), eltContent); out.flush(); } /** * Return a string representing a CDATA section. Warning: a String is * Unicode, which may not match the outputter's specified * encoding. * * @param cdata CDATA to format. **/ public String outputString(CDATA cdata) { StringWriter out = new StringWriter(); try { output(cdata, out); // output() flushes } catch (IOException e) { } return out.toString(); } /** * Return a string representing a comment. Warning: a String is * Unicode, which may not match the outputter's specified * encoding. * * @param comment Comment to format. **/ public String outputString(Comment comment) { StringWriter out = new StringWriter(); try { output(comment, out); // output() flushes } catch (IOException e) { } return out.toString(); } /** * Return a string representing a DocType. Warning: a String is * Unicode, which may not match the outputter's specified * encoding. * * @param doc DocType to format. **/ public String outputString(DocType doctype) { StringWriter out = new StringWriter(); try { output(doctype, out); // output() flushes } catch (IOException e) { } return out.toString(); } // * * * * * String * * * * * /** * Return a string representing a document. Uses an internal * StringWriter. Warning: a String is Unicode, which may not match * the outputter's specified encoding. * * @param doc Document to format. **/ public String outputString(Document doc) { StringWriter out = new StringWriter(); try { output(doc, out); // output() flushes } catch (IOException e) { } return out.toString(); } /** * Return a string representing an element. Warning: a String is * Unicode, which may not match the outputter's specified * encoding. * * @param doc Element to format. **/ public String outputString(Element element) { StringWriter out = new StringWriter(); try { output(element, out); // output() flushes } catch (IOException e) { } return out.toString(); } /** * Return a string representing an entity. Warning: a String is * Unicode, which may not match the outputter's specified * encoding. * * @param doc EntityRef to format. **/ public String outputString(EntityRef entity) { StringWriter out = new StringWriter(); try { output(entity, out); // output() flushes } catch (IOException e) { } return out.toString(); } /** * Return a string representing a PI. Warning: a String is * Unicode, which may not match the outputter's specified * encoding. * * @param doc ProcessingInstruction to format. **/ public String outputString(ProcessingInstruction pi) { StringWriter out = new StringWriter(); try { output(pi, out); // output() flushes } catch (IOException e) { } return out.toString(); } /** * parse command-line arguments of the form -omitEncoding * -indentSize 3 ... * @return int index of first parameter that we didn't understand **/ public int parseArgs(String[] args, int i) { for (; i * This will handle printing out an {@link Attribute} list. *

* * @param attributes List of Attribute objcts * @param out Writer to write to */ protected void printAttributes(List attributes, Element parent, Writer out, NamespaceStack namespaces) 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 Set prefixes = new HashSet(); Iterator itr = attributes.iterator(); while (itr.hasNext()) { Attribute attribute = (Attribute)itr.next(); Namespace ns = attribute.getNamespace(); if (ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) { String prefix = ns.getPrefix(); String uri = namespaces.getURI(prefix); if (!ns.getURI().equals(uri)) { // output a new namespace decl printNamespace(ns, out); namespaces.push(ns); } } out.write(" "); out.write(attribute.getQualifiedName()); out.write("="); out.write("\""); out.write(escapeAttributeEntities(attribute.getValue())); out.write("\""); } } /** *

* This will handle printing out an {@link CDATA}, * and its value. *

* * @param cdata CDATA to output. * @param out Writer to write to. */ protected void printCDATA(CDATA cdata, Writer out) throws IOException { out.write(""); } /** *

* This will write the comment to the specified writer. *

* * @param comment Comment to write. * @param out Writer to write to. */ protected void printComment(Comment comment, Writer out) throws IOException { out.write(""); } // * * * * * Internal printing methods * * * * * /** *

* This will write the declaration to the given Writer. * Assumes XML version 1.0 since we don't directly know. *

* * @param doc Document whose declaration to write. * @param out Writer to write to. * @param encoding The encoding to add to the declaration */ protected void printDeclaration(Document doc, Writer out, String encoding) throws IOException { // Only print the declaration if it's not being omitted if (!omitDeclaration) { // Assume 1.0 version out.write(""); // Print new line after decl always, even if no other new lines // Helps the output look better and is semantically // inconsequential out.write(lineSeparator); } } /** *

* This will write the DOCTYPE declaration if one exists. *

* * @param doc Document whose declaration to write. * @param out Writer to write to. */ protected void printDocType(DocType docType, Writer out) throws IOException { if (docType == null) { return; } String publicID = docType.getPublicID(); String systemID = docType.getSystemID(); boolean hasPublic = false; out.write(""); // Print new line after decl always, even if no other new lines // Helps the output look better and is semantically // inconsequential out.write(lineSeparator); } /** *

* This will handle printing out an {@link Element}, * its {@link Attribute}s, and its value. *

* * @param element Element to output. * @param out Writer to write to. * @param indent int level of indention. * @param namespaces List stack of Namespaces in scope. */ protected void printElement(Element element, Writer out, int indentLevel, NamespaceStack namespaces) throws IOException { // This method prints from the beginning < to the trailing >. // (no leading or trailing whitespace!) List eltContent = element.getContent(); // Print the beginning of the tag plus attributes and any // necessary namespace declarations out.write("<"); out.write(element.getQualifiedName()); // Mark our namespace starting point int previouslyDeclaredNamespaces = namespaces.size(); // Print the element's namespace, if appropriate printElementNamespace(element, out, namespaces); // Print out additional namespace declarations printAdditionalNamespaces(element, out, namespaces); printAttributes(element.getAttributes(), element, out, namespaces); // Calculate if the contents are String/CDATA only // This helps later with the "empty" check boolean stringOnly = true; Iterator itr = eltContent.iterator(); while (itr.hasNext()) { Object o = itr.next(); if (!(o instanceof String) && !(o instanceof CDATA)) { stringOnly = false; break; } } // Calculate if the content is empty // We can handle "" string same as empty (CDATA has no effect here) boolean empty = false; if (stringOnly) { String elementText = textNormalize ? element.getTextNormalize() : element.getText(); if (elementText == null || elementText.equals("")) { empty = true; } } // If empty, print closing; if not empty, print content if (empty) { // Simply close up if (!expandEmptyElements) { out.write(" />"); } else { out.write(">"); } } else { // We know it's not null or empty from above out.write(">"); printElementContent(element, out, indentLevel + 1, namespaces, eltContent); out.write(""); } // remove declared namespaces from stack while (namespaces.size() > previouslyDeclaredNamespaces) { namespaces.pop(); } } /** *

This will handle printing out an {@link * Element}'s content only, not including its tag, * attributes, and namespace info.

* * @param element Element to output. * @param out Writer to write to. * @param indent int level of indentation. **/ protected void printElementContent(Element element, Writer out, int indentLevel, NamespaceStack namespaces, List eltContent) throws IOException { // get same local flags as printElement does // a little redundant code-wise, but not performance-wise boolean empty = eltContent.size() == 0; // Calculate if the content is String/CDATA only boolean stringOnly = true; if (!empty) { stringOnly = isStringOnly(eltContent); } if (stringOnly) { Class justOutput = null; boolean endedWithWhite = false; Iterator itr = eltContent.iterator(); while (itr.hasNext()) { Object content = itr.next(); if (content instanceof String) { String scontent = (String) content; if ((justOutput == CDATA.class) && (textNormalize) && (startsWithWhite(scontent))) { out.write(" "); } printString(scontent, out); endedWithWhite = endsWithWhite(scontent); justOutput = String.class; } else { // We're in a CDATA section if ((justOutput == String.class) && (textNormalize) && (endedWithWhite)) { out.write(" "); // padding } printCDATA((CDATA)content, out); justOutput = CDATA.class; } } } else { // Iterate through children Object content = null; Class justOutput = null; boolean endedWithWhite = false; boolean wasFullyWhite = false; Iterator itr = eltContent.iterator(); while (itr.hasNext()) { content = itr.next(); // See if text, an element, a PI or a comment if (content instanceof Comment) { if (!((justOutput == String.class) && (wasFullyWhite))) { maybePrintln(out, indentLevel); } printComment((Comment) content, out); justOutput = Comment.class; } else if (content instanceof String) { String scontent = (String) content; // bugfix: don't print lines for whitespace that's // only sticking between close tags if (textNormalize && isWhitespace(scontent)) { wasFullyWhite = true; continue; } if ((justOutput == CDATA.class) && (textNormalize) && (startsWithWhite(scontent))) { out.write(" "); } else if ((justOutput != CDATA.class) && (justOutput != String.class) && (justOutput != null) // (justOutput != Element.class) ) { maybePrintln(out, indentLevel); } // if scontent is not a single-character newline if (!((scontent.length() == 1) && ("\r\n".indexOf(scontent.charAt(0)) != -1))) { printString(scontent, out); endedWithWhite = endsWithWhite(scontent); justOutput = String.class; wasFullyWhite = (scontent.trim().length() == 0); } } else if (content instanceof Element) { if (!((justOutput == String.class) && (wasFullyWhite))) { maybePrintln(out, indentLevel); } printElement((Element) content, out, indentLevel, namespaces); justOutput = Element.class; } else if (content instanceof EntityRef) { if (!((justOutput == String.class) && (wasFullyWhite))) { maybePrintln(out, indentLevel); } printEntityRef((EntityRef) content, out); justOutput = EntityRef.class; } else if (content instanceof ProcessingInstruction) { if (!((justOutput == String.class) && (wasFullyWhite))) { maybePrintln(out, indentLevel); } printProcessingInstruction((ProcessingInstruction) content, out); justOutput = ProcessingInstruction.class; } else if (content instanceof CDATA) { if ((justOutput == String.class) && (textNormalize) && (endedWithWhite)) { out.write(" "); // padding } else if (justOutput != String.class && justOutput != CDATA.class) { maybePrintln(out, indentLevel); } printCDATA((CDATA)content, out); justOutput = CDATA.class; } // Unsupported types are *not* printed, nor should they exist } maybePrintln(out, indentLevel - 1); } } // printElementContent private void printElementNamespace(Element element, Writer out, NamespaceStack namespaces) throws IOException { // Add namespace decl only if it's not the XML namespace and it's // not the NO_NAMESPACE with the prefix "" not yet mapped // (we do output xmlns="" if the "" prefix was already used and we // need to reclaim it for the NO_NAMESPACE) Namespace ns = element.getNamespace(); if (ns != Namespace.XML_NAMESPACE && !(ns == Namespace.NO_NAMESPACE && namespaces.getURI("") == null)) { String prefix = ns.getPrefix(); String uri = namespaces.getURI(prefix); if (!ns.getURI().equals(uri)) { // output a new namespace decl namespaces.push(ns); printNamespace(ns, out); } } } /** *

* This will handle printing out an {@link EntityRef}. * Only the entity reference such as &entity; * will be printed. However, subclasses are free to override * this method to print the contents of the entity instead. *

* * @param entity EntityRef to output. * @param out Writer to write to. */ protected void printEntityRef(EntityRef entity, Writer out) throws IOException { out.write(new StringBuffer() .append("&") .append(entity.getName()) .append(";") .toString()); } /** *

* This will handle printing out any needed {@link Namespace} * declarations. *

* * @param ns Namespace to print definition of * @param out Writer to write to. */ private void printNamespace(Namespace ns, Writer out) throws IOException { out.write(" xmlns"); String prefix = ns.getPrefix(); if (!prefix.equals("")) { out.write(":"); out.write(prefix); } out.write("=\""); out.write(ns.getURI()); out.write("\""); } /** *

* This will write the processing instruction to the specified writer. *

* * @param comment ProcessingInstruction to write. * @param out Writer to write to. */ protected void printProcessingInstruction(ProcessingInstruction pi, Writer out) throws IOException { String target = pi.getTarget(); String rawData = pi.getData(); // Write or if no data then just if (!"".equals(rawData)) { out.write(""); } else { out.write(""); } } /** * Print a string. Escapes the element entities, trims interior * whitespace if necessary. **/ protected void printString(String s, Writer out) throws IOException { s = escapeElementEntities(s); // patch by Brad Morgan to strip interior whitespace // (Brad.Morgan@e-pubcorp.com) if (textNormalize) { StringTokenizer tokenizer = new StringTokenizer(s); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); out.write(token); if (tokenizer.hasMoreTokens()) { out.write(" "); } } } else { out.write(s); } } /** * Sets the output encoding. The name should be an accepted XML * encoding. * * @param encoding the encoding format. Use XML-style names like * "UTF-8" or "ISO-8859-1" or "US-ASCII" **/ public void setEncoding(String encoding) { this.encoding = encoding; } /** *

* This will set whether empty elements are expanded from * <tagName> to * <tagName></tagName>. *

* * @param expandEmptyElements boolean indicating whether or not * empty elements should be expanded. */ public void setExpandEmptyElements(boolean expandEmptyElements) { this.expandEmptyElements = expandEmptyElements; } /** *

This will set the indent String to use; this * is usually a String of empty spaces. If you pass * null, or the empty string (""), then no indentation will * happen.

* Default: none (null) * * @param indent String to use for indentation. **/ public void setIndent(String indent) { // if passed the empty string, change it to null, for marginal // performance gains later (can compare to null first instead // of calling equals()) if ("".equals(indent)) indent = null; this.indent = indent; } /** * Set the indent on or off. If setting on, will use the value of * STANDARD_INDENT, which is usually two spaces. * * @param doIndent if true, set indenting on; if false, set indenting off **/ public void setIndent(boolean doIndent) { if (doIndent) { this.indent = STANDARD_INDENT; } else { this.indent = null; } } /** * Set the initial indentation level. * * @deprecated Deprecated in beta7, because this is better done with a * stacked FilterOutputStream */ public void setIndentLevel(int indentLevel) { } /** *

* This will set the indent String's size; an indentSize * of 4 would result in the indentation being equivalent to the * String "    " (four space chars). *

* * @param indentSize int number of spaces in indentation. */ public void setIndentSize(int indentSize) { StringBuffer indentBuffer = new StringBuffer(); for (int i=0; iThis will set the new-line separator. The default is * \r\n. Note that if the "newlines" property is * false, this value is irrelevant. To make it output the system * default line ending string, call * setLineSeparator(System.getProperty("line.separator")) *

* *

* To output "UNIX-style" documents, call * setLineSeparator("\n"). To output "Mac-style" * documents, call setLineSeparator("\r"). DOS-style * documents use CR-LF ("\r\n"), which is the default. *

* *

* Note that this only applies to newlines generated by the * outputter. If you parse an XML document that contains newlines * embedded inside a text node, and you do not call * setTextNormalize, then the newlines will be output * verbatim, as "\n" which is how parsers normalize them. *

* * @see #setNewlines(boolean) * @see #setTextNormalize(boolean) * * @param separator String line separator to use. **/ public void setLineSeparator(String separator) { lineSeparator = separator; } /** * @see #setLineSeparator(String) * @param newlines true indicates new lines should be * printed, else new lines are ignored (compacted). **/ public void setNewlines(boolean newlines) { this.newlines = newlines; } /** *

* This will set whether the XML declaration * (<?xml version="1.0"?>) * will be omitted or not. It is common to omit this in uses such * as SOAP and XML-RPC calls. *

* * @param omitDeclaration boolean indicating whether or not * the XML declaration should be omitted. */ public void setOmitDeclaration(boolean omitDeclaration) { this.omitDeclaration = omitDeclaration; } /** *

* This will set whether the XML declaration * (<?xml version="1.0" encoding="UTF-8"?>) * includes the encoding of the document. It is common to omit * this in uses such as WML and other wireless device protocols. *

* * @param omitEncoding boolean indicating whether or not * the XML declaration should indicate the document encoding. */ public void setOmitEncoding(boolean omitEncoding) { this.omitEncoding = omitEncoding; } /** *

Ensure that text immediately preceded by or followed by an * element will be "padded" with a single space.

* * @deprecated Deprecated in beta7, because this is no longer necessary */ public void setPadText(boolean padText) { } /** *

* This will set whether the XML declaration * (<?xml version="1.0"?>) * will be suppressed or not. It is common to suppress this in uses such * as SOAP and XML-RPC calls. *

* * @param suppressDeclaration boolean indicating whether or not * the XML declaration should be suppressed. * @deprecated Deprecated in beta7, use setOmitDeclaration() instead */ public void setSuppressDeclaration(boolean suppressDeclaration) { this.omitDeclaration = suppressDeclaration; } /** *

This will set whether the text is output verbatim (false) * or with whitespace normalized as per {@link * org.jdom.Element#getTextNormalize()}.

* *

Default: false

* * @param textNormalize boolean true=>normalize the * whitespace, false=>use text verbatim **/ public void setTextNormalize(boolean textNormalize) { this.textNormalize = textNormalize; } /** *

This will set whether the text is output verbatim (false) * or with whitespace stripped.

* *

Default: false

* * @param trimText boolean true=>trim the whitespace, * false=>use text verbatim * * @deprecated Deprecated in beta7, use setTextNormalize() instead **/ public void setTrimText(boolean textTrim) { this.textNormalize = textTrim; } private boolean startsWithWhite(String s) { return (s.length() > 0 && s.charAt(0) <= ' '); } private String toByte(char ch) { switch (ch) { case '\r': return "\\r"; case '\n': return "\\n"; case '\t': return "\\t"; default: return ("[" + ((int)ch) + "]"); } } private String toBytes(String x) { StringBuffer buf = new StringBuffer(); for (int i=0; i