/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.libraries.xmlns.writer;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.HashMap;
import java.util.Properties;
import org.pentaho.reporting.libraries.base.util.FastStack;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.xmlns.common.AttributeList;
/**
* A support class for writing XML files.
*
* @author Thomas Morgner
*/
public class XmlWriterSupport
{
/**
* An internal state-management class containing the state for nested
* tags.
*/
private static class ElementLevel
{
private String namespace;
private String prefix;
private String tagName;
private DeclaredNamespaces namespaces;
/**
* Creates a new ElementLevel object.
*
* @param namespace the namespace of the current tag (can be null).
* @param prefix the namespace prefix of the current tag (can be null).
* @param tagName the tagname (never null).
* @param namespaces the collection of all currently known namespaces (never null).
*/
protected ElementLevel(final String namespace,
final String prefix,
final String tagName,
final DeclaredNamespaces namespaces)
{
if (tagName == null)
{
throw new NullPointerException();
}
if (namespaces == null)
{
throw new NullPointerException();
}
this.prefix = prefix;
this.namespace = namespace;
this.tagName = tagName;
this.namespaces = namespaces;
}
/**
* Creates a new ElementLevel with no namespace information.
*
* @param tagName the xml-tagname.
* @param namespaces the currently known namespaces.
*/
protected ElementLevel(final String tagName,
final DeclaredNamespaces namespaces)
{
if (tagName == null)
{
throw new NullPointerException();
}
if (namespaces == null)
{
throw new NullPointerException();
}
this.namespaces = namespaces;
this.tagName = tagName;
}
/**
* Returns the defined namespace prefix for this entry.
*
* @return the namespace prefix.
*/
public String getPrefix()
{
return prefix;
}
/**
* Returns the defined namespace uri for this entry.
*
* @return the namespace uri.
*/
public String getNamespace()
{
return namespace;
}
/**
* Returns the tagname for this entry.
*
* @return the tagname.
*/
public String getTagName()
{
return tagName;
}
/**
* Returns the map of defined namespace for this entry.
*
* @return the namespaces.
*/
public DeclaredNamespaces getNamespaces()
{
return namespaces;
}
}
/**
* A constant for controlling the indent function.
*/
public static final int OPEN_TAG_INCREASE = 1;
/**
* A constant for controlling the indent function.
*/
public static final int CLOSE_TAG_DECREASE = 2;
/**
* A constant for controlling the indent function.
*/
public static final int INDENT_ONLY = 3;
/**
* A constant for close.
*/
public static final boolean CLOSE = true;
/**
* A constant for open.
*/
public static final boolean OPEN = false;
/**
* A list of safe tags.
*/
private TagDescription safeTags;
/**
* The indent level for that writer.
*/
private FastStack openTags;
/**
* The indent string.
*/
private String indentString;
private boolean lineEmpty;
private int additionalIndent;
private boolean alwaysAddNamespace;
private boolean assumeDefaultNamespace;
private HashMap impliedNamespaces;
private boolean writeFinalLinebreak;
private boolean htmlCompatiblityMode;
private String lineSeparator;
private StringBuffer normalizeBuffer;
private String encoding;
private boolean encodingvalid;
private CharsetEncoder charsetEncoder;
/**
* Default Constructor. The created XMLWriterSupport will not have no safe tags and starts with an indention level of
* 0.
*/
public XmlWriterSupport()
{
this(new DefaultTagDescription(), " ");
}
/**
* Creates a new support instance.
*
* @param safeTags the tags that are safe for line breaks.
* @param indentString the indent string.
*/
public XmlWriterSupport(final TagDescription safeTags,
final String indentString)
{
this(safeTags, indentString, StringUtils.getLineSeparator());
}
/**
* Create a new XmlWriterSupport instance.
*
* @param safeTags the tags that are safe for line breaks.
* @param indentString the indent string.
* @param lineseparator the lineseparator that should be used for writing XML files.
*/
public XmlWriterSupport(final TagDescription safeTags,
final String indentString,
final String lineseparator)
{
if (indentString == null)
{
throw new NullPointerException("IndentString must not be null");
}
if (safeTags == null)
{
throw new NullPointerException("SafeTags must not be null");
}
if (lineseparator == null)
{
throw new NullPointerException("LineSeparator must not be null");
}
this.normalizeBuffer = new StringBuffer(128);
this.safeTags = safeTags;
this.openTags = new FastStack();
this.indentString = indentString;
this.lineEmpty = true;
this.writeFinalLinebreak = true;
this.lineSeparator = lineseparator;
addImpliedNamespace("http://www.w3.org/XML/1998/namespace", "xml");
}
/**
* Checks, whether the HTML compatibility mode is enabled. In HTML compatibility
* mode, closed empty tags will have a space between the tagname and the
* close-indicator.
*
* @return true, if the HTML compatiblity mode is enabled, false otherwise.
*/
public boolean isHtmlCompatiblityMode()
{
return htmlCompatiblityMode;
}
/**
* Enables or disables the HTML Compatibility mode. In HTML compatibility
* mode, closed empty tags will have a space between the tagname and the
* close-indicator.
*
* @param htmlCompatiblityMode true, if the HTML compatiblity mode is enabled, false otherwise.
*/
public void setHtmlCompatiblityMode(final boolean htmlCompatiblityMode)
{
this.htmlCompatiblityMode = htmlCompatiblityMode;
}
/**
* Checks, whether the XML writer should always add a namespace prefix to
* the attributes. The XML specification leaves it up to the application on
* how to handle unqualified attributes. If this mode is enabled, all
* attributes will always be fully qualified - which removed the ambugity
* but may not be compatible with simple, non namespace aware parsers.
*
* @return true, if all attributes should be qualified, false otherwise.
*/
public boolean isAlwaysAddNamespace()
{
return alwaysAddNamespace;
}
/**
* Defines, whether the XML writer should always add a namespace prefix to
* the attributes. The XML specification leaves it up to the application on
* how to handle unqualified attributes. If this mode is enabled, all
* attributes will always be fully qualified - which removed the ambuigity
* but may not be compatible with simple, non namespace aware parsers.
*
* @param alwaysAddNamespace set to true, if all attributes should be qualified,
* false otherwise.
*/
public void setAlwaysAddNamespace(final boolean alwaysAddNamespace)
{
this.alwaysAddNamespace = alwaysAddNamespace;
}
/**
* Returns the indent level that should be added to the automaticly
* computed indentation.
*
* @return the indent level.
*/
public int getAdditionalIndent()
{
return additionalIndent;
}
/**
* Defines the indent level that should be added to the automaticly
* computed indentation.
*
* @param additionalIndent the indent level.
*/
public void setAdditionalIndent(final int additionalIndent)
{
this.additionalIndent = additionalIndent;
}
/**
* Returns the line separator.
*
* @return the line separator.
*/
public String getLineSeparator()
{
return lineSeparator;
}
/**
* Writes the XML declaration that usually appears at the top of every XML
* file.
*
* @param encoding the encoding that should be declared (this has to match the
* encoding of the writer, or funny things may happen when
* parsing the xml file later).
* @throws java.io.IOException if there is a problem writing to the character
* stream.
*/
public void writeXmlDeclaration(final Writer writer, final String encoding)
throws IOException
{
if (encoding == null)
{
writer.write("<?xml version=\"1.0\"?>");
writer.write(getLineSeparator());
return;
}
writer.write("<?xml version=\"1.0\" encoding=\"");
writer.write(encoding);
writer.write("\"?>");
writer.write(getLineSeparator());
setEncoding(encoding);
}
public void setEncoding(final String encoding)
{
this.encoding = encoding;
this.encodingvalid = true;
final Charset charset = Charset.forName(encoding);
this.charsetEncoder = charset.newEncoder();
}
/**
* Writes an opening XML tag that has no attributes.
*
* @param w the writer.
* @param namespaceUri the namespace URI for the element.
* @param name the tag name.
* @throws java.io.IOException if there is an I/O problem.
*/
public void writeTag(final Writer w,
final String namespaceUri,
final String name)
throws IOException
{
writeTag(w, namespaceUri, name, null, XmlWriterSupport.OPEN);
}
/**
* Writes a closing XML tag.
*
* @param w the writer.
* @throws java.io.IOException if there is an I/O problem.
*/
public void writeCloseTag(final Writer w)
throws IOException
{
indentForClose(w);
final ElementLevel level = (ElementLevel) openTags.pop();
setLineEmpty(false);
w.write("</");
final String prefix = level.getPrefix();
if (prefix != null)
{
w.write(prefix);
w.write(":");
w.write(level.getTagName());
}
else
{
w.write(level.getTagName());
}
w.write(">");
doEndOfLine(w);
}
/**
* Writes a linebreak to the writer.
*
* @param writer the writer.
* @throws IOException if there is a problem writing to the character stream.
*/
public void writeNewLine(final Writer writer)
throws IOException
{
if (isLineEmpty() == false)
{
writer.write(lineSeparator);
setLineEmpty(true);
}
}
/**
* Checks, whether the currently generated line of text is empty.
*
* @return true, if the line is empty, false otherwise.
*/
public boolean isLineEmpty()
{
return lineEmpty;
}
/**
* A marker flag to track, wether the current line is empty. This influences the indention.
*
* @param lineEmpty defines, whether the current line should be treated as empty line.
*/
public void setLineEmpty(final boolean lineEmpty)
{
this.lineEmpty = lineEmpty;
}
/**
* Writes an opening XML tag with an attribute/value pair.
*
* @param w the writer.
* @param namespace the namespace URI for the element
* @param name the tag name.
* @param attributeName the attribute name.
* @param attributeValue the attribute value.
* @param close controls whether the tag is closed.
* @throws java.io.IOException if there is an I/O problem.
*/
public void writeTag(final Writer w,
final String namespace,
final String name,
final String attributeName,
final String attributeValue,
final boolean close)
throws IOException
{
if (attributeName != null)
{
final AttributeList attr = new AttributeList();
attr.setAttribute(namespace, attributeName, attributeValue);
writeTag(w, namespace, name, attr, close);
}
else
{
writeTag(w, namespace, name, null, close);
}
}
/**
* Adds an implied namespace to the document. Such a namespace is not explicitly declared, it is assumed that the
* xml-parser knows the prefix by some other means. Using implied namespaces for standalone documents is almost always
* a bad idea.
*
* @param uri the uri of the namespace.
* @param prefix the defined prefix.
*/
public void addImpliedNamespace(final String uri, final String prefix)
{
if (openTags.isEmpty() == false)
{
throw new IllegalStateException("Cannot modify the implied namespaces in the middle of the processing");
}
if (prefix == null)
{
if (impliedNamespaces == null)
{
return;
}
impliedNamespaces.remove(uri);
}
else
{
if (impliedNamespaces == null)
{
impliedNamespaces = new HashMap();
}
impliedNamespaces.put(uri, prefix);
}
}
/**
* Copies all currently declared namespaces of the given XmlWriterSupport instance as new implied namespaces into this
* instance.
*
* @param writerSupport the Xml-writer from where to copy the declared namespaces.
*/
public void copyNamespaces(final XmlWriterSupport writerSupport)
{
if (openTags.isEmpty() == false)
{
throw new IllegalStateException("Cannot modify the implied namespaces in the middle of the processing");
}
if (impliedNamespaces == null)
{
impliedNamespaces = new HashMap();
}
if (writerSupport.openTags.isEmpty() == false)
{
final ElementLevel parent = (ElementLevel) writerSupport.openTags.peek();
impliedNamespaces.putAll(parent.getNamespaces().getNamespaces());
}
if (writerSupport.impliedNamespaces != null)
{
impliedNamespaces.putAll(writerSupport.impliedNamespaces);
}
}
/**
* Checks, whether the given URI is defined as valid namespace.
*
* @param uri the uri of the namespace.
* @return true, if there's a namespace defined, false otherwise.
*/
public boolean isNamespaceDefined(final String uri)
{
if (impliedNamespaces != null)
{
if (impliedNamespaces.containsKey(uri))
{
return true;
}
}
if (openTags.isEmpty())
{
return false;
}
final ElementLevel parent = (ElementLevel) openTags.peek();
return parent.getNamespaces().isNamespaceDefined(uri);
}
/**
* Checks, whether the given namespace prefix is defined.
*
* @param prefix the namespace prefix.
* @return true, if the prefix is defined, false otherwise.
*/
public boolean isNamespacePrefixDefined(final String prefix)
{
if (impliedNamespaces != null)
{
if (impliedNamespaces.containsValue(prefix))
{
return true;
}
}
if (openTags.isEmpty())
{
return false;
}
final ElementLevel parent = (ElementLevel) openTags.peek();
return parent.getNamespaces().isPrefixDefined(prefix);
}
/**
* Returns all namespaces as properties-collection. This reflects the currently defined namespaces, therefore
* calls to writeOpenTag(..) might cause this method to return different collections.
*
* @return the defined namespaces.
*/
public Properties getNamespaces()
{
final Properties namespaces = new Properties();
if (openTags.isEmpty())
{
if (impliedNamespaces != null)
{
//noinspection UseOfPropertiesAsHashtable
namespaces.putAll(impliedNamespaces);
}
return namespaces;
}
final ElementLevel parent = (ElementLevel) openTags.peek();
//noinspection UseOfPropertiesAsHashtable
namespaces.putAll(parent.getNamespaces().getNamespaces());
return namespaces;
}
/**
* Computes the current collection of defined namespaces.
*
* @return the namespaces declared at this writing position.
*/
protected DeclaredNamespaces computeNamespaces()
{
if (openTags.isEmpty())
{
final DeclaredNamespaces namespaces = new DeclaredNamespaces();
if (impliedNamespaces != null)
{
return namespaces.add(impliedNamespaces);
}
return namespaces;
}
final ElementLevel parent = (ElementLevel) openTags.peek();
return parent.getNamespaces();
}
/**
* Writes an opening XML tag along with a list of attribute/value pairs.
*
* @param w the writer.
* @param namespaceUri the namespace uri for the element (can be null).
* @param name the tag name.
* @param attributes the attributes.
* @param close controls whether the tag is closed.
* @throws java.io.IOException if there is an I/O problem.
*/
public void writeTag(final Writer w,
final String namespaceUri,
final String name,
final AttributeList attributes,
final boolean close)
throws IOException
{
if (name == null)
{
throw new NullPointerException();
}
indent(w);
setLineEmpty(false);
DeclaredNamespaces namespaces = computeNamespaces();
if (attributes != null)
{
namespaces = namespaces.add(attributes);
}
w.write("<");
if (namespaceUri == null)
{
w.write(name);
openTags.push(new ElementLevel(name, namespaces));
}
else
{
final String nsPrefix = namespaces.getPrefix(namespaceUri);
if (nsPrefix == null)
{
throw new IllegalArgumentException("Namespace " + namespaceUri + " is not defined.");
}
if ("".equals(nsPrefix))
{
w.write(name);
openTags.push(new ElementLevel(namespaceUri, null, name, namespaces));
}
else
{
w.write(nsPrefix);
w.write(":");
w.write(name);
openTags.push(new ElementLevel(namespaceUri, nsPrefix, name, namespaces));
}
}
if (attributes != null)
{
final AttributeList.AttributeEntry[] entries = attributes.toArray();
for (int i = 0; i < entries.length; i++)
{
final AttributeList.AttributeEntry entry = entries[i];
w.write(" ");
buildAttributeName(entry, namespaces, w);
w.write("=\"");
writeTextNormalized(w, entry.getValue(), true);
w.write("\"");
}
}
if (close)
{
if (isHtmlCompatiblityMode())
{
w.write(" />");
}
else
{
w.write("/>");
}
openTags.pop();
doEndOfLine(w);
}
else
{
w.write(">");
doEndOfLine(w);
}
}
/**
* Conditionally writes an end-of-line character. The End-Of-Line is only written, if the tag description indicates
* that the currently open element does not expect any CDATA inside. Writing a newline for CDATA-elements may have
* sideeffects.
*
* @param w the writer.
* @throws java.io.IOException if there is an I/O problem.
*/
private void doEndOfLine(final Writer w)
throws IOException
{
if (openTags.isEmpty())
{
if (isWriteFinalLinebreak())
{
writeNewLine(w);
}
}
else
{
final ElementLevel level = (ElementLevel) openTags.peek();
if (getTagDescription().hasCData
(level.getNamespace(), level.getTagName()) == false)
{
writeNewLine(w);
}
}
}
/**
* Processes a single attribute and searches for namespace declarations. If a namespace declaration is found, it is
* returned in a normalized way. If namespace processing is active, the attribute name will be fully qualified with
* the prefix registered for the attribute's namespace URI.
*
* @param entry the attribute enty.
* @param namespaces the currently known namespaces.
* @param writer the writer that should receive the formatted attribute name.
* @throws IOException if an IO error occured.
*/
private void buildAttributeName(final AttributeList.AttributeEntry entry,
final DeclaredNamespaces namespaces,
final Writer writer) throws IOException
{
final ElementLevel currentElement = (ElementLevel) openTags.peek();
final String name = entry.getName();
final String namespaceUri = entry.getNamespace();
if (isAlwaysAddNamespace() == false &&
ObjectUtilities.equal(currentElement.getNamespace(), namespaceUri))
{
writer.write(name);
return;
}
if (namespaceUri == null)
{
writer.write(name);
return;
}
if (AttributeList.XMLNS_NAMESPACE.equals(namespaceUri))
{
// its a namespace declaration.
if ("".equals(name))
{
writer.write("xmlns");
return;
}
writer.write("xmlns:");
writer.write(name);
return;
}
final String namespacePrefix = namespaces.getPrefix(namespaceUri);
if (namespacePrefix != null && "".equals(namespacePrefix) == false)
{
writer.write(namespacePrefix);
writer.write(':');
writer.write(name);
}
else
{
writer.write(name);
}
}
/**
* Normalizes the given string using a shared buffer.
*
* @param s the string that should be XML-Encoded.
* @param transformNewLine a flag controling whether to transform newlines into character-entities.
* @return the transformed string.
*/
public String normalizeLocal(final String s,
final boolean transformNewLine)
{
if (s == null)
{
return "";
}
normalizeBuffer.delete(0, normalizeBuffer.length());
final int len = s.length();
for (int i = 0; i < len; i++)
{
final char ch = s.charAt(i);
switch (ch)
{
case '<':
{
normalizeBuffer.append("<");
break;
}
case '>':
{
normalizeBuffer.append(">");
break;
}
case '&':
{
normalizeBuffer.append("&");
break;
}
case '"':
{
normalizeBuffer.append(""");
break;
}
case '\n':
{
if (transformNewLine)
{
normalizeBuffer.append("
");
}
else
{
normalizeBuffer.append('\n');
}
break;
}
case '\r':
{
if (transformNewLine)
{
normalizeBuffer.append("
");
}
else
{
normalizeBuffer.append('\r');
}
break;
}
case 0x09:
{
normalizeBuffer.append(ch);
break;
}
default:
{
if (ch >= 0x20)
{
normalizeBuffer.append(ch);
}
}
}
}
final String retval = normalizeBuffer.toString();
normalizeBuffer.delete(0, normalizeBuffer.length());
return retval;
}
/**
* Normalizes the given string and writes the result directly to the stream.
*
* @param writer the writer that should receive the normalized content.
* @param s the string that should be XML-Encoded.
* @param transformNewLine a flag controling whether to transform newlines into character-entities.
* @throws IOException if writing to the stream failed.
*/
public void writeTextNormalized(final Writer writer,
final String s,
final boolean transformNewLine) throws IOException
{
if (s == null)
{
return;
}
final char[] data = s.toCharArray();
final int len = data.length;
int startIdx = 0;
int length = 0;
for (int i = 0; i < len; i++)
{
final char ch = data[i];
switch (ch)
{
case '<':
{
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
writer.write("<");
startIdx = i + 1;
continue;
}
case '>':
{
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
writer.write(">");
startIdx = i + 1;
continue;
}
case '&':
{
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
writer.write("&");
startIdx = i + 1;
continue;
}
case '"':
{
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
writer.write(""");
startIdx = i + 1;
continue;
}
case '\n':
{
if (transformNewLine)
{
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
writer.write("
");
startIdx = i + 1;
continue;
}
break;
}
case '\r':
{
if (transformNewLine)
{
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
writer.write("
");
startIdx = i + 1;
continue;
}
break;
}
case 0x09: // tab
{
break;
}
default:
{
if (this.encodingvalid && this.encoding != null)
{
try
{
if (charsetEncoder.canEncode(ch) == false)
{
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
writer.write("&#x");
final String charEncoded = Integer.toHexString(ch & 0x00ffffff);
final int fillUp = 4 - charEncoded.length();
for (int x = 0; x < fillUp; x++)
{
writer.write('0');
}
writer.write(charEncoded.toUpperCase());
writer.write(";");
startIdx = i + 1;
continue;
}
}
catch (IOException t)
{
throw t;
}
catch (Throwable t)
{
// ignore all other errors ..
}
}
if (ch >= 0x20)
{
// anything above the control-character range is ok.
break;
}
// skip ..
if (length != 0)
{
writer.write(data, startIdx, length);
length = 0;
}
startIdx = i + 1;
continue;
}
}
length += 1;
}
if (length != 0)
{
writer.write(data, startIdx, length);
}
}
/**
* Normalises a string, replacing certain characters with their escape sequences so that the XML text is not
* corrupted.
*
* @param s the string.
* @param transformNewLine true, if a newline in the string should be converted into a character entity.
* @return the normalised string.
*/
public static String normalize(final String s,
final boolean transformNewLine)
{
if (s == null)
{
return "";
}
final int len = s.length();
final StringBuffer str = new StringBuffer(len);
for (int i = 0; i < len; i++)
{
final char ch = s.charAt(i);
switch (ch)
{
case '<':
{
str.append("<");
break;
}
case '>':
{
str.append(">");
break;
}
case '&':
{
str.append("&");
break;
}
case '"':
{
str.append(""");
break;
}
case '\n':
{
if (transformNewLine)
{
str.append("
");
}
else
{
str.append('\n');
}
break;
}
case '\r':
{
if (transformNewLine)
{
str.append("
");
}
else
{
str.append('\r');
}
break;
}
case 0x09: // tab
{
str.append((char) 0x09);
}
default:
{
if (ch >= 0x20)
{
str.append(ch);
}
}
}
}
return (str.toString());
}
/**
* Indent the line. Called for proper indenting in various places.
*
* @param writer the writer which should receive the indentention.
* @throws java.io.IOException if writing the stream failed.
*/
public void indent(final Writer writer)
throws IOException
{
if (openTags.isEmpty())
{
for (int i = 0; i < additionalIndent; i++)
{
writer.write(this.indentString);
}
return;
}
final ElementLevel level = (ElementLevel) openTags.peek();
if (getTagDescription().hasCData(level.getNamespace(),
level.getTagName()) == false)
{
doEndOfLine(writer);
for (int i = 0; i < this.openTags.size(); i++)
{
writer.write(this.indentString);
}
for (int i = 0; i < additionalIndent; i++)
{
writer.write(this.indentString);
}
}
}
/**
* Indent the line. Called for proper indenting in various places.
*
* @param writer the writer which should receive the indentention.
* @throws java.io.IOException if writing the stream failed.
*/
public void indentForClose(final Writer writer)
throws IOException
{
if (openTags.isEmpty())
{
for (int i = 0; i < additionalIndent; i++)
{
writer.write(this.indentString);
}
return;
}
final ElementLevel level = (ElementLevel) openTags.peek();
if (getTagDescription().hasCData(level.getNamespace(),
level.getTagName()) == false)
{
doEndOfLine(writer);
for (int i = 1; i < this.openTags.size(); i++)
{
writer.write(this.indentString);
}
for (int i = 0; i < additionalIndent; i++)
{
writer.write(this.indentString);
}
}
}
/**
* Returns the list of safe tags.
*
* @return The list.
*/
public TagDescription getTagDescription()
{
return this.safeTags;
}
/**
* Writes a comment into the generated xml file.
*
* @param writer the writer.
* @param comment the comment text
* @throws IOException if there is a problem writing to the character stream.
*/
public void writeComment(final Writer writer, final String comment)
throws IOException
{
if (openTags.isEmpty() == false)
{
final ElementLevel level = (ElementLevel) openTags.peek();
if (getTagDescription().hasCData
(level.getNamespace(), level.getTagName()) == false)
{
indent(writer);
}
}
setLineEmpty(false);
writer.write("<!-- ");
writeTextNormalized(writer, comment, false);
writer.write(" -->");
doEndOfLine(writer);
}
/**
* Checks, whether attributes of the same namespace as the current element should be written without a prefix.
* Attributes without a prefix are considered to be not in any namespace at all. How to treat such attributes is
* implementation dependent. (Appendix A; Section 6.2 of the XmlNamespaces recommendation)
*
* @return true, if attributes in the element's namespace should be written without a prefix, false to write all
* attributes with a prefix.
*/
public boolean isAssumeDefaultNamespace()
{
return assumeDefaultNamespace;
}
/**
* Defines, whether attributes of the same namespace as the current element should be written without a prefix.
* Attributes without a prefix are considered to be not in any namespace at all. How to treat such attributes is
* implementation dependent. (Appendix A; Section 6.2 of the XmlNamespaces recommendation)
*
* @param assumeDefaultNamespace true, if attributes in the element's namespace should be written without a prefix,
* false to write all attributes with a prefix.
*/
public void setAssumeDefaultNamespace(final boolean assumeDefaultNamespace)
{
this.assumeDefaultNamespace = assumeDefaultNamespace;
}
/**
* Returns the current indention level.
*
* @return the indention level.
*/
public int getCurrentIndentLevel()
{
return additionalIndent + openTags.size();
}
/**
* Defines, whether the written XML file should end with an empty line.
*
* @param writeFinalLinebreak true, if an linebreak should be added at the
* end of the file, false otherwise.
*/
public void setWriteFinalLinebreak(final boolean writeFinalLinebreak)
{
this.writeFinalLinebreak = writeFinalLinebreak;
}
/**
* Checks, whether the written XML file should end with an empty line.
*
* @return true, if an linebreak should be added at the
* end of the file, false otherwise.
*/
public boolean isWriteFinalLinebreak()
{
return writeFinalLinebreak;
}
}