/*--
Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer that follows
these conditions in the documentation and/or other materials
provided with the distribution.
3. The name "JDOM" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact license@jdom.org.
4. Products derived from this software may not be called "JDOM", nor
may "JDOM" appear in their name, without prior written permission
from the JDOM Project Management (pm@jdom.org).
In addition, we request (but do not require) that you include in the
end-user documentation provided with the redistribution and/or in the
software itself an acknowledgement equivalent to the following:
"This product includes software developed by the
JDOM Project (http://www.jdom.org/)."
Alternatively, the acknowledgment may be graphical using the logos
available at http://www.jdom.org/images/logos.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
This software consists of voluntary contributions made by many
individuals on behalf of the JDOM Project and was originally
created by Brett McLaughlin
* This will create an
* This will create an
* This will create an
* This will create an
* This will create an
* This will create an
* This will set the new-line separator. The default is
* This will set whether the XML declaration (
* This will set whether the XML declaration (
* This will set the indent
* This will set the indent XMLOutputter takes a JDOM tree and
* formats it to a stream as XML. This formatter performs typical
* document formatting. The XML declaration
* and processing instructions are always on their own lines. Empty
* elements are printed as <empty/> and text-only contents are printed as
* <tag>content</tag> on a single line. Constructor parameters control the
* indent amount and whether new lines are printed between elements. For
* compact machine-readable output pass in an empty string indent and a
* false for printing new lines.
* false */
private boolean suppressDeclaration = false;
/** Whether or not to output the encoding in the XML declaration - default is false */
private boolean omitEncoding = false;
/** Whether or not to use indentation - default is true */
private boolean useIndentation = true;
/** The default indent is no spaces (as original document) */
private String indent = "";
/** The default new line flag, set to do new lines only as in original document */
private boolean newlines = false;
/** Whether or not to omit white space - default is false */
private boolean omitWhite = false;
/** The encoding format */
private String enc = "UTF8";
/** New line separator */
private String newline = "\r\n";
/**
* XMLOutputter with
* no additional whitespace (indent or new lines) added;
* the whitespace from the element text content is fully preserved.
* XMLOutputter with
* the given indent added but no new lines added;
* all whitespace from the element text content is included as well.
* XMLOutputter with
* the given indent that prints newlines only if newlines is
* true;
* all whitespace from the element text content is included as well.
* 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;
}
/**
* XMLOutputter with
* the given indent that prints newlines only if newlines is
* true;
* all whitespace from the element text content is included as well.
* 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, boolean omitWhite) {
this.indent = indent;
this.newlines = newlines;
this.omitWhite = omitWhite;
}
/**
* XMLOutputter with
* the given indent and new lines printing only if newlines is
* true, and encoding format encoding.
* 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.
*/
public XMLOutputter(String indent, boolean newlines, String encoding) {
this.indent = indent;
this.newlines = newlines;
this.enc = encoding;
}
/**
* XMLOutputter with
* the given indent and new lines printing only if newlines is
* true, and encoding format encoding.
* 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.
*/
public XMLOutputter(String indent, boolean newlines, boolean omitWhite, String encoding) {
this.indent = indent;
this.newlines = newlines;
this.omitWhite = omitWhite;
this.enc = encoding;
}
/**
* \r\n.
* String line separator to use.
*/
public void setLineSeparator(String separator) {
newline = separator;
}
/**
* <xml version="1.0">)
* will be suppressed or not. It is common to suppress this in uses such
* as SOAP and XML-RPC calls.
* boolean indicating whether or not
* the XML declaration should be suppressed.
*/
public void setSuppressDeclaration(boolean suppressDeclaration) {
this.suppressDeclaration = suppressDeclaration;
}
/**
* <xml version="1.0">)
* includes the encoding of the document. It is common to suppress this in uses such
* as WML and other wireless device protocols.
* boolean indicating whether or not
* the XML declaration should indicate the document encoding.
*/
public void setOmitEncoding(boolean omitEncoding) {
this.omitEncoding = omitEncoding;
}
/**
* String to use; this is usually
* a String of empty spaces. Note that this will not affect the
* output if no indentation is being used. This can be set through
* {@link #setIndenting(boolean)}.
* String to use for indentation.
*/
public void setIndent(String indent) {
this.indent = indent;
}
/**
* String's size; an indentSize
* of 4 would result in the indention being equivalent to the String
* " ". Note that this will not affect the
* output if no indentation is being used. This can be set through
* {@link #setIndenting(boolean)}.
* int number of spaces in indentation.
*/
public void setIndentSize(int indentSize) {
StringBuffer indentBuffer = new StringBuffer();
for (int i=0; i
boolean indicating whether indentation should
* be used.
*/
public void setIndenting(boolean useIndentation) {
this.useIndentation = useIndentation;
}
/**
* *
* * @param omitWhiteboolean indicating whether white space should
* be omitted.
*/
public void setOmitWhite(boolean omitWhite) {
this.omitWhite = omitWhite;
}
/**
* * This will print the proper indent characters for the given indent level. *
* * @param outWriter to write to
* @param level int indentation level
*/
protected void indent(Writer out, int level) throws IOException {
if (useIndentation) {
for (int i = 0; i < level; i++) {
out.write(indent);
}
}
}
/**
* * This will print a new line only if the newlines flag was set to true *
* * @param outWriter to write to
*/
protected void maybePrintln(Writer out) throws IOException {
if (newlines) {
out.write(newline);
}
}
/**
*
* This will print the Document to the given Writer.
* The characters are printed using the specified encoding.
*
Document to format.
* @param out OutputStream to write to.
* @param encoding encoding format to use
* @throws IOException - if there's any problem writing.
*/
public void output(Document doc, OutputStream out, String encoding)
throws IOException {
/**
* Get an OutputStreamWriter, use specified encoding.
*/
Writer writer = new OutputStreamWriter(
new BufferedOutputStream(out), encoding);
// Print out XML declaration
printDeclaration(doc, writer, encoding);
printDocType(doc.getDocType(), writer);
// Print out root element, as well as any root level
// comments and processing instructions,
// starting with no indentation
Iterator i = doc.getMixedContent().iterator();
while (i.hasNext()) {
Object obj = i.next();
if (obj instanceof Element) {
// 0 is indentation
printElement(doc.getRootElement(), writer, 0);
} else if (obj instanceof Comment) {
printComment((Comment) obj, writer, 0);
} else if (obj instanceof ProcessingInstruction) {
printProcessingInstruction((ProcessingInstruction) obj, writer, 0);
} else if (obj instanceof CDATA) {
printCDATASection((CDATA)obj, writer, 0);
}
}
// Flush the output
writer.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.
*
Document to format.
* @param out Writer to write to.
* @throws IOException - if there's any problem writing.
*/
public void output(Document doc, OutputStream out)
throws IOException {
output(doc, out, this.enc);
}
/**
* * This will write the comment to the specified writer. *
* * @param commentComment to write.
* @param out Writer to write to.
* @param indentLevel Current depth in hierarchy.
*/
protected void printComment(Comment comment,
Writer out, int indentLevel) throws IOException {
indent(out, indentLevel);
out.write(comment.getSerializedForm());
maybePrintln(out);
}
/**
* * This will write the processing instruction to the specified writer. *
* * @param commentProcessingInstruction to write.
* @param out Writer to write to.
* @param indentLevel Current depth in hierarchy.
*/
protected void printProcessingInstruction(ProcessingInstruction pi,
Writer out, int indentLevel) throws IOException {
indent(out, indentLevel);
out.write(pi.getSerializedForm());
maybePrintln(out);
}
/**
* * This will write the declaration to the given Writer. * Assumes XML version 1.0 since we don't directly know. *
* * @param docTypeDocType whose declaration to write.
* @param out Writer to write to.
*/
protected void printDeclaration(Document doc,
Writer out,
String encoding) throws IOException {
// Only print of declaration is not suppressed
if (!suppressDeclaration) {
// Assume 1.0 version
if (encoding.equals("UTF8")) {
out.write("");
} else {
out.write("");
}
// Always print new line after declaration, to be safe
out.write(newline);
}
}
/**
* * This will write the DOCTYPE declaration if one exists. *
* * @param docDocument 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("");
maybePrintln(out);
}
/**
*
* This will handle printing out an {@link CDATA},
* and its value.
*
CDATA to output.
* @param out Writer to write to.
* @param indent int level of indention.
*/
protected void printCDATASection(CDATA cdata,
Writer out, int indentLevel) throws IOException {
indent(out, indentLevel);
out.write(cdata.getSerializedForm());
maybePrintln(out);
}
/**
*
* This will handle printing out an {@link Element},
* its {@link Attribute}s, and its value.
*
Element to output.
* @param out Writer to write to.
* @param indent int level of indention.
*/
protected void printElement(Element element, Writer out,
int indentLevel) throws IOException {
// if this is the root element we could pre-initialize the namespace stack
// with the namespaces
printElement(element, out, indentLevel, new NamespaceStack());
}
/**
*
* This will handle printing out an {@link Element},
* its {@link Attribute}s, and its value.
*
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 {
List mixedContent = element.getMixedContent();
boolean empty = mixedContent.size() == 0;
boolean stringOnly =
!empty &&
mixedContent.size() == 1 &&
mixedContent.get(0) instanceof String;
// Print beginning element tag
/* maybe the doctype, xml declaration, and processing instructions
should only break before and not after; then this check is unnecessary,
or maybe the println should only come after and never before.
Then the output always ends with a newline */
indent(out, indentLevel);
// Print the beginning of the tag plus attributes and any
// necessary namespace declarations
out.write("<");
out.write(element.getQualifiedName());
int previouslyDeclaredNamespaces = namespaces.size();
Namespace ns = element.getNamespace();
if (ns != Namespace.NO_NAMESPACE) {
String prefix = ns.getPrefix();
String uri = namespaces.getURI(prefix);
if (!ns.getURI().equals(uri)) { // output a new namespace declaration
namespaces.push(ns);
printNamespace(ns, out);
}
}
printAttributes(element.getAttributes(), element, out, namespaces);
if (empty) {
// Simply close up
out.write(" />");
maybePrintln(out);
} else if (stringOnly) {
// Print the tag with String on same line
// Example:
* This will handle printing out an {@link Entity}.
* 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.
*
Entity to output.
* @param out Writer to write to.
*/
protected void printEntity(Entity entity, Writer out) throws IOException {
out.write(entity.getSerializedForm());
}
/**
*
* This will handle printing out any needed {@link Namespace}
* declarations.
*
Namespace to print definition of
* @param out Writer to write to.
*/
protected void printNamespace(Namespace ns, Writer out) throws IOException {
out.write(" xmlns");
if (!ns.getPrefix().equals("")) {
out.write(":");
out.write(ns.getPrefix());
}
out.write("=\"");
out.write(ns.getURI());
out.write("\"");
}
/**
*
* This will handle printing out an {@link Attribute} list.
*
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();
for (int i=0, size=attributes.size(); i < size; i++) {
Attribute attribute = (Attribute)attributes.get(i);
Namespace ns = attribute.getNamespace();
if (ns != Namespace.NO_NAMESPACE) {
String prefix = ns.getPrefix();
String uri = namespaces.getURI(prefix);
if (!ns.getURI().equals(uri)) { // output a new namespace declaration
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 Attribute} list.
*
List of Attribute objcts
* @param out Writer to write to
*/
protected void printAttributes(List attributes, Element parent,
Writer out) 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();
for (int i=0, size=attributes.size(); i < size; i++) {
Attribute attribute = (Attribute)attributes.get(i);
Namespace ns = attribute.getNamespace();
if (ns != Namespace.NO_NAMESPACE) {
if (!prefixes.contains(ns.getPrefix())) {
prefixes.add(ns.getPrefix());
boolean printedNamespace = false;
Element ancestor = parent;
while (ancestor != null) {
Namespace ancestorSpace = ancestor.getNamespace();
if (ancestorSpace == Namespace.NO_NAMESPACE) continue;
String uri = ancestorSpace.getURI();
String prefix = ancestorSpace.getPrefix();
if (uri.equals(ns.getURI())) {
if (prefix.equals(ns.getPrefix())) {
printedNamespace = true;
break;
}
}
else { // different URI
if (prefix.equals(ns.getPrefix())) {
// Different URI, but same prefix;
// prefix has been redeclared; therefore we must
// redeclare
break;
}
}
ancestor = ancestor.getParent();
}
if (!printedNamespace) printNamespace(attribute.getNamespace(), out);
}
}
out.write(" ");
out.write(attribute.getQualifiedName());
out.write("=");
out.write("\"");
out.write(escapeAttributeEntities(attribute.getValue()));
out.write("\"");
}
}
/**
* * This will take the five pre-defined entities in XML 1.0 and * convert their character representation to the appropriate * entity reference, suitable for XML attributes. *
* * @param stString input to escape.
* @return String with escaped content.
*/
private 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 stString input to escape.
* @return String with escaped content.
*/
private 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();
}
}
class NamespaceStack {
private Stack prefixes = new Stack();
private Stack uris = new Stack();
public void push(Namespace ns) {
prefixes.push(ns.getPrefix());
uris.push(ns.getURI());
}
public void pop() {
String s = (String) prefixes.pop();
uris.pop();
}
public int size() {
return prefixes.size();
}
// find the URI matching the nearest prefix
public String getURI(String prefix) {
int index = prefixes.lastIndexOf(prefix);
if (index == -1) return null;
String s = (String) uris.elementAt(index);
return s;
}
/* For debugging...
public void printStack() {
System.out.println("Stack: " + prefixes.size());
for (int i = 0; i < prefixes.size(); i++) {
System.out.println(prefixes.elementAt(i) + "&" + uris.elementAt(i));
}
}
*/
}