com.bigdata.rdf.sail.webapp.XMLBuilder Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.rdf.sail.webapp;
import java.io.IOException;
import java.io.Writer;
import com.bigdata.util.HTMLUtility;
/**
* Utility Java class for outputting XML.
*
* The motivation is to provide a similar interface to a document builder but
* instead of building an in-memory model, to directly stream the output.
*
* Example usage:
*
*
* Content
*
* Content
*
* Content
*
*
*
* XMLBuilder.Node closed = new XMLBuilder(false,writer)
* .root("xml")
* .node("div")
* .attr("attr", "attr1")
* .text("Content")
* .close()
* .node("div", "Content")
* .node("div")
* .node("div", "Content")
* .close()
* .close();
*
* // The close on the root node will return null
*
* assert(closed == null);
*
*
* @author Martyn Cutcher
*/
public class XMLBuilder {
private final boolean xml;
private final Writer m_writer;
// private boolean m_pp = false;
// public XMLBuilder() throws IOException {
//
// this(true, (OutputStream) null);
//
// }
//
// public XMLBuilder(boolean xml) throws IOException {
//
// this(xml, (OutputStream) null);
//
// }
//
// public XMLBuilder(final boolean xml, final OutputStream outstr)
// throws IOException {
//
// this(xml, null/* encoding */, outstr);
//
// }
//
// public XMLBuilder(final boolean xml, final String encoding)
// throws IOException {
//
// this(xml, encoding, (OutputStream) null);
//
// }
/**
* Return the backing {@link Writer}.
*/
public Writer getWriter() {
return m_writer;
}
public XMLBuilder(final Writer w) throws IOException {
this(true/* xml */, null/* encoding */, w/* writer */);
}
public XMLBuilder(final boolean xml, final String encoding,
final Writer w) throws IOException {
if(w == null)
throw new IllegalArgumentException();
this.xml = xml;
this.m_writer = w;
if (xml) {
if (encoding != null) {
m_writer.write("");
} else {
m_writer.write("");
}
} else {
/*
* Note: The optional encoding should also be included in a meta tag
* for an HTML document.
*/
m_writer.write("");
}
}
// public void prettyPrint(boolean pp) {
// m_pp = pp;
// }
// private void initWriter(OutputStream outstr) {
// }
// Note: This method assumed that m_writer was a StringWriter!!!
// public String toString() {
// return m_writer.toString();
// }
public Node root(String name) throws IOException {
return new Node(name, null);
}
public Node root(String name, String nodeText) throws IOException {
Node root = new Node(name, null);
root.text(nodeText);
return root.close();
}
public void closeAll(Node node) throws IOException {
while (node != null) {
node = node.close();
}
}
public class Node {
private boolean m_open = true;
private String m_tag;
private Node m_parent;
private int m_attrs = 0;
private int m_text = 0;
private int m_nodes = 0;
Node(final String name, final Node parent) throws IOException {
m_tag = name;
m_parent = parent;
m_writer.write("<" + m_tag);
}
public String toString() {
return "<"
+ m_tag
+ "...>"
+ (m_parent == null ? "" : "(parent=" + m_parent.m_tag
+ ")");
}
/**
* Return the {@link XMLBuilder} to which this {@link Node} belongs.
*/
public XMLBuilder getBuilder() {
return XMLBuilder.this;
}
/**
* Add an attribute value to an open element head.
*
* @param attr
* The name of the attribute.
* @param value
* The value of the attribute, which will be automatically
* encoded.
*
* @return This {@link Node}
*
* @throws IOException
*/
public Node attr(final String attr, final Object value)
throws IOException {
m_writer.write(" " + attr + "=\"" + attrib(value.toString()) + "\"");
m_attrs++;
return this;
}
/**
* Write out the text.
*
* The head for the current element is first closed if it was still
* open. Then the text is then encoded and written out.
*
* This does not generate the closing tag for the node. More text or
* embedded nodes can still be output.
*
* @param text
* The text, which will be automatically encoded in a manner
* suitable for a CDATA section.
*
* @return This {@link Node}
*
* @throws IOException
*/
public Node text(final String text) throws IOException {
closeHead();
m_writer.write(cdata(text));
m_text++;
return this;
}
/**
* Write out the text without encoding for (X)HTML.
*
* The head for the current element is first closed if it was still
* open. Then the text is NOT encoded before written out.
*
* This does not generate the closing tag for the node. More text or
* embedded nodes can still be output.
*
* @param text
* The text, which will be automatically encoded in a manner
* suitable for a CDATA section.
*
* @return This {@link Node}
*
* @throws IOException
*/
public Node textNoEncode(final String text) throws IOException {
closeHead();
m_writer.write(text);
m_text++;
return this;
}
/**
* Create and return a new {@link Node}. The head of the {@link Node}
* will be open. Attributes may be specified until the closing tag is
* generated by {@link Node#close()} or until the head of the node is
* closed by {@link Node#text(String)}.
*
* @param tag
* The tag for that {@link Node}.
*
* @return The new {@link Node}.
*
* @throws IOException
*/
public Node node(final String tag) throws IOException {
closeHead();
m_nodes++;
return new Node(tag, this);
}
/**
* Generate an open element for the tag, output the indicated text, and
* then output the closing element for that tag.
*
* @param tag
* The tag (element name).
* @param text
* The text, which will be automatically encoded as
* appropriate for a CDATA section.
*
* @return This {@link Node}.
*
* @throws IOException
*/
public Node node(final String tag, final String text)
throws IOException {
closeHead();
m_nodes++;
final Node tmp = new Node(tag, this);
tmp.text(text);
final Node ret = tmp.close();
assert ret == this;
return ret;
}
/**
* Close the open element.
*
* @return The parent element.
*
* @throws IOException
*/
public Node close() throws IOException {
if(xml) {
// Always do an END element for XML.
return close(false/*simpleEnd*/);
}
return close(isSimpleEndTagForHTML());
}
/**
* Return true
if this is an HTML element which can be
* closed without an XML style end tag.
*
* TODO This does not cover all cases by a long shot. It just covers the
* ones that we are currently using.
*
* TODO This should be done with a hash map for faster lookup once there
* are more tags that we recognize in this method.
*/
private boolean isSimpleEndTagForHTML() {
if ("form".equalsIgnoreCase(m_tag)) {
return false;
}
return true;
}
/**
* Close the open element.
*
* @param simpleEnd
* When true
an open tag without a body will be
* closed by a single > symbol rather than the XML style
* />.
*
* @return The parent element.
* @throws IOException
*/
public Node close(final boolean simpleEnd) throws IOException {
assert(m_open);
if (emptyBody()) {
if(simpleEnd) {
m_writer.write(">");
} else {
m_writer.write("/>");
}
} else {
m_writer.write("" + m_tag + "\n>");
}
m_open = false;
// If root node then flush the writer
if (m_parent == null) {
m_writer.flush();
}
return m_parent;
}
private void closeHead() throws IOException {
if (emptyBody()) {
m_writer.write(">");
}
}
private boolean emptyBody() {
return m_nodes == 0 && m_text == 0;
}
}
/**
* Encode a string for including in a CDATA section.
*
* @param s
* The string.
*
* @return The encoded string.
*/
static private String cdata(final String s) {
if (s == null)
throw new IllegalArgumentException();
return HTMLUtility.escapeForXHTML(s);
}
/**
* Encoding a string for including in an (X)HTML attribute value.
*
* @param s
* The string.
*
* @return
*/
static private String attrib(final String s) {
return HTMLUtility.escapeForXHTML(s);
}
}