| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cocoon.components.serializers; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.util.Arrays; |
| |
| import org.apache.avalon.excalibur.pool.Recyclable; |
| import org.apache.avalon.framework.configuration.Configurable; |
| import org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.cocoon.components.serializers.encoding.XMLEncoder; |
| import org.apache.cocoon.components.serializers.encoding.Charset; |
| import org.apache.cocoon.components.serializers.encoding.CharsetFactory; |
| import org.apache.cocoon.components.serializers.encoding.Encoder; |
| import org.apache.cocoon.components.serializers.util.Namespaces; |
| import org.apache.cocoon.serialization.Serializer; |
| import org.apache.commons.lang.SystemUtils; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * <p>An abstract serializer supporting multiple encodings.</p> |
| * |
| * <p>This serializer can accept the following configuration whenever it |
| * is declared into a sitemap:</p> |
| * |
| * <pre> |
| * <serializer class="org.apache.cocoon.components.serializers..." ... > |
| * <encoding>myencoding</encoding> |
| * <indent>myindenting</indent> |
| * <setContentLength>false</setContentLength> |
| * </serializer> |
| * </pre> |
| * |
| * <p>The value indicated by <i>myencoding</i> must be replaced with a valid |
| * charset encoding (this serializer does not rely on the JVM for character |
| * encoding, you can look into the <code>cocoon-serializers-charsets</code> |
| * JAR file for a list).<p> |
| * |
| * <p>The value indicated by <i>myindenting</i> will control the indenting |
| * level for each element.<p> |
| * |
| * <p>The value indicated by <em>setContentLength</em> determines if the serializer |
| * sets the <code>Content-Length</code> HTTP header. The parameter is optional, the |
| * default value is <em>false</em>. |
| * |
| * @version CVS $Id$ |
| */ |
| public abstract class EncodingSerializer implements Serializer, Locator, Recyclable, Configurable { |
| |
| /** The line separator string */ |
| private static final char S_EOL[] = SystemUtils.LINE_SEPARATOR.toCharArray(); |
| |
| /* ====================================================================== */ |
| |
| /** The position of the namespace URI in the attributes array. */ |
| public static final int ATTRIBUTE_NSURI = 0; |
| |
| /** The position of the local name in the attributes array. */ |
| public static final int ATTRIBUTE_LOCAL = 1; |
| |
| /** The position of the qualified name in the attributes array. */ |
| public static final int ATTRIBUTE_QNAME = 2; |
| |
| /** The position of the value in the attributes array. */ |
| public static final int ATTRIBUTE_VALUE = 3; |
| |
| /** The length of the array of strings representing an attribute. */ |
| public static final int ATTRIBUTE_LENGTH = 4; |
| |
| /* ====================================================================== */ |
| |
| /** Our <code>Encoder</code> instance. */ |
| private Encoder encoder = null; |
| |
| /** Our <code>Locator</code> instance. */ |
| private Locator locator = null; |
| |
| /** Our <code>Writer</code> instance. */ |
| private OutputStreamWriter out = null; |
| |
| /** Flag indicating if the document prolog is being processed. */ |
| private boolean prolog = true; |
| |
| /** Flag indicating if the document is being processed. */ |
| private boolean processing = false; |
| |
| /** Current nesting level */ |
| private int level = 0; |
| |
| /** Whitespace buffer for indentation */ |
| private char[] indentBuffer = null; |
| |
| /* ====================================================================== */ |
| |
| /** The <code>Charset</code> associated with the character encoding. */ |
| protected Charset charset = null; |
| |
| /** The <code>Namespace</code> associated with this instance. */ |
| protected Namespaces namespaces = new Namespaces(); |
| |
| /** Per level indent spaces */ |
| protected int indentPerLevel = 0; |
| /* ====================================================================== */ |
| |
| /** Determine if the serializer should set the Content-Length HTTP header. */ |
| private boolean setContentLength; |
| |
| /** |
| * Create a new instance of this <code>EncodingSerializer</code> |
| */ |
| protected EncodingSerializer(Encoder encoder) { |
| super(); |
| this.encoder = encoder; |
| this.recycle(); |
| } |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Test if the component wants to set the content length. |
| */ |
| public boolean shouldSetContentLength() { |
| return this.setContentLength; |
| } |
| |
| /** |
| * Reset this <code>EncodingSerializer</code>. |
| */ |
| public void recycle() { |
| if (processing) throw new IllegalStateException(); |
| this.namespaces = new Namespaces(); |
| this.locator = null; |
| this.out = null; |
| this.prolog = true; |
| if (this.encoder instanceof XMLEncoder) ((XMLEncoder) this.encoder).reset(); |
| } |
| |
| /** |
| * Set the <code>OutputStream</code> where this serializer will |
| * write data to. |
| * |
| * @param out The <code>OutputStream</code> used for output. |
| */ |
| public void setOutputStream(OutputStream out) |
| throws IOException { |
| if (out == null) throw new NullPointerException("Null output"); |
| |
| this.out = new OutputStreamWriter(out, this.charset.getName()); |
| } |
| |
| /** |
| * Set the configurations for this serializer. |
| */ |
| public void configure(Configuration conf) |
| throws ConfigurationException { |
| String encoding = conf.getChild("encoding").getValue(null); |
| try { |
| this.charset = CharsetFactory.newInstance().getCharset(encoding); |
| } catch (UnsupportedEncodingException exception) { |
| throw new ConfigurationException("Encoding not supported: " |
| + encoding, exception); |
| } |
| |
| indentPerLevel = conf.getChild("indent").getValueAsInteger(0); |
| if (indentPerLevel > 0) { |
| assureIndentBuffer(indentPerLevel * 6); |
| } |
| |
| this.setContentLength = conf.getChild("setContentLength").getValueAsBoolean(false); |
| } |
| |
| /* ====================================================================== */ |
| |
| private char[] assureIndentBuffer( int size ) { |
| if (indentBuffer == null || indentBuffer.length < size) { |
| indentBuffer = new char[size]; |
| Arrays.fill(indentBuffer,' '); |
| } |
| return indentBuffer; |
| } |
| |
| /** |
| * Encode and write a <code>String</code> |
| */ |
| protected void encode(String data) |
| throws SAXException { |
| char array[] = data.toCharArray(); |
| this.encode(array, 0, array.length); |
| } |
| |
| /** |
| * Encode and write an array of characters. |
| */ |
| protected void encode(char data[]) |
| throws SAXException { |
| this.encode(data, 0, data.length); |
| } |
| |
| /** |
| * Encode and write a specific part of an array of characters. |
| */ |
| protected void encode(char data[], int start, int length) |
| throws SAXException { |
| int end = start + length; |
| |
| if (data == null) throw new NullPointerException("Null data"); |
| if ((start < 0) || (start > data.length) || (length < 0) || |
| (end > data.length) || (end < 0)) |
| throw new IndexOutOfBoundsException("Invalid data"); |
| if (length == 0) return; |
| |
| for (int x = start; x < end; x++) { |
| char c = data[x]; |
| |
| if (this.charset.allows(c) && this.encoder.allows(c)) { |
| continue; |
| } |
| |
| if (start != x) this.write(data, start, x - start ); |
| this.write(this.encoder.encode(c)); |
| start = x + 1; |
| continue; |
| } |
| if (start != end) this.write(data, start, end - start ); |
| } |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Receive an object for locating the origin of SAX document events. |
| */ |
| public final void setDocumentLocator(Locator locator) { |
| this.locator = locator; |
| } |
| |
| /** |
| * Return the public identifier for the current document event. |
| * |
| * @return A <code>String</code> containing the public identifier, |
| * or <b>null</b> if none is available. |
| */ |
| public String getPublicId() { |
| return(this.locator == null? null: this.locator.getPublicId()); |
| } |
| |
| /** |
| * Return the system identifier for the current document event. |
| * |
| * @return A <code>String</code> containing the system identifier, |
| * or <b>null</b> if none is available. |
| */ |
| public String getSystemId() { |
| return(this.locator == null? null: this.locator.getSystemId()); |
| } |
| |
| /** |
| * Return the line number where the current document event ends. |
| * |
| * @return The line number, or -1 if none is available. |
| */ |
| public int getLineNumber() { |
| return(this.locator == null? -1: this.locator.getLineNumber()); |
| } |
| |
| /** |
| * Return the column number where the current document event ends. |
| * |
| * @return The column number, or -1 if none is available. |
| */ |
| public int getColumnNumber() { |
| return(this.locator == null? -1: this.locator.getColumnNumber()); |
| } |
| |
| /** |
| * Return a <code>String</code> describing the current location. |
| */ |
| protected String getLocation() { |
| if (this.locator == null) return(""); |
| StringBuffer buf = new StringBuffer(" ("); |
| if (this.getSystemId() != null) { |
| buf.append(this.getSystemId()); |
| buf.append(' '); |
| } |
| buf.append("line " + this.getLineNumber()); |
| buf.append(" col " + this.getColumnNumber()); |
| buf.append(')'); |
| return(buf.toString()); |
| } |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Flush the stream. |
| */ |
| protected void flush() |
| throws SAXException { |
| try { |
| this.out.flush(); |
| } catch (IOException e) { |
| throw new SAXException("I/O error flushing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write an array of characters. |
| */ |
| protected void write(char data[]) |
| throws SAXException { |
| try { |
| this.out.write(data, 0, data.length); |
| } catch (IOException e) { |
| throw new SAXException("I/O error writing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write a portion of an array of characters. |
| */ |
| protected void write(char data[], int start, int length) |
| throws SAXException { |
| try { |
| this.out.write(data, start, length); |
| } catch (IOException e) { |
| throw new SAXException("I/O error writing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write a single character. |
| */ |
| protected void write(int c) |
| throws SAXException { |
| try { |
| this.out.write(c); |
| } catch (IOException e) { |
| throw new SAXException("I/O error writing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write a string. |
| */ |
| protected void write(String data) |
| throws SAXException { |
| try { |
| this.out.write(data); |
| } catch (IOException e) { |
| throw new SAXException("I/O error writing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write a portion of a string. |
| */ |
| protected void write(String data, int start, int length) |
| throws SAXException { |
| try { |
| this.out.write(data, start, length); |
| } catch (IOException e) { |
| throw new SAXException("I/O error writing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write a end-of-line character. |
| */ |
| protected void writeln() |
| throws SAXException { |
| try { |
| this.out.write(S_EOL); |
| } catch (IOException e) { |
| throw new SAXException("I/O error writing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write a string and a end-of-line character. |
| */ |
| protected void writeln(String data) |
| throws SAXException { |
| try { |
| this.out.write(data); |
| this.out.write(S_EOL); |
| } catch (IOException e) { |
| throw new SAXException("I/O error writing: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Write out character to indent the output according |
| * to the level of nesting |
| * @param indent |
| * @throws SAXException |
| */ |
| protected void writeIndent(int indent) throws SAXException { |
| this.charactersImpl("\n".toCharArray(),0,1); |
| if (indent > 0) { |
| this.charactersImpl(assureIndentBuffer(indent),0,indent); |
| } |
| } |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Receive notification of the beginning of a document. |
| */ |
| public void startDocument() |
| throws SAXException { |
| this.processing = true; |
| this.level = 0; |
| } |
| |
| /** |
| * Receive notification of the end of a document. |
| */ |
| public void endDocument() |
| throws SAXException { |
| this.processing = false; |
| this.flush(); |
| } |
| |
| /** |
| * Begin the scope of a prefix-URI Namespace mapping. |
| */ |
| public void startPrefixMapping(String prefix, String uri) |
| throws SAXException { |
| this.namespaces.push(prefix, uri); |
| } |
| |
| /** |
| * End the scope of a prefix-URI mapping. |
| */ |
| public void endPrefixMapping(String prefix) |
| throws SAXException { |
| this.namespaces.pop(prefix); |
| } |
| |
| /** |
| * Receive notification of the beginning of an element. |
| */ |
| public void startElement(String nsuri, String local, String qual, |
| Attributes attributes) |
| throws SAXException { |
| if (indentPerLevel > 0) { |
| this.writeIndent(indentPerLevel*level); |
| level++; |
| } |
| |
| String name = this.namespaces.qualify(nsuri, local, qual); |
| |
| if (this.prolog) { |
| this.body(nsuri, local, name); |
| this.prolog = false; |
| } |
| |
| String ns[][] = this.namespaces.commit(); |
| |
| String at[][] = new String [attributes.getLength()][4]; |
| for (int x = 0; x < at.length; x++) { |
| at[x][ATTRIBUTE_NSURI] = attributes.getURI(x); |
| at[x][ATTRIBUTE_LOCAL] = attributes.getLocalName(x); |
| at[x][ATTRIBUTE_QNAME] = namespaces.qualify( |
| attributes.getURI(x), |
| attributes.getLocalName(x), |
| attributes.getQName(x)); |
| at[x][ATTRIBUTE_VALUE] = attributes.getValue(x); |
| } |
| |
| this.startElementImpl(nsuri, local, name, ns, at); |
| } |
| |
| public void characters (char ch[], int start, int length) |
| throws SAXException { |
| if (indentPerLevel > 0) { |
| this.writeIndent(indentPerLevel*level + 1); |
| } |
| this.charactersImpl(ch, start, length); |
| } |
| |
| /** |
| * Receive notification of the end of an element. |
| */ |
| public void endElement(String nsuri, String local, String qual) |
| throws SAXException { |
| if (indentPerLevel > 0) { |
| level--; |
| this.writeIndent(indentPerLevel*level); |
| } |
| |
| String name = this.namespaces.qualify(nsuri, local, qual); |
| this.endElementImpl(nsuri, local, name); |
| } |
| |
| /** |
| * Receive notification of the beginning of the document body. |
| * |
| * @param uri The namespace URI of the root element. |
| * @param local The local name of the root element. |
| * @param qual The fully-qualified name of the root element. |
| */ |
| public abstract void body(String uri, String local, String qual) |
| throws SAXException; |
| |
| /** |
| * Receive notification of the beginning of an element. |
| * |
| * @param uri The namespace URI of the root element. |
| * @param local The local name of the root element. |
| * @param qual The fully-qualified name of the root element. |
| * @param namespaces An array of <code>String</code> objects containing |
| * the namespaces to be declared by this element. |
| * @param attributes An array of <code>String</code> objects containing |
| * all attributes of this element. |
| */ |
| public abstract void startElementImpl(String uri, String local, String qual, |
| String namespaces[][], String attributes[][]) |
| throws SAXException; |
| |
| /** |
| * Receive character notifications |
| * @param ch |
| * @param start |
| * @param length |
| * @throws SAXException |
| */ |
| public abstract void charactersImpl (char ch[], int start, int length) |
| throws SAXException; |
| |
| /** |
| * Receive notification of the end of an element. |
| * |
| * @param uri The namespace URI of the root element. |
| * @param local The local name of the root element. |
| * @param qual The fully-qualified name of the root element. |
| */ |
| public abstract void endElementImpl(String uri, String local, String qual) |
| throws SAXException; |
| } |