blob: 1a66b1941ae6c0334620134a91943ef30b72f2ef [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 Santiago Pericas-Geertsen
* @author G. Todd Miller
*
*/
package org.apache.xalan.xsltc.runtime.output;
import java.util.Vector;
import java.io.Writer;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import org.apache.xalan.xsltc.*;
import org.apache.xalan.xsltc.runtime.*;
import org.apache.xalan.xsltc.runtime.Hashtable;
public class StreamHTMLOutput extends StreamOutput {
private static final String HREF_STR = "href";
private static final String CITE_STR = "cite";
private static final String SRC_STR = "src";
private static final Hashtable _emptyElements = new Hashtable();
private static final String[] tags = { "area", "base", "basefont", "br",
"col", "frame", "hr", "img", "input",
"isindex", "link", "meta", "param" };
static {
for (int i = 0; i < tags.length; i++) {
_emptyElements.put(tags[i], "");
}
}
private boolean _headTagOpen = false;
private boolean _inStyleScript = false;
private String _mediaType = "text/html";
public StreamHTMLOutput(StreamOutput output) {
super(output);
_buffer = new WriterOutputBuffer(_writer);
setIndent(true); // default for HTML
initNamespaces();
// System.out.println("StreamHTMLOutput.<init> this = " + this);
}
public StreamHTMLOutput(Writer writer, String encoding) {
super(writer, encoding);
_buffer = new WriterOutputBuffer(_writer);
setIndent(true); // default for HTML
initNamespaces();
//System.out.println("StreamHTMLOutput.<init> this = " + this);
}
public StreamHTMLOutput(OutputStream out, String encoding)
throws IOException
{
super(out, encoding);
_buffer = new WriterOutputBuffer(_writer);
setIndent(true); // default for HTML
initNamespaces();
//System.out.println("StreamHTMLOutput.<init> this = " + this);
}
public void startDocument() throws TransletException {
// empty
}
public void endDocument() throws TransletException {
// Finally, output buffer to writer
outputBuffer();
}
public void startElement(String elementName) throws TransletException {
if (_startTagOpen) {
closeStartTag();
}
// Handle document type declaration (for first element only)
if (_firstElement) {
if (_doctypeSystem != null || _doctypePublic != null) {
appendDTD(elementName);
}
_firstElement = false;
}
if (_indent) {
if (!_emptyElements.containsKey(elementName.toLowerCase())) {
indent(_lineFeedNextStartTag);
_lineFeedNextStartTag = true;
_indentNextEndTag = false;
}
_indentLevel++;
}
_buffer.append('<').append(elementName);
_startTagOpen = true;
_indentNextEndTag = false;
if (elementName.equalsIgnoreCase("head")) {
_headTagOpen = true;
}
else if (elementName.equalsIgnoreCase("style") ||
elementName.equalsIgnoreCase("script"))
{
_inStyleScript = true;
}
}
public void endElement(String elementName)
throws TransletException
{
if (_inStyleScript &&
(elementName.equalsIgnoreCase("style") ||
elementName.equalsIgnoreCase("script")))
{
_inStyleScript = false;
}
if (_startTagOpen) {
appendAttributes();
if (_emptyElements.containsKey(elementName.toLowerCase())) {
_buffer.append('>');
}
else {
closeStartTag();
_buffer.append("</").append(elementName).append('>');
}
_startTagOpen = false;
if (_indent) {
_indentLevel--;
_lineFeedNextStartTag = _indentNextEndTag = false;
}
}
else {
if (_indent) {
_indentLevel--;
if (_indentNextEndTag) {
indent(_indentNextEndTag);
_lineFeedNextStartTag = _indentNextEndTag = true;
}
}
_buffer.append("</").append(elementName).append('>');
_indentNextEndTag = true;
}
}
public void characters(String characters)
throws TransletException
{
if (_startTagOpen) {
closeStartTag();
}
if (_escaping && !_inStyleScript) {
escapeCharacters(characters.toCharArray(), 0, characters.length());
}
else {
_buffer.append(characters);
}
}
public void characters(char[] characters, int offset, int length)
throws TransletException
{
if (_startTagOpen) {
closeStartTag();
}
if (_escaping && !_inStyleScript) {
escapeCharacters(characters, offset, length);
}
else {
_buffer.append(characters, offset, length);
}
}
public void attribute(String name, String value)
throws TransletException
{
// System.out.println("attribute = " + name + " " + value);
if (_startTagOpen) {
int k;
Attribute attr;
if (name.equalsIgnoreCase(HREF_STR) ||
name.equalsIgnoreCase(SRC_STR) ||
name.equals(CITE_STR))
{
attr = new Attribute(name, escapeURL(value));
}
else {
attr = new Attribute(name, escapeNonURL(value));
}
if ((k = _attributes.indexOf(attr)) >= 0) {
_attributes.setElementAt(attr, k);
}
else {
_attributes.add(attr);
}
}
}
public void comment(String comment) throws TransletException {
if (_startTagOpen) {
closeStartTag();
}
appendComment(comment);
}
public void processingInstruction(String target, String data)
throws TransletException
{
if (_startTagOpen) {
closeStartTag();
}
// Handle document type declaration
if (_firstElement) {
if (_doctypeSystem != null || _doctypePublic != null) {
appendDTD("html");
}
_firstElement = false;
}
// A PI in HTML ends with ">" instead of "?>"
_buffer.append("<?").append(target).append(' ')
.append(data).append('>');
}
public boolean setEscaping(boolean escape) throws TransletException
{
final boolean temp = _escaping;
_escaping = escape;
return temp;
}
public void close() {
try {
_writer.close();
}
catch (Exception e) {
// ignore
}
}
public void setCdataElements(Hashtable elements) {
// ignore when method type is HTML
}
public void setType(int type) {
// ignore: default is HTML
}
/**
* Set the output media type - only relevant for HTML output
*/
public void setMediaType(String mediaType) {
_mediaType = mediaType;
}
/**
* Escape non ASCII characters (> u007F) as &#XXX; entities.
*/
private String escapeNonURL(String base) {
final int length = base.length();
StringBuffer result = null;
for (int i = 0; i < length; i++){
final char ch = base.charAt(i);
if ((ch >= '\u007F' && ch < '\u00A0') ||
(_is8859Encoded && ch > '\u00FF'))
{
if (result == null) {
result = new StringBuffer((int) (1.2 * length));
result.append(base.substring(0, i));
}
result.append(CHAR_ESC_START)
.append(Integer.toString((int) ch))
.append(';');
}
else if (result != null) {
result.append(ch);
}
}
return (result == null) ? base : result.toString();
}
/*
private String escapeNonURL(String base) {
final int length = base.length();
final StringBuffer result = new StringBuffer();
for (int i = 0; i < length; i++){
final char ch = base.charAt(i);
if ((ch >= '\u007F' && ch < '\u00A0') ||
(_is8859Encoded && ch > '\u00FF'))
{
result.append(CHAR_ESC_START)
.append(Integer.toString((int) ch))
.append(';');
}
else {
result.append(ch);
}
}
return result.toString();
}
*/
/**
* This method escapes special characters used in HTML attribute values
*/
private String escapeURL(String base) {
final char[] chs = base.toCharArray();
final StringBuffer result = new StringBuffer();
final int length = chs.length;
for (int i = 0; i < length; i++) {
final char ch = chs[i];
if (ch <= 0x20) {
result.append('%').append(makeHHString(ch));
}
else if (ch > '\u007F') {
result.append('%')
.append(makeHHString((ch >> 6) | 0xC0))
.append('%')
.append(makeHHString((ch & 0x3F) | 0x80));
}
else {
// These chars are reserved or unsafe in URLs
switch (ch) {
case '\u007F' :
case '\u007B' :
case '\u007D' :
case '\u007C' :
case '\\' :
case '\t' :
case '\u005E' :
case '\u007E' :
case '\u005B' :
case '\u005D' :
case '\u0060' :
case '\u0020' :
result.append('%')
.append(Integer.toHexString((int) ch));
break;
case '"':
result.append("%22");
break;
default:
result.append(ch);
break;
}
}
}
return result.toString();
}
private String makeHHString(int i) {
final String s = Integer.toHexString(i).toUpperCase();
return (s.length() == 1) ? "0" + s : s;
}
/**
* Emit HTML meta info
*/
private void appendHeader() {
_buffer.append("<meta http-equiv=\"Content-Type\" content=\"")
.append(_mediaType).append("; charset=")
.append(_encoding).append("\">");
}
protected void closeStartTag() throws TransletException {
super.closeStartTag();
// Insert <META> tag directly after <HEAD> element in HTML output
if (_headTagOpen) {
appendHeader();
_headTagOpen = false;
}
}
/**
* This method escapes special characters used in text nodes
*/
protected void escapeCharacters(char[] ch, int off, int len) {
int limit = off + len;
int offset = off;
if (limit > ch.length) {
limit = ch.length;
}
// Step through characters and escape all special characters
for (int i = off; i < limit; i++) {
final char current = ch[i];
switch (current) {
case '&':
_buffer.append(ch, offset, i - offset).append(AMP);
offset = i + 1;
break;
case '<':
_buffer.append(ch, offset, i - offset).append(LT);
offset = i + 1;
break;
case '>':
_buffer.append(ch, offset, i - offset).append(GT);
offset = i + 1;
break;
case '\u00A0':
_buffer.append(ch, offset, i - offset).append(NBSP);
offset = i + 1;
break;
default:
if ((current >= '\u007F' && current < '\u00A0') ||
(_is8859Encoded && current > '\u00FF'))
{
_buffer.append(ch, offset, i - offset)
.append(CHAR_ESC_START)
.append(Integer.toString((int)ch[i]))
.append(';');
offset = i + 1;
}
}
}
// Output remaining characters (that do not need escaping).
if (offset < limit) {
_buffer.append(ch, offset, limit - offset);
}
}
}