// $Id: XMLOutputHandler.java 97 2005-02-28 21:18:32Z blindsey $
package com.jclark.xsl.sax2;
import com.jclark.xsl.sax.CommentHandler;
import com.jclark.xsl.sax.Destination;
import org.xml.sax.*;
import java.io.CharConversionException;
import java.io.OutputStream;
import java.io.IOException;
import java.util.Properties;
/**
* A ContentHandler that writes an XML representation to
* an OutputStream.
*/
public class XMLOutputHandler implements OutputContentHandler,
CommentHandler,
RawCharactersHandler
{
private OutputStream out = null;
private boolean keepOpen;
private boolean inStartTag = false;
private boolean omitXmlDeclaration = false;
private String standalone;
private static final int DEFAULT_BUF_LENGTH = 8*1024;
private byte[] buf = new byte[DEFAULT_BUF_LENGTH];
private int bufUsed = 0;
private String lineSeparator;
private byte minimize = MINIMIZE_EMPTY_ELEMENTS;
private String doctypeSystem;
private String doctypePublic;
private boolean outputDoctype = false;
static final public byte MINIMIZE_NONE = 0;
static final public byte MINIMIZE_EMPTY_ELEMENTS = 1;
static final public byte MINIMIZE_EMPTY_ELEMENTS_HTML = 2;
/**
* Create a XMLOutputHandler that will write in UTF-8
* to an OutputStream.
*/
public XMLOutputHandler()
{
lineSeparator = System.getProperty("line.separator");
}
public XMLOutputHandler(OutputStream out)
{
this();
this.out = out;
}
/**
* return <code>this</code> intialized for writing to the
* output represented by <code>dest</code> with the output
* parameters found in <code>props</code>.
*/
public ContentHandler init(Destination dest, Properties props)
throws IOException
{
// FIXME: we must support at least utf-16 in addition to utf-8
// FIXME: check here if we need to swap in a Writer if we
// havent been provided with an OutputStream
this.out = dest.getOutputStream("application/xml", null);
this.keepOpen = dest.keepOpen();
if ("yes".equals(props.getProperty("omit-xml-declaration"))) {
omitXmlDeclaration = true;
}
this.standalone = props.getProperty("standalone");
this.doctypeSystem = props.getProperty("doctype-system");
this.doctypePublic = props.getProperty("doctype-public");
if (this.doctypeSystem != null || this.doctypePublic != null) {
outputDoctype = true;
}
if ("yes".equals(props.getProperty("indent"))) {
return new Indenter(this, this);
}
return this;
}
public void setMinimize(byte minimize)
{
this.minimize = minimize;
}
public void startDocument() throws SAXException
{
if (!omitXmlDeclaration) {
writeRaw("<?xml version=\"1.0\" encoding=\"utf-8\"");
if (standalone != null) {
writeRaw(" standalone=\"");
writeRaw(standalone);
put((byte)'"');
}
writeRaw("?>");
writeRaw(lineSeparator);
}
}
public void characters(char cbuf[], int off, int len) throws SAXException
{
if (len == 0) {
return;
}
if (inStartTag) {
finishStartTag();
}
do {
char c = cbuf[off++];
switch (c) {
case '\n':
writeRaw(lineSeparator);
break;
case '&':
writeRaw("&");
break;
case '<':
writeRaw("<");
break;
case '>':
writeRaw(">");
break;
default:
if (c < 0x80)
put((byte)c);
else {
try {
writeMB(c);
}
catch (CharConversionException e) {
if (len-- == 0) {
throw new SAXException(e);
}
writeSurrogatePair(cbuf[off - 1], cbuf[off]);
off++;
}
}
}
} while (--len > 0);
}
public void rawCharacters(String chars) throws SAXException
{
if (inStartTag) {
finishStartTag();
}
writeRaw(chars);
}
public void ignorableWhitespace (char ch[], int start, int length)
throws SAXException
{
for (; length > 0; length--, start++) {
put( (byte) ch[start] );
}
}
private void writeRaw(String str) throws SAXException
{
final int n = str.length();
for (int i = 0; i < n; i++) {
char c = str.charAt(i);
if (c < 0x80)
put((byte)c);
else {
try {
writeMB(str.charAt(i));
}
catch (CharConversionException e) {
if (++i == n)
throw new SAXException(e.getMessage());
writeSurrogatePair(c, str.charAt(i));
}
}
}
}
private final void writeMB(char c)
throws SAXException,
CharConversionException
{
switch (c & 0xF800) {
case 0:
put((byte)(((c >> 6) & 0x1F) | 0xC0));
put((byte)((c & 0x3F) | 0x80));
break;
default:
put((byte)(((c >> 12) & 0xF) | 0xE0));
put((byte)(((c >> 6) & 0x3F) | 0x80));
put((byte)((c & 0x3F) | 0x80));
break;
case 0xD800:
throw new CharConversionException("invalid surrogate pair");
}
}
private final void writeSurrogatePair(char c1, char c2)
throws SAXException
{
if ((c1 & 0xFC00) != 0xD800 || (c2 & 0xFC00) != 0xDC00) {
throw new SAXException("invalid surrogate pair");
}
int c = ((c1 & 0x3FF) << 10) | (c2 & 0x3FF);
c += 0x10000;
put((byte)(((c >> 18) & 0x7) | 0xF0));
put((byte)(((c >> 12) & 0x3F) | 0x80));
put((byte)(((c >> 6) & 0x3F) | 0x80));
put((byte)((c & 0x3F) | 0x80));
}
public void startElement(String namespace, String localName,
String qName,
Attributes atts) throws SAXException
{
// N.B. expects to receive ns decls as attributes
if (inStartTag) {
finishStartTag();
}
if (outputDoctype) {
outputDoctype = false;
writeRaw("<!DOCTYPE ");
writeRaw(qName);
if (doctypePublic != null) {
writeRaw(" PUBLIC ");
byte lit = doctypePublic.indexOf('"') >= 0 ?
(byte)'\'' :
(byte)'"';
put(lit);
writeRaw(doctypePublic);
put(lit);
} else {
writeRaw(" SYSTEM");
}
if (doctypeSystem != null) {
byte lit = doctypeSystem.indexOf('"') >= 0 ?
(byte)'\'' :
(byte)'"';
put((byte)' ');
put(lit);
writeRaw(doctypeSystem);
put(lit);
}
put((byte)'>');
writeRaw(lineSeparator);
}
put((byte)'<');
writeRaw(qName);
int n = atts.getLength();
for (int i = 0; i < n; i++) {
put((byte)' ');
writeRaw(atts.getQName(i));
put((byte)'=');
put((byte)'"');
attributeValue(atts.getValue(i));
put((byte)'"');
}
inStartTag = true;
}
protected void attributeValue(String value)
throws SAXException
{
int valueLength = value.length();
for (int j = 0; j < valueLength; j++) {
char c = value.charAt(j);
switch (c) {
case '\n':
writeRaw(" ");
break;
case '&':
writeRaw("&");
break;
case '<':
writeRaw("<");
break;
case '"':
writeRaw(""");
break;
case '\r':
writeRaw(" ");
break;
case '\t':
writeRaw("	");
break;
default:
if (c < 0x80)
put((byte)c);
else {
try {
writeMB(c);
}
catch (CharConversionException e) {
if (++j == valueLength)
throw new SAXException(e.getMessage());
writeSurrogatePair(value.charAt(j - 1), value.charAt(j));
}
}
break;
}
}
}
private final void finishStartTag() throws SAXException
{
inStartTag = false;
put((byte)'>');
}
public void endElement(String namespace, String localName,
String qName) throws SAXException
{
if (inStartTag) {
inStartTag = false;
if (minimize != MINIMIZE_NONE) {
if (minimize == MINIMIZE_EMPTY_ELEMENTS_HTML)
put((byte)' ');
put((byte)'/');
put((byte)'>');
return;
}
put((byte)'>');
}
put((byte)'<');
put((byte)'/');
writeRaw(qName);
put((byte)'>');
}
public void processingInstruction(String target, String data)
throws SAXException
{
if (target == null) {
comment(data);
return;
}
if (inStartTag) {
finishStartTag();
}
put((byte)'<');
put((byte)'?');
writeRaw(target);
if (data.length() > 0) {
put((byte)' ');
writeMarkup(data);
}
put((byte)'?');
put((byte)'>');
}
public void markup(String chars) throws SAXException
{
if (inStartTag) {
finishStartTag();
}
writeMarkup(chars);
}
public void comment(String body) throws SAXException
{
if (inStartTag) {
finishStartTag();
}
writeRaw("<!--");
writeMarkup(body);
writeRaw("-->");
}
private void writeMarkup(String str) throws SAXException
{
int len = str.length();
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
if (c == '\n') {
writeRaw(lineSeparator);
} else if (c < 0x80) {
put((byte)c);
} else {
try {
writeMB(c);
}
catch (CharConversionException e) {
if (++i == len) {
throw new SAXException(e);
}
writeSurrogatePair(c, str.charAt(i));
}
}
}
}
private final void put(byte b) throws SAXException
{
if (bufUsed == buf.length) {
flushBuf();
}
buf[bufUsed++] = b;
}
private final void flushBuf() throws SAXException
{
try {
out.write(buf, 0, bufUsed);
bufUsed = 0;
}
catch (java.io.IOException e) {
throw new SAXException(e);
}
}
public void startPrefixMapping(String prefix, String namespaceURI)
{}
public void endPrefixMapping(String prefix)
{}
public void skippedEntity(String name)
{}
public void setDocumentLocator(Locator loc)
{ }
public void endDocument() throws SAXException
{
if (bufUsed != 0) {
flushBuf();
}
try {
if (out != null) {
if (keepOpen) {
out.flush();
} else {
out.close();
}
out = null;
}
}
catch (java.io.IOException e) {
throw new SAXException(e);
}
out = null;
buf = null;
}
}