/*
* 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.base.config.DefaultConfiguration;
import org.pentaho.reporting.libraries.base.util.FastStack;
import org.pentaho.reporting.libraries.resourceloader.DependencyCollector;
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.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.HashMap;
/**
* A base root SAX handler.
*
* @author Peter Becker
* @author Thomas Morgner
*/
public class RootXmlReadHandler extends DefaultHandler {
/**
* Storage for the parser configuration.
*/
private DefaultConfiguration parserConfiguration;
/**
* The DocumentLocator can be used to resolve the current parse position.
*/
private Locator documentLocator;
/**
* The current handlers.
*/
private FastStack<XmlReadHandler> currentHandlers;
/**
* The list of parent handlers.
*/
private FastStack<FastStack<XmlReadHandler>> outerScopes;
/**
* The root handler.
*/
private XmlReadHandler rootHandler;
/**
* The object registry.
*/
private HashMap<String, Object> objectRegistry;
/**
* A flag indicating whether this handler has initialized the root-element.
*/
private boolean rootHandlerInitialized;
/**
* The current comment handler used to receive xml comments.
*/
private CommentHandler commentHandler;
private DependencyCollector dependencyCollector;
private ResourceKey source;
private ResourceKey context;
private ResourceManager manager;
private FastStack<String> namespaces;
private boolean firstCall;
private boolean xmlnsUrisNotAvailable;
/**
* Creates a new root-handler using the given versioning information and resource-manager.
*
* @param manager the resource manager that loaded this xml-file.
* @param source the source-key that identifies from where the file was loaded.
* @param version the versioning information for the root-file.
*/
public RootXmlReadHandler( final ResourceManager manager,
final ResourceKey source,
final long version ) {
this( manager, source, source, version );
}
/**
* Creates a new root-handler using the given versioning information and resource-manager.
*
* @param manager the resource manager that loaded this xml-file.
* @param source the source-key that identifies 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.
*/
public RootXmlReadHandler( final ResourceManager manager,
final ResourceKey source,
final ResourceKey context,
final long version ) {
if ( manager == null ) {
throw new NullPointerException();
}
if ( source == null ) {
throw new NullPointerException();
}
this.firstCall = true;
this.manager = manager;
this.source = source;
this.context = context;
this.dependencyCollector = new DependencyCollector( source, version );
this.objectRegistry = new HashMap<String, Object>();
this.parserConfiguration = new DefaultConfiguration();
this.commentHandler = new CommentHandler();
this.namespaces = new FastStack<String>();
}
/**
* Returns the context key. This key may specify a base context for loading resources. (It behaves like the 'base-url'
* setting of HTML and allows to reference external resources as relative paths without being bound to the original
* location of the xml file.)
*
* @return the context.
*/
public ResourceKey getContext() {
return context;
}
/**
* Returns the resource-manager that is used to load external resources.
*
* @return the resource-manager.
*/
public ResourceManager getResourceManager() {
return manager;
}
/**
* Checks, whether this is the first call to the handler.
*
* @return true, if this is the first call, false otherwise.
*/
public boolean isFirstCall() {
return firstCall;
}
/**
* Returns the source key. This key points to the file or stream that is currently parsed.
*
* @return the source key.
*/
public ResourceKey getSource() {
return source;
}
/**
* Returns the current dependency collector for this parse-operation. The Collector allows to check compound-keys for
* changes.
*
* @return the dependency collector.
*/
public DependencyCollector getDependencyCollector() {
return dependencyCollector;
}
/**
* Returns the comment handler that is used to collect comments.
*
* @return the comment handler.
*/
public CommentHandler getCommentHandler() {
return this.commentHandler;
}
/**
* Returns the parser-configuration. This can be use to configure the parsing process.
*
* @return the parser's configuration.
*/
public DefaultConfiguration getParserConfiguration() {
return parserConfiguration;
}
/**
* Receive an object for locating the origin of SAX document events.
* <p/>
* The documentLocator allows the application to determine the end position of any document-related event, even if the
* parser is not reporting an error. Typically, the application will use this information for reporting its own errors
* (such as character content that does not match an application's business rules). The information returned by the
* documentLocator is probably not sufficient for use with a search engine.
*
* @param locator the documentLocator.
*/
public void setDocumentLocator( final Locator locator ) {
this.documentLocator = locator;
}
/**
* Returns the current documentLocator.
*
* @return the documentLocator.
*/
public Locator getDocumentLocator() {
return this.documentLocator;
}
/**
* Adds an object to the registry.
*
* @param key the key.
* @param value the object.
*/
public void setHelperObject( final String key, final Object value ) {
if ( value == null ) {
this.objectRegistry.remove( key );
} else {
this.objectRegistry.put( key, value );
}
}
/**
* Returns an object from the registry.
*
* @param key the key.
* @return The object.
*/
public Object getHelperObject( final String key ) {
return this.objectRegistry.get( key );
}
/**
* Returns the array of all currently registered helper-objects. Helper objects are used as simple communication
* process between the various handler implementations.
*
* @return the helper object names.
*/
public String[] getHelperObjectNames() {
return this.objectRegistry.keySet().toArray( new String[ objectRegistry.size() ] );
}
/**
* Sets the root SAX handler.
*
* @param handler the SAX handler.
*/
protected void setRootHandler( final XmlReadHandler handler ) {
if ( handler == null ) {
throw new NullPointerException();
}
this.rootHandler = handler;
this.rootHandlerInitialized = false;
}
/**
* Returns the root SAX handler.
*
* @return the root SAX handler.
*/
protected XmlReadHandler getRootHandler() {
return this.rootHandler;
}
/**
* Start a new handler stack and delegate to another handler.
*
* @param handler the handler.
* @param uri the namespace uri of the current tag.
* @param tagName the tag name.
* @param attrs the attributes.
* @throws SAXException if there is a problem with the parser.
*/
public void recurse( final XmlReadHandler handler,
final String uri,
final String tagName,
final Attributes attrs )
throws SAXException {
if ( handler == null ) {
throw new NullPointerException();
}
this.outerScopes.push( this.currentHandlers );
this.currentHandlers = new FastStack<XmlReadHandler>();
this.currentHandlers.push( handler );
handler.startElement( uri, tagName, attrs );
}
/**
* Delegate to another handler.
*
* @param handler the new handler.
* @param tagName the tag name.
* @param uri the namespace uri of the current tag.
* @param attrs the attributes.
* @throws SAXException if there is a problem with the parser.
*/
public void delegate( final XmlReadHandler handler,
final String uri,
final String tagName,
final Attributes attrs )
throws SAXException {
if ( handler == null ) {
throw new NullPointerException();
}
this.currentHandlers.push( handler );
handler.init( this, uri, tagName );
handler.startElement( uri, tagName, attrs );
}
/**
* Hand control back to the previous handler.
*
* @param tagName the tagname.
* @param uri the namespace uri of the current tag.
* @throws SAXException if there is a problem with the parser.
*/
public void unwind( final String uri, final String tagName )
throws SAXException {
// remove current handler from stack ..
this.currentHandlers.pop();
if ( this.currentHandlers.isEmpty() && !this.outerScopes.isEmpty() ) {
// if empty, but "recurse" had been called, then restore the old handler stack ..
// but do not end the recursed element ..
this.currentHandlers = this.outerScopes.pop();
} else if ( !this.currentHandlers.isEmpty() ) {
// if there are some handlers open, close them too (these handlers must be delegates)..
getCurrentHandler().endElement( uri, tagName );
}
}
/**
* Returns the current handler.
*
* @return The current handler.
*/
protected XmlReadHandler getCurrentHandler() {
return this.currentHandlers.peek();
}
/**
* Starts processing a document.
*
* @throws SAXException not in this implementation.
*/
public void startDocument() throws SAXException {
this.outerScopes = new FastStack<FastStack<XmlReadHandler>>();
this.currentHandlers = new FastStack<XmlReadHandler>();
if ( rootHandler != null ) {
// When dealing with the multiplexing beast, we cant define a
// root handler unless we've seen the first element and all its
// namespace declarations ...
this.currentHandlers.push( this.rootHandler );
}
}
/**
* 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.
*/
public final void startElement( final String originalUri,
final String localName,
final String qName,
final Attributes attributes )
throws SAXException {
// Check the default-namespace ..
if ( firstCall ) {
firstCall = false;
interceptFirstStartElement( originalUri, localName, qName, attributes );
return;
}
final String defaultNamespace;
final String nsuri = attributes.getValue( "xmlns" );
if ( nsuri != null ) {
defaultNamespace = nsuri;
} else if ( namespaces.isEmpty() ) {
defaultNamespace = "";
} else {
defaultNamespace = namespaces.peek();
}
pushDefaultNamespace( defaultNamespace );
final String uri;
if ( ( originalUri == null || "".equals( originalUri ) ) &&
defaultNamespace != null ) {
uri = defaultNamespace;
} else {
uri = originalUri;
}
if ( rootHandlerInitialized == false ) {
rootHandler.init( this, uri, localName );
rootHandlerInitialized = true;
}
final XmlReadHandler currentHandler = getCurrentHandler();
currentHandler.startElement( uri, localName,
wrapAttributes( new FixNamespaceUriAttributes( uri, attributes ) ) );
}
protected Attributes wrapAttributes( final Attributes attributes ) {
return attributes;
}
/**
* A helper call that allows to override the first call to the startElememt method. This allows the implementation of
* an multiplexing parser, which requires the information from the root-level elements.
*
* @param uri the namespace uri of the current tag.
* @param localName the unqualified tag-name.
* @param qName the qualified tag-name.
* @param attributes the attributes of the current element.
* @throws SAXException if something goes wrong.
*/
protected void interceptFirstStartElement( final String uri,
final String localName,
final String qName,
final Attributes attributes )
throws SAXException {
startElement( uri, localName, qName, attributes );
}
/**
* Updates the current default namespace.
*
* @param nsuri the uri of the current namespace.
*/
protected final void pushDefaultNamespace( final String nsuri ) {
namespaces.push( nsuri );
}
/**
* Sets and configures the root handle for the given root-level element.
*
* @param handler the read handler for the root element.
* @param uri the uri of the root elements namespace.
* @param localName the local tagname of the root element.
* @param attributes the attributes of the root element.
* @throws SAXException if something goes wrong.
*/
protected void installRootHandler( final XmlReadHandler handler,
final String uri,
final String localName,
final Attributes attributes )
throws SAXException {
if ( handler == null ) {
throw new NullPointerException();
}
this.rootHandler = handler;
this.rootHandler.init( this, uri, localName );
this.currentHandlers.push( handler );
this.rootHandlerInitialized = true;
this.rootHandler.startElement( uri, localName, attributes );
}
/**
* Process character data.
*
* @param ch the character buffer.
* @param start the start index.
* @param length the length of the character data.
* @throws SAXException if there is a parsing error.
*/
public void characters( final char[] ch, final int start, final int length )
throws SAXException {
try {
getCurrentHandler().characters( ch, start, length );
} catch ( SAXException se ) {
throw se;
} catch ( Exception e ) {
throw new ParseException
( "Failed at handling character data", e, getDocumentLocator() );
}
}
/**
* Finish processing an element.
*
* @param originalUri the URI.
* @param localName the local name.
* @param qName the qName.
* @throws SAXException if there is a parsing error.
*/
public final void endElement( final String originalUri,
final String localName,
final String qName )
throws SAXException {
final String defaultNamespace = namespaces.pop();
final String uri;
if ( ( originalUri == null || "".equals( originalUri ) ) &&
defaultNamespace != null ) {
uri = defaultNamespace;
} else {
uri = originalUri;
}
final XmlReadHandler currentHandler = getCurrentHandler();
currentHandler.endElement( uri, localName );
}
/**
* Tries to return the parse-result of the selected root-handler.
*
* @return the parse-result.
* @throws SAXException if an error occurs.
*/
public Object getResult() throws SAXException {
if ( this.rootHandler != null ) {
return this.rootHandler.getObject();
}
return null;
}
public EntityResolver getEntityResolver() {
return this;
}
/**
* Returns, whether the parser resolves namespace-URIs.
*
* @return true, if the parser will *NOT* resolve namespaces, false otherwise.
*/
public boolean isXmlnsUrisNotAvailable() {
return xmlnsUrisNotAvailable;
}
/**
* Sets a hint that the parser will not be able to return URIs for XML-Namespaces. You should not see this nowadays,
* as all the common JAXP-parser implementations seem to work fine with namespaces.
*
* @param xmlnsUrisNotAvailable a flag indicating that the XML parser has troubles resolving namespaces.
*/
public void setXmlnsUrisNotAvailable( final boolean xmlnsUrisNotAvailable ) {
this.xmlnsUrisNotAvailable = xmlnsUrisNotAvailable;
}
}