/* * 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); } } }