/** * Copyright 2011 meltmedia * * Licensed 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.xchain.framework.digester; import org.xchain.framework.sax.HandlerWrapper; import org.xchain.framework.sax.util.XHtmlHandler; import org.apache.xml.serializer.Serializer; import org.apache.xml.serializer.SerializerFactory; import org.apache.xml.serializer.OutputPropertiesFactory; import org.apache.commons.digester.Rule; import org.apache.commons.digester.Digester; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Writer; import java.io.StringWriter; import java.util.Iterator; import java.util.Properties; import java.util.Map; import org.xml.sax.ContentHandler; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /** * @author Christian Trimble * @author Josh Kennedy */ public class SerializationRule extends Rule { public static Logger log = LoggerFactory.getLogger( SerializationRule.class ); //public static String HTML_METHOD = Method.HTML; //public static String TEXT_METHOD = Method.TEXT; //public static String XML_METHOD = Method.XML; private class RuleSerializationHandler extends HandlerWrapper { private int depth = 0; public void startElement( String namespaceUri, String localName, String qName, Attributes atts) throws SAXException { depth++; // if we are not including the containing element, then do not pass it on. Otherwise, pass this event on to super. if( depth == 1 && includeContainingElement || depth > 1 ) { super.startElement( namespaceUri, localName, qName, atts ); } // if the depth is ever less than or equal to zero, then things are broken. else if( depth <= 0 ) { throw new IllegalStateException("Unmatched start and end elements detected."); } } public void endElement( String namespaceUri, String localName, String qName ) throws SAXException { // if we are not including the containing element, then do not pass it on. if( depth == 1 && includeContainingElement || depth > 1 ) { super.endElement( namespaceUri, localName, qName ); } // decrement the depth. depth--; // close down the document and return control to the rule. if( depth <= 0 ) { Digester digester = getDigester(); // reset the handlers. digester.setCustomContentHandler(oldCustomContentHandler); oldCustomContentHandler = null; if( digester instanceof ExtendedDigester ) { ((ExtendedDigester)digester).setCustomLexicalHandler(oldCustomLexicalHandler); oldCustomLexicalHandler = null; } // close all of the namespace mappings. Iterator currentNamespaceIterator = digester.getCurrentNamespaces().entrySet().iterator(); while( currentNamespaceIterator.hasNext() ) { Map.Entry currentNamespace = (Map.Entry)currentNamespaceIterator.next(); handler.endPrefixMapping((String)currentNamespace.getKey()); } // terminate the document. super.endDocument(); // pass control back to the digester. digester.endElement( namespaceUri, localName, qName ); } } } /** The handler that will create the serialized form of the nodes. */ protected RuleSerializationHandler handler = null; /** The old custom handler that we displaced. */ protected ContentHandler oldCustomContentHandler; /** The old lexical handler that we displaced. */ protected LexicalHandler oldCustomLexicalHandler; /** The method that we will be using to render the output. */ protected String method = "text"; protected Boolean indent = Boolean.TRUE; /** The budder that we will be writting to. */ protected StringBuffer buffer = null; /** * If true, the element that caused this rule to file will be passed to the serializer, otherwise the next element * after this rule will be sent. */ protected boolean includeContainingElement = false; public void begin(String namespaceUri, String name, Attributes attributes) throws Exception { // get the digester. Digester digester = getDigester(); // store the old content handlers. oldCustomContentHandler = digester.getCustomContentHandler(); if( digester instanceof ExtendedDigester ) { oldCustomLexicalHandler = ((ExtendedDigester)digester).getCustomLexicalHandler(); } // create the handler. handler = new RuleSerializationHandler(); // set up the handlers that will do the serialization. handler.setWrappedHandler(newHandler()); // set the new handlers. digester.setCustomContentHandler(handler); if( digester instanceof ExtendedDigester ) { ((ExtendedDigester)digester).setCustomLexicalHandler(handler); } // push the buffer onto the stack. digester.push(buffer); // start the document. handler.startDocument(); // set all of the namespaces that are defined on the digester. Iterator currentNamespaceIterator = digester.getCurrentNamespaces().entrySet().iterator(); while( currentNamespaceIterator.hasNext() ) { Map.Entry currentNamespace = (Map.Entry)currentNamespaceIterator.next(); handler.startPrefixMapping((String)currentNamespace.getKey(), (String)currentNamespace.getValue()); } // send the current element to the handler. if( digester.getNamespaceAware() ) { handler.startElement(namespaceUri, name, digester.getCurrentElementName(), attributes); } else { handler.startElement(namespaceUri, name, name, attributes); } } public void end() throws Exception { getDigester().pop(); } /** * Returns a new handler for the body of a serializer block. */ protected ContentHandler newHandler() throws Exception { Serializer serializer = newSerializer(); if( method.toLowerCase().equals("html") ) { // wrap the html handler. XHtmlHandler xhtmlHandler = new XHtmlHandler(); xhtmlHandler.setWrappedHandler(serializer); return xhtmlHandler; } else { return serializer.asContentHandler(); } } /** * Sets up the wrapped handler. */ protected Serializer newSerializer() { Properties outputProperties = OutputPropertiesFactory.getDefaultMethodProperties( method ); if ( method.toLowerCase().equals("html") ) { if (indent == null) indent = true; // default to indenting mode outputProperties.setProperty( "media-type", "text/html" ); outputProperties.setProperty( "doctype-system", "http://www.w3.org/TR/html4/loose.dtd" ); outputProperties.setProperty( "doctype-public", "-//W3C//DTD HTML 4.01 Transitional//EN" ); } else if ( method.toLowerCase().equals("xhtml") ) { if (indent == null) indent = true; // default to indenting mode outputProperties.setProperty( "media-type", "application/xhtml+xml" ); outputProperties.setProperty( "omit-xml-declaration", "yes" ); // todo: should be browser sensitive outputProperties.setProperty( "doctype-system", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" ); outputProperties.setProperty( "doctype-public", "-//W3C//DTD XHTML 1.0 Transitional//EN" ); } else if ( method.toLowerCase().equals("xml") ) { outputProperties.setProperty( "media-type", "text/xml" ); } else if ( method.toLowerCase().equals("text") ) { outputProperties.setProperty( "media-type", "text/plain" ); } if ( Boolean.TRUE.equals( indent )) { outputProperties.setProperty( "indent", "yes" ); outputProperties.setProperty( "{http://xml.apache.org/xalan}indent-amount", "2" ); } else { outputProperties.setProperty( "indent", "no" ); } try { Serializer serializer = SerializerFactory.getSerializer( outputProperties ); serializer.setWriter(newStringWriter()); return serializer; } catch (org.apache.xml.serializer.utils.WrappedRuntimeException e) { log.error("Serializer threw wrapped exception", e.getException()); throw e; } /* if( TEXT_METHOD.equals(method ) ) { // create the content handler. ToTextStream toTextStream = new ToTextStream(); toTextStream.setOutputFormat(newTextProperties()); // set the writer. toTextStream.setWriter(newStringWriter()); // return the handler. return toTextStream; } else if( HTML_METHOD.equals(method) ) { // create the handler. ToHTMLStream toHtmlStream = new ToHTMLStream(); // configure the handler for html output. toHtmlStream.setOutputFormat(newHtmlProperties()); // set the writer. toHtmlStream.setWriter(newStringWriter()); // wrap the html handler. XHtmlHandler xhtmlHandler = new XHtmlHandler(); xhtmlHandler.setWrappedHandler(toHtmlStream); return xhtmlHandler; } else if( XML_METHOD.equals(method) ) { // create the handler. ToXMLStream toXmlStream = new ToXMLStream(); // configure the handler for html output. toXmlStream.setOutputFormat(newHtmlProperties()); // set the writer. toXmlStream.setWriter(newStringWriter()); return toXmlStream; } else { throw new IllegalStateException("Invalid method specified."); } */ } /* protected Properties newTextProperties() { return OutputPropertiesFactory.getDefaultMethodProperties(Method.TEXT); } protected Properties newHtmlProperties() { Properties properties = OutputPropertiesFactory.getDefaultMethodProperties(Method.HTML); properties.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); return properties; } protected Properties newXmlProperties() { Properties properties = OutputPropertiesFactory.getDefaultMethodProperties(Method.XML); properties.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); return properties; } */ protected StringWriter newStringWriter() { StringWriter stringWriter = new StringWriter(); buffer = stringWriter.getBuffer(); return stringWriter; } }