/* * Copyright (C) 2011 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.novelang.rendering.multipage; import java.util.Map; import com.google.common.base.Preconditions; import org.apache.xml.utils.MutableAttrListImpl; import org.xml.sax.Attributes; import org.xml.sax.EntityResolver; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.novelang.outfit.xml.NamespaceAwareness; import org.novelang.outfit.xml.SaxPipeline; import org.novelang.outfit.xml.SaxRecorder; import org.novelang.outfit.xml.XmlNamespaces; /** * Captures the XML document inside a <{@value #MULTIPAGE_STYLESHEET_LOCALNAME}> element * (inside the "{@value org.novelang.outfit.xml.XmlNamespaces#XSL_META_NAMESPACE_URI}" namespace) * as a standalone XML document. * The purpose is to use it as a stylesheet afterwards. * * @author Laurent Caillette */ public abstract class XslMultipageStylesheetCapture extends SaxPipeline.Stage { private SaxRecorder documentBuilder = null ; /** * TODO use this one. */ private final EntityResolver entityResolver ; /** * Override to do something with freshly-parsed stylesheet like saving it somewhere. */ protected abstract void onStylesheetDocumentBuilt( SaxRecorder.Player stylesheetPlayer ) ; protected XslMultipageStylesheetCapture( final EntityResolver entityResolver ) { this.entityResolver = Preconditions.checkNotNull( entityResolver ) ; } private boolean insideNestedStylesheet() { return documentBuilder != null ; } // ================== // NamespaceAwareness // ================== private final NamespaceAwareness namespaceAwareness = new NamespaceAwareness( XmlNamespaces.XSL_META_NAMESPACE_URI ) ; private NamespaceAwareness getNamespaceAwareness() { return namespaceAwareness ; } private static final String MULTIPAGE_STYLESHEET_LOCALNAME = "multipage" ; private boolean isNestedStylesheetRootElement( final String uri, final String localName ) { return getNamespaceAwareness().isMetaPrefix( uri ) && MULTIPAGE_STYLESHEET_LOCALNAME.equals( localName ) ; } private String getXsltPrefixMapping() { return getNamespaceAwareness().getPrefixMappings().inverse() .get( XmlNamespaces.XSL_NAMESPACE_URI ) ; } // ============== // ContentHandler // ============== @Override public void startElement( final String uri, final String localName, final String qName, final Attributes attributes ) throws SAXException { if( isNestedStylesheetRootElement( uri, localName ) ) { if( documentBuilder == null ) { documentBuilder = new SaxRecorder() ; documentBuilder.setDocumentLocator( getDocumentLocator() ) ; // documentBuilder.setEntityResolver( entityResolver ) ; documentBuilder.startDocument() ; for( final Map.Entry< String, String > prefixMapping : getNamespaceAwareness().getPrefixMappings().entrySet() ) { documentBuilder.startPrefixMapping( prefixMapping.getKey(), prefixMapping.getValue() ) ; } } else { getNamespaceAwareness().throwException( "Not allowed: nested " + getNamespaceAwareness().getNamespacePrefix() + ":" + MULTIPAGE_STYLESHEET_LOCALNAME ) ; } } if( documentBuilder != null ) { if( isNestedStylesheetRootElement( uri, localName ) ) { final AttributesImpl attributesWithVersion = new MutableAttrListImpl( attributes ) ; // String uri, String localName, String qName, String type, String value attributesWithVersion.addAttribute( "", "version", "version", "", "1.0" ) ; documentBuilder.startElement( XmlNamespaces.XSL_NAMESPACE_URI, "stylesheet", getXsltPrefixMapping() + ":" + "stylesheet", attributesWithVersion ) ; } else { documentBuilder.startElement( uri, localName, qName, attributes ) ; } } if( ! insideNestedStylesheet() ) { super.startElement( uri, localName, qName, attributes ) ; } } @Override public void endElement( final String uri, final String localName, final String qName ) throws SAXException { // Remain symmetrical with startElement, evaluate this before changing documentBuilder. final boolean wasInsideNestedStylesheet = insideNestedStylesheet() ; if( documentBuilder != null ) { if( isNestedStylesheetRootElement( uri, localName ) ) { documentBuilder.endElement( XmlNamespaces.XSL_NAMESPACE_URI, "stylesheet", getXsltPrefixMapping() + ":" + "stylesheet" ) ; } else { documentBuilder.endElement( uri, localName, qName ) ; } if( isNestedStylesheetRootElement( uri, localName ) ) { documentBuilder.endDocument() ; onStylesheetDocumentBuilt( documentBuilder.getPlayer() ) ; documentBuilder = null ; } } if( !wasInsideNestedStylesheet ) { super.endElement( uri, localName, qName ) ; } } @Override public void setDocumentLocator( final Locator locator ) { super.setDocumentLocator( locator ) ; namespaceAwareness.setDocumentLocator( getDocumentLocator() ) ; if( documentBuilder != null ) { documentBuilder.setDocumentLocator( getDocumentLocator() ) ; } } @Override public void startPrefixMapping( final String prefix, final String uri ) throws SAXException { namespaceAwareness.startPrefixMapping( prefix, uri ) ; if( documentBuilder != null ) { documentBuilder.startPrefixMapping( prefix, uri ) ; } if( ! insideNestedStylesheet() ) { super.startPrefixMapping( prefix, uri ) ; } } @Override public void endPrefixMapping( final String prefix ) throws SAXException { namespaceAwareness.endPrefixMapping( prefix ) ; if( documentBuilder != null ) { documentBuilder.endPrefixMapping( prefix ) ; } if( ! insideNestedStylesheet() ) { super.endPrefixMapping( prefix ) ; } } @Override public void characters( final char[] characters, final int start, final int length ) throws SAXException { if( documentBuilder != null ) { documentBuilder.characters( characters, start, length ) ; } if( ! insideNestedStylesheet() ) { super.characters( characters, start, length ) ; } } @Override public void ignorableWhitespace( final char[] characters, final int start, final int length ) throws SAXException { if( documentBuilder != null ) { documentBuilder.ignorableWhitespace( characters, start, length ) ; } if( ! insideNestedStylesheet() ) { super.ignorableWhitespace( characters, start, length ) ; } } @Override public void processingInstruction( final String target, final String data ) throws SAXException { if( documentBuilder != null ) { documentBuilder.processingInstruction( target, data ) ; } if( ! insideNestedStylesheet() ) { super.processingInstruction( target, data ) ; } } @Override public void skippedEntity( final String name ) throws SAXException { if( documentBuilder != null ) { documentBuilder.skippedEntity( name ) ; } if( ! insideNestedStylesheet() ) { super.skippedEntity( name ) ; } } }