blob: eccc47541a4fffae21237ceee0f4a1111df6dc19 [file] [log] [blame]
/*
* @(#)$Id$
*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xalan" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 2001, Sun
* Microsystems., http://www.sun.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* @author Morten Jorgensen
* @author G. Todd Miller
* @author Santiago Pericas-Geertsen
*/
package org.apache.xalan.xsltc.runtime;
import java.io.IOException;
import java.io.Writer;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
public class DefaultSAXOutputHandler implements ContentHandler, LexicalHandler {
// The output writer
private Writer _writer;
// Contains end-tags for closing elements (for speeding up output)
private Hashtable _endTags = new Hashtable();
// Contains commonly used attributes (for speeding up output)
private Hashtable _attributeTemplates = new Hashtable();
// Contains all HTML elements that should not get an end tag
private Hashtable _emptyElements = new Hashtable();
// Contains the name of the last opened element (set in startElement())
private String _element = null;
// Settings passed on from TextOutput
private int _outputType = TextOutput.UNKNOWN;
private String _encoding = "utf-8";
private String _version = "1.0";
private String _standalone = null;
private boolean _indent = false;
private boolean _omitHeader = false;
// This variable is set to 'true' when a start tag is left open
private boolean _startTagOpen = false;
// Commonly used strings are stored as char arrays for speed
private static final char[] BEGPI = "<?".toCharArray();
private static final char[] ENDPI = "?>".toCharArray();
private static final char[] GT_CR = ">".toCharArray();
private static final char[] GT_LT_SL = "></".toCharArray();
private static final char[] SL_GT = "/>".toCharArray();
private static final char[] XMLNS = " xmlns".toCharArray();
// All of these are used to control/track output indentation
private static final char[] INDENT = " ".toCharArray();
private static final int MAX_INDENT_LEVEL = (INDENT.length >> 1);
private static final int MAX_INDENT = INDENT.length;
private static final String EMPTYSTRING = "";
private boolean _lineFeedNextStartTag = false;
private boolean _linefeedNextEndTag = false;
private boolean _indentNextEndTag = false;
private int _indentLevel = 0;
// This is used for aggregating namespace declarations
private AttributeList _namespaceDeclarations = new AttributeList();
/**
* Constructor - set Writer to send output to and output encoding
*/
public DefaultSAXOutputHandler(Writer writer, String encoding)
throws IOException {
_writer = writer;
_encoding = encoding;
init();
}
/**
* Constructor - simple, initially for use in servlets
*/
public DefaultSAXOutputHandler(Writer writer) throws IOException {
this(writer, "utf-8");
}
/**
* Constructor - set output-stream & output encoding.
*/
public DefaultSAXOutputHandler(OutputStream out, String encoding)
throws IOException {
OutputStreamWriter writer;
try {
writer = new OutputStreamWriter(out, _encoding = encoding);
}
catch (java.io.UnsupportedEncodingException e) {
writer = new OutputStreamWriter(out, _encoding = "utf-8" );
}
_writer = new BufferedWriter(writer);
init();
}
/**
* Constructor - set output file and output encoding
*/
public DefaultSAXOutputHandler(String filename, String encoding)
throws IOException {
this(new FileOutputStream(filename), encoding);
}
/**
* Utility method: Initialise the output handler
*/
private void init() throws IOException {
// These are HTML tags that can occur as empty elements with
// no closing tags (such as <br> insteadof XHTML's <br/>).
final String[] tags = { "area", "base", "basefont", "br",
"col", "frame", "hr", "img", "input",
"isindex", "link", "meta", "param" };
for (int i = 0; i < tags.length; i++)
_emptyElements.put(tags[i],tags[i]);
_endTags.clear();
_outputType = TextOutput.UNKNOWN;
_indent = false;
_indentNextEndTag = false;
_indentLevel = 0;
_startTagOpen = false;
}
/**
* Close the output stream
*/
public void close() {
try {
if (_writer != null) _writer.close();
}
catch (IOException e) {
// what can you do?
}
}
/**
* Utility method - outputs an XML header
*/
private void emitHeader() throws SAXException {
// First check if the 'omit-xml-declaration' was set to yes in the
// stylesheet's <xsl:output> element (if any)
if (_omitHeader) return;
// If not go ahead and output the XML header
StringBuffer buffer = new StringBuffer();
buffer.append("<?xml version=\"");
buffer.append(_version);
buffer.append("\" encoding=\"");
buffer.append(_encoding);
if (_standalone != null) {
buffer.append("\" standalone=\"");
buffer.append(_standalone);
}
buffer.append("\" ?>\n");
characters(buffer.toString());
}
/**
* Utility method - determine output type; XML or HTML
*/
private void determineOutputType(String element) throws SAXException {
// Assume this is an HTML document if first element is <html>
if ((element != null) && (element.toLowerCase().equals("html"))) {
_outputType = TextOutput.HTML;
}
// Otherwise we assume this is an XML document
else {
_outputType = TextOutput.XML;
emitHeader();
}
}
/**
* SAX2: Receive notification of the beginning of a document.
*/
public void startDocument() throws SAXException { }
/**
* SAX2: Receive notification of the end of an element.
*/
public void endDocument() throws SAXException {
try {
_writer.flush();
} catch (IOException e) {
throw new SAXException(e);
}
}
/**
* SAX2: Receive notification of the beginning of an element.
*/
public void startElement(String uri, String localname,
String elementName, Attributes attrs) throws
SAXException {
try {
// Determine the output document type if not already known
if (_outputType == TextOutput.UNKNOWN)
determineOutputType(elementName);
if (_startTagOpen) closeStartTag(true); // Close any open element.
_element = elementName; // Save element name
// Handle indentation (not a requirement)
if (_indent) {
if (!_emptyElements.containsKey(elementName.toLowerCase())) {
indent(_lineFeedNextStartTag);
_lineFeedNextStartTag = true;
_indentNextEndTag = false;
}
_indentLevel++;
}
// Now, finally, output the start tag for the element.
_writer.write('<');
_writer.write(elementName);
_startTagOpen = true;
_indentNextEndTag = false;
// Output namespace declarations first...
int declCount = _namespaceDeclarations.getLength();
for (int i=0; i<declCount; i++) {
final String prefix = _namespaceDeclarations.getQName(i);
_writer.write(XMLNS);
if ((prefix != null) && (prefix != EMPTYSTRING)) {
_writer.write(':');
_writer.write(prefix);
}
_writer.write('=');
_writer.write('\"');
_writer.write(_namespaceDeclarations.getValue(i));
_writer.write('\"');
}
_namespaceDeclarations.clear();
// ...then output all attributes
int attrCount = attrs.getLength();
for (int i=0; i<attrCount; i++) {
_writer.write(' ');
_writer.write(attrs.getQName(i));
_writer.write('=');
_writer.write('\"');
_writer.write(attrs.getValue(i));
_writer.write('\"');
}
} catch (IOException e) {
throw new SAXException(e);
}
}
/**
* SAX2: Receive notification of the end of an element.
*/
public void endElement(String uri, String localname,
String elementName) throws SAXException {
try {
if (_indent) _indentLevel--;
if (_startTagOpen) {
closeStartTag(false);
}
else {
if ((_indent) && (_indentNextEndTag)) {
indent(_indentNextEndTag);
_indentNextEndTag = true;
}
char[] endTag = (char[])_endTags.get(elementName);
if (endTag == null) {
// We dont' want to concatenate String objects!!!!
// endTag = ("</"+elementName+">").toCharArray();
final int len = elementName.length();
final char[] src = elementName.toCharArray();
endTag = new char[len+3];
System.arraycopy(src, 0, endTag, 2, len);
endTag[0] = '<';
endTag[1] = '/';
endTag[len+2] = '>';
_endTags.put(elementName,endTag);
}
_writer.write(endTag);
}
_indentNextEndTag = true;
} catch (IOException e) {
throw new SAXException(e);
}
}
/**
* Utility method - pass a string to the SAX handler's characters() method
*/
private void characters(String str) throws SAXException{
final char[] ch = str.toCharArray();
characters(ch, 0, ch.length);
}
/**
* Utility method - pass a whole character array to the SAX handler
*/
private void characters(char[] ch) throws SAXException{
characters(ch, 0, ch.length);
}
/**
* SAX2: Receive notification of character data.
*/
public void characters(char[] ch, int off, int len) throws SAXException {
try {
// Determine the output document type if not already known
if (_outputType == TextOutput.UNKNOWN)
determineOutputType(null);
if (len == 0) return;
// Close any open start-tags.
if (_startTagOpen) closeStartTag(true);
// Output text
_writer.write(ch, off, len);
}
catch (IOException e) {
throw new SAXException(e);
}
}
/**
* SAX2: Receive notification of a processing instruction.
*/
public void processingInstruction(String target, String data)
throws SAXException {
try {
if (_startTagOpen) closeStartTag(true);
_writer.write(BEGPI);
_writer.write(target);
_writer.write(' ');
_writer.write(data);
if (_outputType == TextOutput.HTML)
_writer.write('>');
else
_writer.write(ENDPI);
} catch (IOException e) {
throw new SAXException(e);
}
}
/**
* SAX2: Receive notification of ignorable whitespace in element content.
*/
public void ignorableWhitespace(char[] ch, int start, int len) { }
/**
* SAX2: Receive an object for locating the origin of SAX document events.
*/
public void setDocumentLocator(Locator locator) { }
/**
* SAX2: Receive notification of a skipped entity.
*/
public void skippedEntity(String name) { }
/**
* SAX2: Begin the scope of a prefix-URI Namespace mapping.
* Namespace declarations are output in startElement()
*/
public void startPrefixMapping(String prefix, String uri) {
_namespaceDeclarations.add(prefix,uri);
}
/**
* SAX2: End the scope of a prefix-URI Namespace mapping.
*/
public void endPrefixMapping(String prefix) {
// Do nothing
}
// The above are ignored methods of the org.xml.sax.ext.LexicalHandler intf.
public void startCDATA() { }
public void endCDATA() { }
public void comment(char[] ch, int start, int length) { }
public void startEntity(java.lang.String name) { }
public void endDTD() { }
public void endEntity(String name) { }
/**
* This method is part of the LexicalHandler interface. It is only used to
* pass DOCTYPE declarations (based on the doctype-system/public attributes
* in the <xsl:output> element) to the output handler.
* @param name The document type name (name of first element)
* @param publicId <xsl:output doctype-public="..."/>
* @param systemId <xsl:output doctype-system="..."/>
* @throws SAXException Whenever
*/
public void startDTD(String name, String publicId, String systemId)
throws SAXException {
try {
StringBuffer buf = new StringBuffer("<!DOCTYPE ");
buf.append(name);
if (publicId == null) {
buf.append(" SYSTEM");
}
else {
buf.append(" PUBLIC \"");
buf.append(publicId);
buf.append("\"");
}
if (systemId != null) {
buf.append(" \"");
buf.append(systemId);
buf.append("\">\n");
}
else {
buf.append(">\n");
}
_writer.write(buf.toString());
}
catch (IOException e) {
throw new SAXException(e);
}
}
/**
* Adds a newline in the output stream and indents to correct level
*/
private void indent(boolean linefeed) throws IOException {
if (linefeed)
_writer.write('\n');
if (_indentLevel < MAX_INDENT_LEVEL)
_writer.write(INDENT, 0, (_indentLevel+_indentLevel));
else
_writer.write(INDENT, 0, MAX_INDENT);
}
/**
* Closes a start tag of an element
*/
private void closeStartTag(boolean content) throws SAXException {
try {
// Take special care when outputting empty tags in HTML documents.
if (!content) {
if (_outputType == TextOutput.HTML) {
// HTML: output empty element as <tag> or <tag></tag>
if (!_emptyElements.containsKey(_element.toLowerCase())){
_writer.write(GT_LT_SL);
_writer.write(_element);
}
else {
_writer.write(GT_CR);
}
}
else {
// XML: output empty element as <tag/>
_writer.write(SL_GT);
}
}
else {
_writer.write('>');
}
_startTagOpen = false;
}
catch (IOException e) {
throw new SAXException(e);
}
}
/**
* Turns output indentation on/off (used with XML and HTML output only)
* Breaks the SAX HandlerBase interface, but TextOutput will only call
* this method of the SAX handler is an instance of this class.
*/
public void setIndent(boolean indent) {
_indent = indent;
}
/**
* Sets the version number that will be output in the XML header.
*/
public void setVersion(String version) {
_version = version;
}
/**
* Sets the 'standalone' attribute that will be output in the XML header.
* The attribute will be omitted unless this method is called.
*/
public void setStandalone(String standalone) {
_standalone = standalone;
}
/**
* Turns xml declaration generation on/off, dependent on the attribute
* omit-xml-declaration in any xsl:output element.
* Breaks the SAX HandlerBase interface, but TextOutput will only call
* this method of the SAX handler is an instance of this class.
*/
public void omitHeader(boolean value) {
_omitHeader = value;
}
/**
* Set the output type (either TEXT, HTML or XML)
* Breaks the SAX HandlerBase interface, but TextOutput will only call
* this method of the SAX handler is an instance of this class.
*/
public void setOutputType(int type) throws SAXException {
_outputType = type;
if (_outputType == TextOutput.XML ) {
emitHeader();
}
}
}