| /* |
| * 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.CharArrayWriter; |
| |
| import org.apache.cocoon.components.serializers.encoding.XMLEncoder; |
| import org.apache.cocoon.components.serializers.util.DocType; |
| import org.apache.cocoon.components.serializers.util.Namespaces; |
| import org.apache.commons.lang.SystemUtils; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * <p>A fancy XML serializer not relying on the JAXP API.</p> |
| * |
| * <p>For configuration options of this serializer, please look at the |
| * {@link EncodingSerializer}.</p> |
| * |
| * @version CVS $Id$ |
| */ |
| public class XMLSerializer extends EncodingSerializer { |
| |
| private static final XMLEncoder XML_ENCODER = new XMLEncoder(); |
| |
| private static final char S_EOL[] = SystemUtils.LINE_SEPARATOR.toCharArray(); |
| |
| private static final char S_DOCUMENT_1[] = "<?xml version=\"1.0".toCharArray(); |
| private static final char S_DOCUMENT_2[] = "\" encoding=\"".toCharArray(); |
| private static final char S_DOCUMENT_3[] = "\"?>".toCharArray(); |
| |
| private static final char S_ELEMENT_1[] = "=\"".toCharArray(); |
| private static final char S_ELEMENT_2[] = "</".toCharArray(); |
| private static final char S_ELEMENT_3[] = " />".toCharArray(); |
| private static final char S_ELEMENT_4[] = " xmlns".toCharArray(); |
| |
| private static final char S_CDATA_1[] = "<[CDATA[".toCharArray(); |
| private static final char S_CDATA_2[] = "]]>".toCharArray(); |
| |
| private static final char S_COMMENT_1[] = "<!--".toCharArray(); |
| private static final char S_COMMENT_2[] = "-->".toCharArray(); |
| |
| private static final char S_PROCINSTR_1[] = "<?".toCharArray(); |
| private static final char S_PROCINSTR_2[] = "?>".toCharArray(); |
| |
| private static final char C_LT = '<'; |
| private static final char C_GT = '>'; |
| private static final char C_SPACE = ' '; |
| private static final char C_QUOTE = '"'; |
| private static final char C_NSSEP = ':'; |
| |
| private static final boolean DEBUG = false; |
| |
| /* ====================================================================== */ |
| |
| /** Whether an element is left open like "<name ". */ |
| private boolean hanging_element = false; |
| |
| /** True if we are processing the prolog. */ |
| private boolean processing_prolog = true; |
| |
| /** True if we are processing the DTD. */ |
| private boolean processing_dtd = false; |
| |
| /** A <code>Writer</code> for prolog elements. */ |
| private PrologWriter prolog = new PrologWriter(); |
| |
| /* ====================================================================== */ |
| |
| /** The <code>DocType</code> instance representing the document. */ |
| protected DocType doctype = null; |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Create a new instance of this <code>XMLSerializer</code> |
| */ |
| public XMLSerializer() { |
| super(XML_ENCODER); |
| } |
| |
| /** |
| * Create a new instance of this <code>XMLSerializer</code> |
| */ |
| protected XMLSerializer(XMLEncoder encoder) { |
| super(encoder); |
| } |
| |
| /** |
| * Reset this <code>XMLSerializer</code>. |
| */ |
| public void recycle() { |
| super.recycle(); |
| this.doctype = null; |
| this.hanging_element = false; |
| this.processing_prolog = true; |
| this.processing_dtd = false; |
| if (this.prolog != null) this.prolog.reset(); |
| } |
| |
| /** |
| * Return the MIME Content-Type produced by this serializer. |
| */ |
| public String getMimeType() { |
| if (this.charset == null) return("text/xml"); |
| return("text/xml; charset=" + this.charset.getName()); |
| } |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Receive notification of the beginning of a document. |
| */ |
| public void startDocument() |
| throws SAXException { |
| super.startDocument(); |
| this.head(); |
| } |
| |
| /** |
| * Receive notification of the end of a document. |
| */ |
| public void endDocument() |
| throws SAXException { |
| this.writeln(); |
| super.endDocument(); |
| } |
| |
| /** |
| * Write the XML document header. |
| * <p> |
| * This method will write out the <code><?xml version="1.0" |
| * ...></code> header. |
| * </p> |
| */ |
| protected void head() |
| throws SAXException { |
| this.write(S_DOCUMENT_1); // [<?xml version="1.0] |
| if (this.charset != null) { |
| this.write(S_DOCUMENT_2); // [" encoding="] |
| this.write(this.charset.getName()); |
| } |
| this.write(S_DOCUMENT_3); // ["?>] |
| this.writeln(); |
| } |
| |
| /** |
| * Report the start of DTD declarations, if any. |
| */ |
| public void startDTD(String name, String public_id, String system_id) |
| throws SAXException { |
| this.processing_dtd = true; |
| this.doctype = new DocType(name, public_id, system_id); |
| } |
| |
| /** |
| * Report the start of DTD declarations, if any. |
| */ |
| public void endDTD() |
| throws SAXException { |
| this.processing_dtd = false; |
| } |
| |
| /** |
| * 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 void body(String uri, String local, String qual) |
| throws SAXException { |
| this.processing_prolog = false; |
| this.writeln(); |
| |
| /* We have a document type. */ |
| if (this.doctype != null) { |
| |
| String root_name = this.doctype.getName(); |
| /* Check the DTD and the root element */ |
| if (!root_name.equals(qual)) { |
| throw new SAXException("Root element name \"" + root_name |
| + "\" declared by document type declaration differs " |
| + "from actual root element name \"" + qual + "\""); |
| } |
| /* Output the <!DOCTYPE ...> declaration. */ |
| this.write(this.doctype.toString()); |
| } |
| |
| /* Output all PIs and comments we cached in the prolog */ |
| this.prolog.writeTo(this); |
| this.writeln(); |
| } |
| |
| /** |
| * 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 void startElementImpl(String uri, String local, String qual, |
| String namespaces[][], String attributes[][]) |
| throws SAXException { |
| this.closeElement(false); |
| this.write(C_LT); // [<] |
| if (DEBUG) { |
| this.write('['); |
| this.write(uri); |
| this.write(']'); |
| } |
| this.write(qual); |
| |
| for (int x = 0; x < namespaces.length; x++) { |
| this.write(S_ELEMENT_4); // [ xmlns] |
| if (namespaces[x][Namespaces.NAMESPACE_PREFIX].length() > 0) { |
| this.write(C_NSSEP); // [:] |
| this.write(namespaces[x][Namespaces.NAMESPACE_PREFIX]); |
| } |
| this.write(S_ELEMENT_1); // [="] |
| this.encode(namespaces[x][Namespaces.NAMESPACE_URI]); |
| this.write(C_QUOTE); // ["] |
| } |
| |
| for (int x = 0; x < attributes.length; x++) { |
| this.write(C_SPACE); // [ ] |
| if (DEBUG) { |
| this.write('['); |
| this.write(attributes[x][ATTRIBUTE_NSURI]); |
| this.write(']'); |
| } |
| this.write(attributes[x][ATTRIBUTE_QNAME]); |
| this.write(S_ELEMENT_1); // [="] |
| this.encode(attributes[x][ATTRIBUTE_VALUE]); |
| this.write(C_QUOTE); // ["] |
| } |
| |
| this.hanging_element = true; |
| } |
| |
| /** |
| * 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 void endElementImpl(String uri, String local, String qual) |
| throws SAXException { |
| if (closeElement(true)) return; |
| this.write(S_ELEMENT_2); // [</] |
| if (DEBUG) { |
| this.write('['); |
| this.write(uri); |
| this.write(']'); |
| } |
| this.write(qual); |
| this.write(C_GT); // [>] |
| } |
| |
| /** |
| * Write the end part of a start element (if necessary). |
| * |
| * @param end_element Whether this method was called because an element |
| * is being closed or not. |
| * @return <b>true</b> if this call successfully closed the element (and |
| * no further <code></element></code> is required. |
| */ |
| protected boolean closeElement(boolean end_element) |
| throws SAXException { |
| if (!hanging_element) return false; |
| if (end_element) this.write(S_ELEMENT_3); // [ />] |
| else this.write(C_GT); // [>] |
| this.hanging_element = false; |
| return true; |
| } |
| |
| /** |
| * Report the start of a CDATA section. |
| */ |
| public void startCDATA() |
| throws SAXException { |
| if (this.processing_prolog) return; |
| this.closeElement(false); |
| this.write(S_CDATA_1); // [<[CDATA[] |
| } |
| |
| /** |
| * Report the end of a CDATA section. |
| */ |
| public void endCDATA() |
| throws SAXException { |
| if (this.processing_prolog) return; |
| this.closeElement(false); |
| this.write(S_CDATA_2); // []]>] |
| } |
| |
| /** |
| * Receive notification of character data. |
| */ |
| public void charactersImpl(char data[], int start, int length) |
| throws SAXException { |
| if (this.processing_prolog) return; |
| this.closeElement(false); |
| this.encode(data, start, length); |
| } |
| |
| /** |
| * Receive notification of ignorable whitespace in element content. |
| */ |
| public void ignorableWhitespace(char data[], int start, int length) |
| throws SAXException { |
| this.charactersImpl(data, start, length); |
| } |
| |
| /** |
| * Report an XML comment anywhere in the document. |
| */ |
| public void comment(char data[], int start, int length) |
| throws SAXException { |
| if (this.processing_dtd) return; |
| |
| if (this.processing_prolog) { |
| this.prolog.write(S_COMMENT_1); // [<!--] |
| this.prolog.write(data, start, length); |
| this.prolog.write(S_COMMENT_2); // [-->] |
| this.prolog.write(S_EOL); |
| return; |
| } |
| |
| this.closeElement(false); |
| this.write(S_COMMENT_1); // [<!--] |
| this.write(data, start, length); |
| this.write(S_COMMENT_2); // [-->] |
| } |
| |
| /** |
| * Receive notification of a processing instruction. |
| */ |
| public void processingInstruction(String target, String data) |
| throws SAXException { |
| if (this.processing_dtd) return; |
| |
| if (this.processing_prolog) { |
| this.prolog.write(S_PROCINSTR_1); // [<?] |
| this.prolog.write(target); |
| if (data != null) { |
| this.prolog.write(C_SPACE); // [ ] |
| this.prolog.write(data); |
| } |
| this.prolog.write(S_PROCINSTR_2); // [?>] |
| this.prolog.write(S_EOL); |
| return; |
| } |
| |
| this.closeElement(false); |
| |
| this.write(S_PROCINSTR_1); // [<?] |
| this.write(target); |
| if (data != null) { |
| this.write(C_SPACE); // [ ] |
| this.write(data); |
| } |
| this.write(S_PROCINSTR_2); // [?>] |
| } |
| |
| /** |
| * Report the beginning of some internal and external XML entities. |
| */ |
| public void startEntity(String name) |
| throws SAXException { |
| } |
| |
| /** |
| * Report the end of an entity. |
| */ |
| public void endEntity(String name) |
| throws SAXException { |
| } |
| |
| /** |
| * Receive notification of a skipped entity. |
| */ |
| public void skippedEntity(String name) |
| throws SAXException { |
| } |
| |
| /* ====================================================================== */ |
| |
| /** |
| * The <code>PrologWriter</code> is a simple extension to a |
| * <code>CharArrayWriter</code>. |
| */ |
| private static final class PrologWriter extends CharArrayWriter { |
| |
| /** Create a new <code>PrologWriter</code> instance. */ |
| private PrologWriter() { |
| super(); |
| } |
| |
| /** |
| * Write an array of characters. |
| * <p> |
| * The <code>CharArrayWriter</code> implementation of this method |
| * throws an unwanted <code>IOException</code>. |
| * </p> |
| */ |
| public void write(char c[]) { |
| this.write(c, 0, c.length); |
| } |
| |
| /** |
| * Write a <code>String</code>. |
| * <p> |
| * The <code>CharArrayWriter</code> implementation of this method |
| * throws an unwanted <code>IOException</code>. |
| * </p> |
| */ |
| public void write(String str) { |
| this.write(str, 0, str.length()); |
| } |
| |
| /** |
| * Write our contents to a <code>BaseSerializer</code> without |
| * copying the buffer. |
| */ |
| public void writeTo(XMLSerializer serializer) |
| throws SAXException { |
| serializer.write(this.buf, 0, this.count); |
| } |
| } |
| } |