/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.libraries.xmlns.parser; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import org.xml.sax.Attributes; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.IOException; /** * A root-handler that intercepts the first call to startElement to select a XmlReadHandler based on the XmlDocumentInfo * provided by the parser. * * @author Thomas Morgner */ public class MultiplexRootElementHandler extends RootXmlReadHandler { /** * A entity resolver that collects information about the DTD used in the document while the underlying parser tries to * resolve the DTD into a local InputSource. */ private static class RootEntityResolver implements EntityResolver { private ParserEntityResolver entityResolver; private String publicId; private String systemId; /** * DefaultConstructor. */ private RootEntityResolver() { entityResolver = ParserEntityResolver.getDefaultResolver(); } /** * Collects the public and System-ID from the call for later use in the XmlDocumentInfo and then forwards the * resolver to the default resolver. * * @param publicId The public identifier of the external entity being referenced, or null if none was supplied. * @param systemId The system identifier of the external entity being referenced. * @return An InputSource object describing the new input source, or null to request that the parser open a regular * URI connection to the system identifier. * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception. * @throws java.io.IOException A Java-specific IO exception, possibly the result of creating a new InputStream * or Reader for the InputSource. * @see org.xml.sax.InputSource */ public InputSource resolveEntity( final String publicId, final String systemId ) throws SAXException, IOException { this.publicId = publicId; this.systemId = systemId; return entityResolver.resolveEntity( publicId, systemId ); } /** * Returns the public ID of the document or null, if the document does not use DTDs. * * @return the public ID of the documents DTD. */ public String getPublicId() { return publicId; } /** * Returns the system ID of the document or null, if the document does not use DTDs. * * @return the system ID of the documents DTD. */ public String getSystemId() { return systemId; } /** * Returns the entity resolver used by this class. * * @return the entity resolver. */ public ParserEntityResolver getEntityResolver() { return entityResolver; } } private XmlFactoryModule[] rootHandlers; private RootEntityResolver entityResolver; private XmlFactoryModule selectedRootHandler; /** * Creates a new MultiplexRootElementHandler for the given root handler selection. * * @param manager the resource manager that loaded this xml-file. * @param source the source-key that idenfies from where the file was loaded. * @param context the key that should be used to resolve relative paths. * @param version the versioning information for the root-file. * @param rootHandlers the roothandlers, never null. */ public MultiplexRootElementHandler ( final ResourceManager manager, final ResourceKey source, final ResourceKey context, final long version, final XmlFactoryModule[] rootHandlers ) { super( manager, source, context, version ); this.entityResolver = new RootEntityResolver(); this.rootHandlers = rootHandlers.clone(); } /** * Returns the entity resolver used in this handler. * * @return the entity resolver. */ public EntityResolver getEntityResolver() { return entityResolver; } /** * Returns the parent entity resolver used in the element handler. This returns the modifiable entity-resolver * backend. * * @return the entity resolver. */ public ParserEntityResolver getParserEntityResolver() { return entityResolver.getEntityResolver(); } /** * Returns all known roothandlers. * * @return the known root handlers. */ protected XmlFactoryModule[] getRootHandlers() { return rootHandlers.clone(); } /** * Starts processing an element. * * @param originalUri the URI. * @param localName the local name. * @param qName the qName. * @param attributes the attributes. * @throws SAXException if there is a parsing problem. */ protected void interceptFirstStartElement( final String originalUri, final String localName, final String qName, Attributes attributes ) throws SAXException { // build the document info and select the root handler that will // deal with the document content. final DefaultXmlDocumentInfo documentInfo = new DefaultXmlDocumentInfo(); documentInfo.setPublicDTDId( entityResolver.getPublicId() ); documentInfo.setSystemDTDId( entityResolver.getSystemId() ); documentInfo.setRootElement( localName ); documentInfo.setRootElementNameSpace( originalUri ); documentInfo.setRootElementAttributes( attributes ); final String nsuri = attributes.getValue( "xmlns" ); if ( nsuri != null ) { documentInfo.setDefaultNameSpace( nsuri ); } else { documentInfo.setDefaultNameSpace( "" ); } // ok, now find the best root handler and start parsing ... XmlFactoryModule bestRootHandler = null; int bestRootHandlerWeight = -1; for ( int i = 0; i < rootHandlers.length; i++ ) { final XmlFactoryModule rootHandler = rootHandlers[ i ]; final int weight = rootHandler.getDocumentSupport( documentInfo ); if ( weight > bestRootHandlerWeight ) { bestRootHandler = rootHandler; bestRootHandlerWeight = weight; } } if ( bestRootHandlerWeight < 0 || bestRootHandler == null ) { throw new NoRootHandlerException( "No suitable root handler known for this document: " + documentInfo ); } final XmlReadHandler readHandler = bestRootHandler.createReadHandler( documentInfo ); if ( readHandler == null ) { throw new NoRootHandlerException( "Unable to create the root handler. " + bestRootHandler ); } this.selectedRootHandler = bestRootHandler; String defaultNamespace = documentInfo.getDefaultNameSpace(); if ( defaultNamespace == null || "".equals( defaultNamespace ) ) { // Now correct the namespace .. defaultNamespace = bestRootHandler.getDefaultNamespace( documentInfo ); if ( defaultNamespace != null && "".equals( defaultNamespace ) == false ) { documentInfo.setRootElementNameSpace( defaultNamespace ); } } pushDefaultNamespace( defaultNamespace ); final String uri; if ( ( originalUri == null || "".equals( originalUri ) ) && defaultNamespace != null ) { uri = defaultNamespace; } else { uri = originalUri; } attributes = new FixNamespaceUriAttributes( uri, attributes ); installRootHandler( readHandler, uri, localName, wrapAttributes( attributes ) ); } public XmlFactoryModule getSelectedRootHandler() { return selectedRootHandler; } }