![JAR search and dependency download from the Maven repository](/logo.png)
org.eigenbase.xom.XMLOutput Maven / Gradle / Ivy
The newest version!
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
package org.eigenbase.xom;
import java.io.*;
import java.util.Vector;
/**
* XMLOutput is a class which implements streaming XML output. Use this class
* to write XML to any streaming source. While the class itself is
* unstructured and doesn't enforce any DTD specification, use of the class
* does ensure that the output is syntactically valid XML.
*/
public class XMLOutput {
// This Writer is the underlying output stream to which all XML is
// written.
private PrintWriter out;
// The tagStack is maintained to check that tags are balanced.
private Vector tagStack;
// The class maintains an indentation level to improve output quality.
private int indent;
// The class also maintains the total number of tags written. This
// is used to monitor changes to the output
private int tagsWritten;
// This flag is set to true if the output should be compacted.
// Compacted output is free of extraneous whitespace and is designed
// for easier transport.
private boolean compact;
/** @see setIndentString */
private String indentString = "\t";
/** @see setGlob */
private boolean glob;
/**
* Whether we have started but not finished a start tag. This only happens
* if glob
is true. The start tag is automatically closed
* when we start a child node. If there are no child nodes, {@link #endTag}
* creates an empty tag.
*/
private boolean inTag;
/** @see #setAlwaysQuoteCData */
private boolean alwaysQuoteCData;
/** @see #setIgnorePcdata */
private boolean ignorePcdata;
/**
* Private helper function to display a degree of indentation
* @param out the PrintWriter to which to display output.
* @param indent the degree of indentation.
*/
private void displayIndent(PrintWriter out, int indent)
{
if(!compact) {
for (int i = 0; i < indent; i++) {
out.print(indentString);
}
}
}
/**
* Constructs a new XMLOutput based on any Writer.
* @param out the writer to which this XMLOutput generates results.
*/
public XMLOutput(Writer out)
{
this.out = new PrintWriter(out, true);
indent = 0;
tagsWritten = 0;
tagStack = new Vector();
}
/**
* Sets or unsets the compact mode. Compact mode causes the generated
* XML to be free of extraneous whitespace and other unnecessary
* characters.
*
* @param compact true to turn on compact mode, or false to turn it off.
*/
public void setCompact(boolean compact)
{
this.compact = compact;
}
public boolean getCompact()
{
return compact;
}
/**
* Sets the string to print for each level of indentation. The default is a
* tab. The value must not be null
. Set this to the empty
* string to achieve no indentation (note that {@link
* #setCompact}(true)
removes indentation and newlines).
*/
public void setIndentString(String indentString)
{
this.indentString = indentString;
}
/**
* Sets whether to detect that tags are empty.
*/
public void setGlob(boolean glob)
{
this.glob = glob;
}
/**
* Sets whether to always quote cdata segments (even if they don't contain
* special characters).
*/
public void setAlwaysQuoteCData(boolean alwaysQuoteCData)
{
this.alwaysQuoteCData = alwaysQuoteCData;
}
/**
* Sets whether to ignore unquoted text, such as whitespace.
*/
public void setIgnorePcdata(boolean ignorePcdata)
{
this.ignorePcdata = ignorePcdata;
}
public boolean getIgnorePcdata()
{
return ignorePcdata;
}
/**
* Sends a string directly to the output stream, without escaping any
* characters. Use with caution!
*/
public void print(String s)
{
out.print(s);
}
/**
* Start writing a new tag to the stream. The tag's name must be given and
* its attributes should be specified by a fully constructed AttrVector
* object.
* @param tagName the name of the tag to write.
* @param attributes an XMLAttrVector containing the attributes to include
* in the tag.
*/
public void beginTag(String tagName, XMLAttrVector attributes)
{
beginBeginTag(tagName);
if (attributes != null) {
attributes.display(out, indent);
}
endBeginTag(tagName);
}
public void beginBeginTag(String tagName)
{
if (inTag) {
// complete the parent's start tag
if (compact) {
out.print(">");
} else {
out.println(">");
}
inTag = false;
}
displayIndent(out, indent);
out.print("<");
out.print(tagName);
}
public void endBeginTag(String tagName)
{
if (glob) {
inTag = true;
} else if (compact) {
out.print(">");
} else {
out.println(">");
}
out.flush();
tagStack.addElement(tagName);
indent++;
tagsWritten++;
}
/**
* Write an attribute.
*/
public void attribute(String name, String value)
{
XMLUtil.printAtt(out, name, value);
}
/**
* If we are currently inside the start tag, finish it off.
*/
public void beginNode()
{
if (inTag) {
// complete the parent's start tag
if (compact) {
out.print(">");
} else {
out.println(">");
}
inTag = false;
}
}
/**
* Complete a tag. This outputs the end tag corresponding to the
* last exposed beginTag. The tag name must match the name of the
* corresponding beginTag.
* @param tagName the name of the end tag to write.
*/
public void endTag(String tagName)
{
// Check that the end tag matches the corresponding start tag
int stackSize = tagStack.size();
String matchTag = (String)(tagStack.elementAt(stackSize-1));
if(!tagName.equalsIgnoreCase(matchTag))
throw new AssertFailure(
"End tag <" + tagName + "> does not match " +
" start tag <" + matchTag + ">");
tagStack.removeElementAt(stackSize-1);
// Lower the indent and display the end tag
indent--;
if (inTag) {
// we're still in the start tag -- this element had no children
if (compact) {
out.print("/>");
} else {
out.println("/>");
}
inTag = false;
} else {
displayIndent(out, indent);
out.print("");
out.print(tagName);
if (compact) {
out.print(">");
} else {
out.println(">");
}
}
out.flush();
}
/**
* Write an empty tag to the stream. An empty tag is one with no
* tags inside it, although it may still have attributes.
* @param tagName the name of the empty tag.
* @param attributes an XMLAttrVector containing the attributes to
* include in the tag.
*/
public void emptyTag(String tagName, XMLAttrVector attributes)
{
if (inTag) {
// complete the parent's start tag
if (compact) {
out.print(">");
} else {
out.println(">");
}
inTag = false;
}
displayIndent(out, indent);
out.print("<");
out.print(tagName);
if(attributes != null) {
out.print(" ");
attributes.display(out, indent);
}
if(compact)
out.print("/>");
else
out.println("/>");
out.flush();
tagsWritten++;
}
/**
* Write a CDATA section. Such sections always appear on their own line.
* The nature in which the CDATA section is written depends on the actual
* string content with respect to these special characters/sequences:
*
* &
* "
* '
* <
* >
*
* Additionally, the sequence ]]>
is special.
*
* - Content containing no special characters will be left as-is.
*
- Content containing one or more special characters but not the
* sequence
]]>
will be enclosed in a CDATA section.
* - Content containing special characters AND at least one
*
]]>
sequence will be left as-is but have all of its
* special characters encoded as entities.
*
* These special treatment rules are required to allow cdata sections
* to contain XML strings which may themselves contain cdata sections.
* Traditional CDATA sections do not nest.
*/
public void cdata(String data)
{
cdata(data, false);
}
/**
* Writes a CDATA section (as {@link #cdata(String)}).
*
* @param data string to write
* @param quote if true, quote in a <![CDATA[
* ... ]]>
regardless of the content of
* data
; if false, quote only if the content needs it
*/
public void cdata(String data, boolean quote)
{
if (inTag) {
// complete the parent's start tag
if (compact) {
out.print(">");
} else {
out.println(">");
}
inTag = false;
}
if (data == null) {
data = "";
}
boolean specials = false;
boolean cdataEnd = false;
// Scan the string for special characters
// If special characters are found, scan the string for ']]>'
if(XOMUtil.stringHasXMLSpecials(data)) {
specials = true;
if(data.indexOf("]]>") > -1)
cdataEnd = true;
}
// Display the result
displayIndent(out, indent);
if (quote || alwaysQuoteCData) {
out.print("");
} else if (!specials && !cdataEnd) {
out.print(data);
} else {
XMLUtil.stringEncodeXML(data, out);
}
out.flush();
tagsWritten++;
}
/**
* Write a String tag; a tag containing nothing but a CDATA section.
*/
public void stringTag(String name, String data)
{
beginTag(name, null);
cdata(data);
endTag(name);
}
/**
* Write content.
*/
public void content(String content)
{
if(content != null) {
indent++;
LineNumberReader in = new LineNumberReader(new StringReader(content));
try {
String line;
while((line = in.readLine()) != null) {
displayIndent(out, indent);
out.println(line);
}
} catch (IOException ex) {
throw new AssertFailure(ex);
}
indent--;
out.flush();
}
tagsWritten++;
}
/**
* Write header. Use default version 1.0.
*/
public void header()
{
out.println("");
out.flush();
tagsWritten++;
}
/**
* Write header, take version as input.
*/
public void header(String version)
{
out.print("");
out.flush();
tagsWritten++;
}
/**
* Get the total number of tags written
* @return the total number of tags written to the XML stream.
*/
public int numTagsWritten()
{
return tagsWritten;
}
}
// End XMLOutput.java
© 2015 - 2025 Weber Informatics LLC | Privacy Policy