/** * 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.jsl; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.XMLFilterImpl; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import org.xchain.framework.lifecycle.Lifecycle; import org.xchain.framework.lifecycle.LifecycleContext; import org.xchain.framework.lifecycle.NamespaceContext; import org.xchain.framework.sax.ReversePrefixMappingContext; /** * This handler takes a sax event stream and creates command classes that will render non-chain elements into chains that write the elements as sax events. * * @author Christian Trimble * @author Jason Rose * @author Devon Tackett */ public abstract class AbstractSaxTemplateHandler extends XMLFilterImpl { /** The namespace for jsl elements. */ public static String JSL_NAMESPACE = "http://www.xchain.org/jsl/1.0"; /** The different element types the elements are grouped into. */ public static enum ElementType { JSL_ELEMENT_TYPE(true), COMMAND_ELEMENT_TYPE(false), TEMPLATE_ELEMENT_TYPE(true); private boolean templateElement; ElementType( boolean templateElement ) { this.templateElement = templateElement; } public boolean isTemplateElement() { return this.templateElement; } }; /** The element type of the previos element. */ private ElementType previousSiblingType = null; /** The prefix mappings for the current element. */ private Map<String, String> prefixMapping = new HashMap<String, String>(); /** The element info stack for the current element. */ protected LinkedList<ElementInfo> elementInfoStack = new LinkedList<ElementInfo>(); /** The set of namespaces that contain xchain commands. */ protected Set<String> commandNamespaceSet = new HashSet<String>(); /** The compiler that compiles generated template source files. */ protected TemplateCompiler sourceCompiler; /** The builder for output characters. */ protected StringBuilder charactersBuilder = new StringBuilder(); protected ReversePrefixMappingContext prefixMappingContext = new ReversePrefixMappingContext(); /** * Returns the type of an element based on a uri. */ private ElementType elementType( String uri ) { ElementType result = null; if( JSL_NAMESPACE.equals(uri) ) { result = ElementType.JSL_ELEMENT_TYPE; } else if( commandNamespaceSet.contains(uri) ) { result = ElementType.COMMAND_ELEMENT_TYPE; } else { result = ElementType.TEMPLATE_ELEMENT_TYPE; } return result; } public void startDocument() throws SAXException { // get the current lifecycle context. LifecycleContext context = Lifecycle.getLifecycleContext(); commandNamespaceSet = Collections.unmodifiableSet(context.getNamespaceContextMap().keySet()); sourceCompiler = new TemplateCompiler(); sourceCompiler.init(context.getClassLoader()); getContentHandler().startDocument(); } public void endDocument() throws SAXException { getContentHandler().endDocument(); } /** * Tests the uri type and calls the correct start element method. */ public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException { flushCharacters(); // push the element info. pushElementInfo(uri, attributes); // remove all of the attribute that are in the jsl namespace. AttributesImpl cleanAttributes = new AttributesImpl(); for( int i = 0; i < attributes.getLength(); i++ ) { if( !JSL_NAMESPACE.equals(uri) && !JSL_NAMESPACE.equals(attributes.getURI(i)) ) { cleanAttributes.addAttribute(attributes.getURI(i), attributes.getLocalName(i), attributes.getQName(i), attributes.getType(i), attributes.getValue(i)); } else if( JSL_NAMESPACE.equals(uri) && !"exclude-result-prefixes".equals(localName) ) { cleanAttributes.addAttribute(attributes.getURI(i), attributes.getLocalName(i), attributes.getQName(i), attributes.getType(i), attributes.getValue(i)); } } switch( elementInfoStack.getFirst().getElementType() ) { case JSL_ELEMENT_TYPE: startJslElement( uri, localName, qName, cleanAttributes ); break; case COMMAND_ELEMENT_TYPE: startCommandElement( uri, localName, qName, cleanAttributes ); break; case TEMPLATE_ELEMENT_TYPE: startTemplateElement( uri, localName, qName, cleanAttributes ); break; } } /** * Handles start events for jsl elements. */ protected abstract void startJslElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException; /** * Handles start events for xchain command elements. */ protected abstract void startCommandElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException; /** * Handles start events for template elements. */ protected abstract void startTemplateElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException; /** * Tests the uri type and calls the correct end element method. */ public void endElement( String uri, String localName, String qName ) throws SAXException { flushCharacters(); // call the end element for the type of element. switch( elementInfoStack.getFirst().getElementType() ) { case JSL_ELEMENT_TYPE: endJslElement( uri, localName, qName ); break; case COMMAND_ELEMENT_TYPE: endCommandElement( uri, localName, qName ); break; case TEMPLATE_ELEMENT_TYPE: endTemplateElement( uri, localName, qName ); break; default: } popElementInfo(); } /** * Handles end events for jsl elements. */ protected abstract void endJslElement( String uri, String localName, String qName ) throws SAXException; /** * Handles end events for xchain command elements. */ protected abstract void endCommandElement( String uri, String localName, String qName ) throws SAXException; /** * Handles end events for template elements. */ protected abstract void endTemplateElement( String uri, String localName, String qName ) throws SAXException; protected void pushElementInfo(String uri, Attributes attributes) throws SAXException { // create the element info. ElementInfo elementInfo = new ElementInfo(); elementInfo.setElementType(elementType(uri)); elementInfo.setPreviousSiblingType(previousSiblingType); elementInfo.setPrefixMapping(new HashMap<String, String>(prefixMapping)); elementInfo.setExcludeResultPrefixSet(excludeResultPrefixSet(uri, attributes)); elementInfoStack.addFirst(elementInfo); // create a map of namespaces that need to go to the handler. Map<String, String> handlerPrefixMapping = new HashMap<String, String>(); // if the previous 2 elements are templates, then we need to add the previous elements handler events. if( elementInfoStack.size() > 2 && elementInfoStack.get(1).getElementType().isTemplateElement() && elementInfoStack.get(2).getElementType().isTemplateElement() ) { handlerPrefixMapping.putAll(elementInfoStack.get(1).getHandlerPrefixMapping()); } // add this elements prefix mapping to the handler prefix mapping. handlerPrefixMapping.putAll(prefixMapping); // store the handler prefix mapping into the element info. elementInfo.setHandlerPrefixMapping(handlerPrefixMapping); // create a map of namespaces that need to go to the source builder. Map<String, String> sourcePrefixMapping = new HashMap<String, String>(); Set<String> sourceExcludeResultPrefixSet = new HashSet<String>(); // if the previous element is not a template, then we need to add the previous elements source prefix mapping to the source prefix mapping. if( elementInfoStack.size() > 1 && !elementInfoStack.get(1).getElementType().isTemplateElement() ) { sourcePrefixMapping.putAll(elementInfoStack.get(1).getSourcePrefixMapping()); sourceExcludeResultPrefixSet.addAll(elementInfoStack.get(1).getSourceExcludeResultPrefixSet()); } // add this elements prefix mapping to the source prefix mapping. sourcePrefixMapping.putAll(prefixMapping); sourceExcludeResultPrefixSet.addAll(elementInfo.getExcludeResultPrefixSet()); // store the source prefix mapping into the element info. elementInfo.setSourcePrefixMapping(sourcePrefixMapping); elementInfo.setSourceExcludeResultPrefixSet(sourceExcludeResultPrefixSet); // clear the previous sibling variable. previousSiblingType = null; // clear the prefix mapping stack. prefixMapping.clear(); } private Set<String> excludeResultPrefixSet( String uri, Attributes attributes ) throws SAXException { // add exclude-result-prefixes info to the element info stack. String excludeResultPrefixes = JSL_NAMESPACE.equals(uri) ? attributes.getValue("", "exclude-result-prefixes") : attributes.getValue(JSL_NAMESPACE, "exclude-result-prefixes"); excludeResultPrefixes = excludeResultPrefixes != null ? excludeResultPrefixes.trim() : excludeResultPrefixes; Set<String> excludeResultPrefixSet = new HashSet<String>(); if( excludeResultPrefixes == null ) { // nothing to do. } else if( "#all".equals(excludeResultPrefixes) ) { excludeResultPrefixSet.add(excludeResultPrefixes); } else { // clean up the excluded result prefixes. for( String prefix : excludeResultPrefixes.split("\\s+") ) { // get the namespace from the prefix mapping context. if( "".equals(prefix) ) { // this is leading whitespace or an empty attribute value, ignore it. } else if( "#all".equals(prefix) ) { throw new SAXException("It is a static error to exclude the result prefix #all with other result prefixes."); } else if( "#default".equals(prefix) ) { // TODO: make sure that default exists. excludeResultPrefixSet.add(prefix); } else { // TODO: make sure that prefix exists. excludeResultPrefixSet.add(prefix); } } } return excludeResultPrefixSet; } protected abstract void flushCharacters() throws SAXException; public void characters( char[] characters, int start, int length ) throws SAXException { // TODO: We may need to start a chain, if the characters is not whitespace and the character builder is, and we are in a template. charactersBuilder.append(characters, start, length); } /** * Handles ignorable whitespace found in the source document. */ public void ignorableWhitespace( char[] characters, int start, int length ) throws SAXException { charactersBuilder.append(characters, start, length); } protected void popElementInfo() { ElementInfo elementInfo = elementInfoStack.removeFirst(); // track the previous element type. previousSiblingType = elementInfo.getElementType(); } protected class ElementInfo { /** The type of this element. */ private ElementType elementType = null; /** The type of this elements previous sibling. */ private ElementType previousSiblingType = null; /** The prefix mappings that are defined on this element. */ private Map<String, String> prefixMapping = null; /** The prefix mappings that will be passed to the start source event. */ private Map<String, String> sourcePrefixMapping = null; /** The exclude result prefix set that will be passed to the start source event. */ private Set<String> sourceExcludeResultPrefixSet = null; /** The prefix mappings that will be passed to the next content handler (The digester). */ private Map<String, String> handlerPrefixMapping = null; /** The set of result prefixes that will be excluded for this element. */ private Set<String> excludeResultPrefixSet = null; public void setElementType( ElementType elementType ) { this.elementType = elementType; } public ElementType getElementType() { return this.elementType; } public void setPreviousSiblingType( ElementType previousSiblingType ) { this.previousSiblingType = previousSiblingType; } public ElementType getPreviousSiblingType() { return this.previousSiblingType; } public void setPrefixMapping( Map<String, String> prefixMapping ) { this.prefixMapping = prefixMapping; } public Map<String, String> getPrefixMapping() { return this.prefixMapping; } public void setSourcePrefixMapping( Map<String, String> sourcePrefixMapping ) { this.sourcePrefixMapping = sourcePrefixMapping; } public Map<String, String> getSourcePrefixMapping() { return this.sourcePrefixMapping; } public void setSourceExcludeResultPrefixSet( Set<String> sourceExcludeResultPrefixSet ) { this.sourceExcludeResultPrefixSet = sourceExcludeResultPrefixSet; } public Set<String> getSourceExcludeResultPrefixSet() { return sourceExcludeResultPrefixSet; } public void setHandlerPrefixMapping( Map<String, String> handlerPrefixMapping ) { this.handlerPrefixMapping = handlerPrefixMapping; } public Map<String, String> getHandlerPrefixMapping() { return this.handlerPrefixMapping; } public void setExcludeResultPrefixSet( Set<String> excludeResultPrefixSet ) { this.excludeResultPrefixSet = excludeResultPrefixSet; } public Set<String> getExcludeResultPrefixSet() { return excludeResultPrefixSet; } } public void startPrefixMapping( String prefix, String uri ) throws SAXException { // cache these. prefixMapping.put(prefix, uri); // track the general namespace context. prefixMappingContext.startPrefixMapping( prefix, uri ); } public void endPrefixMapping( String prefix ) { // the prefix mapping is cleared by pushElementInfo(String) prefixMappingContext.endPrefixMapping(prefix); } public void skippedEntity( String name ) throws SAXException { //throw new SAXException("Skipped Entities are not acceppted by this handler."); } public void processingInstruction( String target, String data ) throws SAXException { //throw new SAXException("Processing instructions are not acceppted by this handler."); } }