| /* |
| * 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 org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.cocoon.components.serializers.encoding.XHTMLEncoder; |
| import org.apache.cocoon.components.serializers.util.DocType; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * <p>A pedantic XHTML serializer encoding all recognized entities with their |
| * proper HTML names.</p> |
| * |
| * <p>For configuration options of this serializer, please look at the |
| * {@link EncodingSerializer}, in addition to those, this serializer also |
| * support the specification of a default doctype. This default will be used |
| * if no document type is received in the SAX events, and can be configured |
| * in the following way:</p> |
| * |
| * <pre> |
| * <serializer class="org.apache.cocoon.components.serializers..." ... > |
| * <doctype-default>mytype</doctype-default> |
| * </serializer> |
| * </pre> |
| * |
| * <p>The value <i>mytype</i> can be one of:</p> |
| * |
| * <dl> |
| * <dt>"<code>none</code>"</dt> |
| * <dd>Not to emit any document type declaration.</dd> |
| * <dt>"<code>strict</code>"</dt> |
| * <dd>The XHTML 1.0 Strict document type.</dd> |
| * <dt>"<code>loose</code>"</dt> |
| * <dd>The XHTML 1.0 Transitional document type.</dd> |
| * <dt>"<code>frameset</code>"</dt> |
| * <dd>The XHTML 1.0 Frameset document type.</dd> |
| * <dt>"<code>xhtml5</code>"</dt> |
| * <dd>The XHTML5 document type.</dd> |
| * </dl> |
| * |
| * @version CVS $Id$ |
| */ |
| public class XHTMLSerializer extends XMLSerializer { |
| |
| /** The namespace URI for XHTML 1.0. */ |
| public static final String XHTML1_NAMESPACE = |
| "http://www.w3.org/1999/xhtml"; |
| |
| /** A representation of the XHTML 1.0 strict document type. */ |
| public static final DocType XHTML1_DOCTYPE_STRICT = new DocType( |
| "html", "-//W3C//DTD XHTML 1.0 Strict//EN", |
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); |
| |
| /** A representation of the XHTML 1.0 transitional document type. */ |
| public static final DocType XHTML1_DOCTYPE_TRANSITIONAL = new DocType( |
| "html", "-//W3C//DTD XHTML 1.0 Transitional//EN", |
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"); |
| |
| /** A representation of the XHTML 1.0 frameset document type. */ |
| public static final DocType XHTML1_DOCTYPE_FRAMESET = new DocType( |
| "html", "-//W3C//DTD XHTML 1.0 Frameset//EN", |
| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"); |
| |
| /** A representation of the XHTML5 document type. */ |
| public static final DocType XHTML5_DOCTYPE = new DocType("html"); |
| |
| /* ====================================================================== */ |
| |
| private static final XHTMLEncoder XHTML_ENCODER = new XHTMLEncoder(); |
| |
| protected boolean encodeCharacters = true; |
| |
| /* ====================================================================== */ |
| |
| /** The <code>DocType</code> instance representing the document. */ |
| protected DocType doctype_default = null; |
| |
| /** Define whether to put XML declaration in the head of the document. */ |
| private String omitXmlDeclaration = null; |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Create a new instance of this <code>XHTMLSerializer</code> |
| */ |
| public XHTMLSerializer() { |
| super(XHTML_ENCODER); |
| } |
| |
| /** |
| * Create a new instance of this <code>XHTMLSerializer</code> |
| */ |
| protected XHTMLSerializer(XHTMLEncoder encoder) { |
| super(encoder); |
| } |
| |
| /** |
| * Return the MIME Content-Type produced by this serializer. |
| */ |
| public String getMimeType() { |
| if (this.charset == null) return("text/html"); |
| return("text/html; charset=" + this.charset.getName()); |
| } |
| |
| /** |
| * Configure this instance by selecting the default document type to use. |
| */ |
| public void configure(Configuration conf) |
| throws ConfigurationException { |
| super.configure(conf); |
| |
| this.omitXmlDeclaration = conf.getChild("omit-xml-declaration").getValue(null); |
| |
| String doctype = conf.getChild("doctype-default").getValue(null); |
| if ("none".equalsIgnoreCase(doctype)) { |
| this.doctype_default = null; |
| } else if ("strict".equalsIgnoreCase(doctype)) { |
| this.doctype_default = XHTML1_DOCTYPE_STRICT; |
| } else if ("loose".equalsIgnoreCase(doctype)) { |
| this.doctype_default = XHTML1_DOCTYPE_TRANSITIONAL; |
| } else if ("frameset".equalsIgnoreCase(doctype)) { |
| this.doctype_default = XHTML1_DOCTYPE_FRAMESET; |
| } else if ("xhtml5".equalsIgnoreCase(doctype)) { |
| this.doctype_default = XHTML5_DOCTYPE; |
| } else { |
| /* Default is transitional */ |
| this.doctype_default = XHTML1_DOCTYPE_TRANSITIONAL; |
| } |
| } |
| |
| /* ====================================================================== */ |
| |
| /** |
| * Write the XML document header. |
| * <p> |
| * This method will write out the <code><?xml version="1.0" |
| * ...></code> header unless omit-xml-declaration is set. |
| * </p> |
| */ |
| protected void head() |
| throws SAXException { |
| if (!"yes".equals(this.omitXmlDeclaration)) { |
| super.head(); |
| } |
| } |
| |
| /** |
| * 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 { |
| if (this.doctype == null) this.doctype = this.doctype_default; |
| if (this.namespaces.getUri("").length() == 0) { |
| this.namespaces.push("", XHTML1_NAMESPACE); |
| } |
| super.body(uri, local, qual); |
| } |
| |
| /** |
| * 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 { |
| if (uri.length() == 0) uri = XHTML1_NAMESPACE; |
| |
| if (isCdataElement(local)) { |
| this.encodeCharacters = false; |
| } |
| |
| super.startElementImpl(uri, local, qual, namespaces, attributes); |
| } |
| |
| /** |
| * 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 (uri.length() == 0) uri = XHTML1_NAMESPACE; |
| |
| if (XHTML1_NAMESPACE.equals(uri)) { |
| if ((local.equalsIgnoreCase("textarea")) || |
| (local.equalsIgnoreCase("script")) || |
| (local.equalsIgnoreCase("style"))) { |
| this.closeElement(false); |
| } else if (local.equalsIgnoreCase("head")) { |
| String loc = "meta"; |
| String qua = this.namespaces.qualify(XHTML1_NAMESPACE, loc, "meta"); |
| String nsp[][] = new String[0][0]; |
| String att[][] = new String[2][ATTRIBUTE_LENGTH]; |
| |
| att[0][ATTRIBUTE_NSURI] = att[1][ATTRIBUTE_NSURI] = ""; |
| att[0][ATTRIBUTE_LOCAL] = att[0][ATTRIBUTE_QNAME] = "http-equiv"; |
| att[1][ATTRIBUTE_LOCAL] = att[1][ATTRIBUTE_QNAME] = "content"; |
| att[0][ATTRIBUTE_VALUE] = "Content-Type"; |
| att[1][ATTRIBUTE_VALUE] = this.getMimeType(); |
| |
| this.closeElement(false); |
| this.startElementImpl(XHTML1_NAMESPACE, loc, qua, nsp, att); |
| this.endElementImpl(XHTML1_NAMESPACE, loc, qua); |
| } |
| } |
| |
| if (isCdataElement(local)) { |
| this.encodeCharacters = true; |
| } |
| |
| super.endElementImpl(uri, local, qual); |
| } |
| |
| /** |
| * script and style are CDATA sections by default, so no encoding |
| * @param localName The local name of the element. |
| * @return If the element should be serialized without encoding. |
| */ |
| protected boolean isCdataElement(String localName) { |
| String upperCase = localName.toUpperCase(); |
| return "SCRIPT".equals(upperCase) || "STYLE".equals(upperCase); |
| } |
| |
| /** |
| * Encode and write a specific part of an array of characters. |
| */ |
| protected void encode(char data[], int start, int length) |
| throws SAXException { |
| if (this.encodeCharacters) { |
| super.encode(data, start, length); |
| } else { |
| this.write(data, start, length); |
| } |
| } |
| } |