/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.app.xmlui.objectmanager;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.NamespaceSupport;


/** 
 * This is a Swiss army like SAX Filter, its purpose is to filter out 
 * undesirable SAX events from the stream. The primary application of 
 * this is for inserting SAX fragment into an existing SAX pipeline, 
 * under this scenario you would not want new startDocument or 
 * endDocument events interfering with the existing pipeline thus 
 * this class can filter those out.
 * 
 * The Swiss army part comes in because it's configurable. Instead of 
 * defining a static set of events that are filled out by default all
 * events are filled out and must be turned on to allow each type
 * Individually.
 * 
 * Primarily you can filter events based upon their type, i.e. start/end 
 * elements or start/end documents. However there is one special control,
 * and that is to only allow elements below a minimum level.
 * . 
 * 
 * @author Scott Phillips
 */
public class SAXFilter implements ContentHandler, LexicalHandler
{
	
	/** Control for which type of SAX events to allow */
	private boolean allowDocuments = false;
	private boolean allowDocumentLocators = false;
	private boolean allowProcessingInstructions = false;
	private boolean allowPrefixMappings = false;
	private boolean allowElements = false;
	private boolean allowIgnorableWhitespace = false;
	private boolean allowSkippedEntities = false;
	private boolean allowCharacters = false;

	private boolean allowDTDs = false;
	private boolean allowEntities = false;
	private boolean allowCDATA = false;
	private boolean allowComments = false;
	
	
	/** The minimum level an element must be before it will be allowed */
	private int minimumElementLevel = -1;
	
	/** 
	 * The current XML level, each time start element is encountered this 
	 * is increased, and each time an end element is encountered it is 
	 * decreased. 
	 */
	private int currentElementLevel = 0;
	
	
	/**
	 * If no URI is provided then substitute this default prefix and URI:
	 */
	private String defaultURI;
	
	/** The SAX handlers and namespace support */
	private ContentHandler contentHandler;
	private LexicalHandler lexicalHandler;
	private NamespaceSupport namespaces;

	/**
	 * Construct a new SAXFilter such that the allowed events will be routed
	 * to the corresponding content and lexical handlers.
	 * 
	 * @param contentHandler The SAX content handler.
	 * @param lexicalHandler The SAX lexical handler.
	 * @param namespaces Namespace support which records what prefixes have been defined.
	 */
	public SAXFilter(ContentHandler contentHandler, LexicalHandler lexicalHandler, NamespaceSupport namespaces)
	{
		this.contentHandler = contentHandler;
		this.lexicalHandler = lexicalHandler;
		this.namespaces = namespaces;
	}

	/** Allow start/end document events */
	public SAXFilter allowDocuments() {
		this.allowDocuments = true;
		return this;
	}
	
	/** Allow document locator events */
	public SAXFilter allowDocumentLocators() {
		this.allowDocumentLocators = true;
		return this;
	}
	
	/** Allow processing instruction events */
	public SAXFilter allowProcessingInstructions() {
		this.allowProcessingInstructions = true;
		return this;
	}
	
	/** allow start/end prefix mapping events */
	public SAXFilter allowPrefixMappings() {
		this.allowPrefixMappings = true;
		return this;
	}
	
	/** allow start/end element events */
	public SAXFilter allowElements() {
		this.allowElements = true;
		return this;
	}
	
	/**
	 * Allow start/end element events.
	 * 
	 * However only allow those start / end events if
	 * they are below the given XML level. I.e. each nested 
	 * element is a new level.
	 * 
	 * @param minimumElementLevel 
	 * 				the minimum level required.
	 * @return
	 */
	public SAXFilter allowElements(int minimumElementLevel)
	{
		this.allowElements = true;
		this.minimumElementLevel = minimumElementLevel;
		return this;
	}
	
	/** Allow ignorable whitespace events */
	public SAXFilter allowIgnorableWhitespace() {
		this.allowIgnorableWhitespace = true;
		return this;
	}
	
	/** Allow start / end events for skipped entities */
	public SAXFilter allowSkippedEntities() {
		this.allowSkippedEntities = true;
		return this;
	}
	
	/** Allow character events */
	public SAXFilter allowCharacters() {
		this.allowCharacters = true;
		return this;
	}
	
	/** Allow DTD events */
	public SAXFilter allowDTDs() {
		this.allowDTDs = true;
		return this;
	}
	
	/** Allow XML entities events */
	public SAXFilter allowEntities() {
		this.allowEntities = true;
		return this;
	}
	
	/** Allow CDATA events */
	public SAXFilter allowCDATA() {
		this.allowCDATA = true;
		return this;
	}
	
	/** Allow comment events */
	public SAXFilter allowComments() {
		this.allowComments = true;
		return this;
	}
	
	/**
	 * Add a default namespace is none is provided. The namespace 
	 * should have allready been declared (add added to the 
	 * namespace support object
	 * 
	 * @param uri the default namespace uri.
	 */
	public SAXFilter setDefaultNamespace(String uri)
	{
		this.defaultURI = uri;
		return this;
	}

	/**
	 * SAX Content events
	 */
	
	public void startDocument() throws SAXException
	{
		if (allowDocuments)
        {
            contentHandler.startDocument();
        }
	}

	public void endDocument() throws SAXException
	{	
		if (allowDocuments)
        {
            contentHandler.endDocument();
        }
	}
	
	public void setDocumentLocator(Locator locator)
	{
		if (allowDocumentLocators)
        {
            contentHandler.setDocumentLocator(locator);
        }
	}
	
	public void processingInstruction(String target, String data)
	throws SAXException
	{
		if (allowProcessingInstructions)
        {
            contentHandler.processingInstruction(target, data);
        }
	}
	
	public void startPrefixMapping(String prefix, String uri)
	throws SAXException
	{
		if (allowPrefixMappings)
        {
            contentHandler.startPrefixMapping(prefix, uri);
        }
	}
	
	public void endPrefixMapping(String prefix) throws SAXException
	{
		if (allowPrefixMappings)
        {
            contentHandler.endPrefixMapping(prefix);
        }
	}
	
	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException
	{
		if (allowElements)
		{	
			currentElementLevel++;
			// check if we are past the minimum level requirement
			if (minimumElementLevel < currentElementLevel)
			{
				if (defaultURI != null && (uri == null || "".equals(uri)))
				{
					// No namespace provided, use the default namespace.
					String prefix = namespaces.getPrefix(defaultURI);
					
					if (!(prefix == null || "".equals(prefix)))
					{
						qName = prefix+":"+localName;
					}
					
					contentHandler.startElement(defaultURI, localName, qName, atts);
				}
				else
				{
					// let the event pass through unmodified.
					contentHandler.startElement(uri, localName, localName, atts);
				}
			}
		}
	}

	public void endElement(String uri, String localName, String qName)
	throws SAXException
	{
		if (allowElements)
		{	
			// check if we are past the minimum level requirements
			if (minimumElementLevel < currentElementLevel)
			{
				if (defaultURI != null && (uri == null || "".equals(uri)))
				{
					// No namespace provided, use the default namespace.
					String prefix = namespaces.getPrefix(defaultURI);
					
					if (!(prefix == null || "".equals(prefix)))
					{
						qName = prefix+":"+localName;
					}
					
					contentHandler.endElement(defaultURI, localName, qName);
				}
				else
				{
					// Let the event pass through unmodified.
					contentHandler.endElement(uri, localName, localName);
				}
			}
			currentElementLevel--;
		}
	}

	public void ignorableWhitespace(char[] ch, int start, int length)
	throws SAXException
	{
		if (allowIgnorableWhitespace)
        {
            contentHandler.ignorableWhitespace(ch, start, length);
        }
	}

	public void skippedEntity(String name) throws SAXException
	{
		if (allowSkippedEntities)
        {
            contentHandler.skippedEntity(name);
        }
	}

	public void characters(char[] ch, int start, int length)
	throws SAXException
	{
		if (allowCharacters)
        {
            contentHandler.characters(ch, start, length);
        }
	}
	
	/**
	 * SAX Lexical events
	 */

	public void startDTD(String name, String publicId, String systemId)
	throws SAXException
	{
		if (allowDTDs)
        {
            lexicalHandler.startDTD(name, publicId, systemId);
        }
	}

	public void endDTD() throws SAXException
	{
		if (allowDTDs)
        {
            lexicalHandler.endDTD();
        }
	}

	public void startEntity(String name)
	throws SAXException
	{
		if (allowEntities)
        {
            lexicalHandler.startEntity(name);
        }
	}

	public void endEntity(String name)
	throws SAXException
	{
		if (allowEntities)
        {
            lexicalHandler.endEntity(name);
        }
	}

	public void startCDATA()
	throws SAXException
	{
		if (allowCDATA)
        {
            lexicalHandler.startCDATA();
        }
	}

	public void endCDATA()
	throws SAXException
	{
		if (allowCDATA)
        {
            lexicalHandler.endCDATA();
        }
	}

	public void comment(char[] ch, int start, int length)
	throws SAXException
	{
		if (allowComments)
        {
            lexicalHandler.comment(ch, start, length);
        }
	}
}



