package com.bradmcevoy.http;
import org.apache.commons.io.output.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bradmcevoy.io.FileUtils;
/**
* Lightweight XML generation. Gives the programmer fine grained control
* of the generated xml, including whitespace.
* <P/>
* The XML is not guaranteed to be parseable.
*
* @author brad
*/
public class XmlWriter {
private Logger log = LoggerFactory.getLogger(XmlWriter.class);
public enum Type {
OPENING,
CLOSING,
NO_CONTENT
};
protected final Writer writer;
public XmlWriter(OutputStream out) {
this.writer = new PrintWriter(out, true);
}
/**
* Append the given raw String to the ouput. No encoding is applied
*
* @param value
*/
private void append(String value) {
try {
writer.write(value);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* Append the given character to the output. No encoding is applied
*
* @param c
*/
private void append(char c) {
try {
writer.write((int) c);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* Convenience method to write a single element containing a piece of text
*
*
* @param namespace - optional, namespace prefix
* @param namespaceInfo - optional, namespace url
* @param name - the local name of the element to create
* @param value - the raw text to insert into the element
*/
public void writeProperty(String namespace, String namespaceInfo, String name, String value) {
writeElement(namespace, namespaceInfo, name, Type.OPENING);
append(value);
writeElement(namespace, namespaceInfo, name, Type.CLOSING);
}
public void writeProperty(String namespace, String name, String value) {
if (value == null) {
writeProperty(namespace, name);
} else {
writeElement(namespace, name, Type.OPENING);
append(value);
writeElement(namespace, name, Type.CLOSING);
}
}
public void writeProperty(String namespace, String name) {
writeElement(namespace, name, Type.NO_CONTENT);
}
public void writeProperty(String name) {
writeElement(null, name, Type.NO_CONTENT);
}
public void writeElement(String namespace, String name, Type type) {
writeElement(namespace, null, name, type);
}
/**
* Write an opening tag
*
* @param namespace
* @param name
*/
public void open(String namespace, String name) {
writeElement(namespace, name, Type.OPENING);
}
/**
* Write a closing tag, Eg </name>
*
* @param namespace
* @param name
*/
public void close(String namespace, String name) {
writeElement(namespace, name, Type.CLOSING);
}
/**
* Write an opening tag
*
* @param name
*/
public void open(String name) {
writeElement(null, name, Type.OPENING);
}
/**
* Write a closing tag for the given name
*
* @param name
*/
public void close(String name) {
writeElement(null, name, Type.CLOSING);
}
/**
* Represents an element which is currently being written
*
*/
public class Element {
private final Element parent;
private final String nsPrefix;
private final String name;
private boolean openEnded;
/**
* Create the element and write the first part of the opening tag
*
* Eg <name
*
* @param name
*/
Element(Element parent, String name) {
this(parent, null, name);
}
/**
* Create the element and write the first part of the opening tag
*
* Eg <name
*
* @param nsPrefix
* @param name
*/
Element(Element parent, String nsPrefix, String name) {
this.parent = parent;
this.name = name;
this.nsPrefix = nsPrefix;
append("<");
if (nsPrefix != null) {
append(nsPrefix);
append(":");
}
append(name);
}
/**
* Write a name/value attribute pair
*
* @param name
* @param value
* @return
*/
public Element writeAtt(String name, String value) {
append(" ");
append(name);
append("=");
append((char) 34);
append(value);
append((char) 34);
return this;
}
/**
* Write the text into the element. Will finish the opening tag if required
*
* @param text
* @return
*/
public Element writeText(String text) {
return writeText(text, true);
}
public Element writeText(String text, boolean newline) {
if (!openEnded) {
open(newline);
}
append(text);
return this;
}
/**
* Completes the opening tag which is started in the constructor. And
* writes a new line
*
* Eg >
*
* @return
*/
public Element open() {
return open(true);
}
public Element open(boolean newline) {
openEnded = true;
append(">");
if (newline) {
append("\n");
}
return this;
}
/**
* Closes the tag by determining its current state. Can close with a no-content
* tag </name> if no content has been written, or with write a close tag
*
* @return - the parent element
*/
public Element close() {
return close(false);
}
public Element close(boolean newline) {
if (openEnded) {
if (nsPrefix != null) {
append("</" + nsPrefix + ":" + name + ">\n");
} else {
append("</" + name + ">\n");
}
if (newline) {
append("\n");
}
return parent;
} else {
if (newline) {
append("\n");
}
return noContent();
}
}
/**
* Write a self closing tag, eg />
*
* @return - the parent element
*/
public Element noContent() {
append("/>\n");
return parent;
}
/**
* Start a new element, completing the open tag if required
*
* @param name
* @return
*/
public Element begin(String name) {
return begin(null, name);
}
public Element begin(String prefix, String name) {
if (!openEnded) {
open();
}
Element el = new Element(this, prefix, name);
return el;
}
/**
* Write a property element like -
* <name>value</name>
*
* @param name
* @param value
* @return
*/
public Element prop(String name, String value) {
begin(name).writeText(value,false).close(true);
return this;
}
public Element prop(String name, Integer value) {
if (value != null) {
prop(name, value.toString());
} else {
begin(name).noContent();
}
return this;
}
}
public Element begin(String name) {
Element el = new Element(null, name);
return el;
}
public Element begin(String nsPrefix, String name) {
Element el = new Element(null, nsPrefix, name);
return el;
}
public void writeElement(String nsPrefix, String nsUrl, String name, Type type) {
if ((nsPrefix != null) && (nsPrefix.length() > 0)) {
switch (type) {
case OPENING:
if (nsUrl != null) {
append("<" + nsPrefix + ":" + name + " xmlns:" + nsPrefix + "=\"" + nsUrl + "\">");
} else {
append("<" + nsPrefix + ":" + name + ">");
}
break;
case CLOSING:
append("</" + nsPrefix + ":" + name + ">\n");
break;
case NO_CONTENT:
default:
if (nsUrl != null) {
append("<" + nsPrefix + ":" + name + " xmlns:" + nsPrefix + "=\"" + nsUrl + "\"/>");
} else {
append("<" + nsPrefix + ":" + name + "/>");
}
break;
}
} else {
switch (type) {
case OPENING:
append("<" + name + ">");
break;
case CLOSING:
append("</" + name + ">\n");
break;
case NO_CONTENT:
default:
append("<" + name + "/>");
break;
}
}
}
/**
* Append plain text.
*
* @param text Text to append
*/
public void writeText(String text) {
append(text);
}
/**
* Write a CDATA segment.
*
* @param data Data to append
*/
public void writeData(String data) {
append("<![CDATA[" + data + "]]>");
}
public void writeXMLHeader() {
append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
}
/**
* Send data and reinitializes buffer.
*/
public void flush() {
try {
writer.flush();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public void sample(InputStream in) {
log.debug("outputting sample");
try {
ByteArrayOutputStream out = FileUtils.readIn(in);
writer.write(out.toString());
} catch (FileNotFoundException ex) {
log.error("", ex);
} catch (IOException ex) {
log.error("", ex);
} finally {
FileUtils.close(in);
}
}
public void newLine() {
append("\n");
}
}