/*
 * $Id: Parser2.java,v 1.1.1.1 2002/09/30 15:08:52 smartine Exp $
 * Copyright (C) 1999-2000 David Brownell
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package xml.vendor;

import java.io.IOException;
import java.util.Locale;

import org.xml.sax.*;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;

import xml.*;

// requires Project X parser (as in JAXP 1.0.1)
// see http://java.sun.com/products/xml
import com.sun.xml.parser.DtdEventListener;
import com.sun.xml.parser.LexicalEventListener;
import com.sun.xml.parser.ValidatingParser;
// there's also a nonvalidating parser ("Parser") there (name collision)


// $Id: Parser2.java,v 1.1.1.1 2002/09/30 15:08:52 smartine Exp $

/**
 * This is a wrapper around the <b>com.sun.xml.parser.*</b> SAX1 parsers,
 * implementing <b>SAX2</b> interfaces.  The standardized
 * SAX2 facilities exposed by this parser include all SAX1 features, as
 * well as a number of additional features and properties identified by
 * standardized URIs.  Those features are summarized below.</p>
 *
 * <table border="1" width='100%' cellpadding='3' cellspacing='0'>
 * <tr bgcolor='#ccccff'>
 *	<th><font size='+1'>Name</font></th>
 *	<th><font size='+1'>Notes</font></th>
 *	</tr>
 *
 * <tr><td colspan=2><center><em>Features ... URL prefix is
 * <b>http://xml.org/sax/features/</b></em></center></td></tr>
 *
 * <tr><td>(URI)/external-general-entities</td>
 *	<td>Value is fixed at <em>true</em></td></tr>
 * <tr><td>(URI)/external-parameter-entities</td>
 *	<td>Value is fixed at <em>true</em></td></tr>
 * <tr><td>(URI)/namespaces</td>
 *	<td>Value is fixed at <em>false</em>
 *		(temporary -- SAX2 demands default of 'true')
 *		</td></tr>
 * <tr><td>(URI)/namespace-prefixes</td>
 *	<td>Value is fixed at <em>true</em>
 *		(temporary -- SAX2 demands default of 'false')
 *		</td></tr>
 * <tr><td>(URI)/string-interning</td>
 *	<td>Value is fixed at <em>true</em></td></tr>
 * <tr><td>(URI)/validation</td>
 *	<td>Defaults to <em>false</em>; may be set to <em>true</em></td></tr>
 *
 * <tr><td colspan=2><center><em>Handler properties ... URL prefix is
 * <b>http://xml.org/sax/properties/</b></em></center></td></tr>
 *
 *
 * <tr><td>(URI)/declaration-handler</td>
 *	<td>A declaration handler may be provided.  Declaration of general
 *	entities is exposed, but not parameter entities; none of the entity
 *	names reported here will begin with "%". </td></tr>
 * <tr><td>(URI)/lexical-handler</td>
 *	<td>A lexical handler may be provided.  While the start and end of
 *	any external subset are reported, expansion of other parameter
 *	entities (e.g. inside attribute list declarations) is not exposed.
 *	Expansion of general entities within attributes is also not exposed
 *	(see below).</td></tr>
 * </table>
 *
 * <p> Other features of the Sun parser, some of which may be significant by
 * their current lack of support in SAX2, include: <ul>
 *
 *	<li> The <em>Internal DTD Subset</em> is not properly exposed
 *	through SAX2; applications (and DOM) requiring literal recreation of
 *	a document's doctype can't do this with SAX2.  (Examples of such
 *	applications include the filtering proxies that many secure message
 *	distribution networks require, and the similar canonicalization
 *	required for digital signature support.)</li>
 *
 *	<li> SAX2 discards some semantic attribute declaration information,
 *	namely the list of just which NOTATIONs an attribute may use. </li>
 *
 *	<li> SAX2 does not report which attributes were defaulted.  This
 *	information can be useful to support some DOM Level 1 features
 *	relating to defaulting of attributes, given APIs (currently being
 *	discussed as for DOM Level 3) to support their implementation. </li>
 *
 *	<li> Sun's parser lets you insert the results of executing the
 *	JavaScript within an XHTML <em>&lt;script&gt;</em> tag into the input
 *	stream as it is parsed, permitting complete XHTML support. </li>
 *
 * </ul>
 *
 * <p> Expansion of entities is not exposed, although the final version of
 * SAX2 does now address the reporting issues noted in response to the
 * implementation of the first (1998) SAX2 API proposals. </li>
 *
 * @author David Brownell
 * @version $Date: 2002/09/30 15:08:52 $
 */
public class Parser2 implements XMLReader
{
    // Stuff used internally to route events correctly
    private Adapter		adapter = new Adapter ();
    private DefaultHandler	defaultHandler = new DefaultHandler ();

    // either a validating or nonvalidating parser
    private Parser		parser;

    // settable features:
    private boolean		validation;

    // handlers
    private DeclHandler		declHandler = defaultHandler;
    private LexicalHandler	lexicalHandler = defaultHandler;
    private ContentHandler	contentHandler = defaultHandler;
    private DTDHandler		dtdHandler = defaultHandler;

    private Locale		locale = Locale.getDefault ();
    private ErrorHandler	errHandler = defaultHandler;
    private EntityResolver	resolver = defaultHandler;


    /**
     * Constructs an unitialized <b>SAX2</b> parser.  By default, this will
     * not validate; validation may be manually enabled.  If it is enabled,
     * you should probably set an error handler accordingly.
     */
    public Parser2 () {
    } 

    /**
     * <b>SAX2</b>: Returns the object used to receive callbacks for XML
     * errors of all levels (fatal, nonfatal, warning).
     */
    public ErrorHandler getErrorHandler ()
    {
	return errHandler;
    }

    /**
     * <b>SAX1</b>: Provides an object which receives callbacks for XML errors
     * of all levels (fatal, nonfatal, warning).
     *
     * <p> <b>NOTE:</b>  When using a validating parser, the SAX default error
     * handler almost certainly does not do what you expected.  Unless you
     * specified validation on a lark, you probably wanted to do something
     * more with validity errors than discard them.  You should provide a
     * handler which does something useful with nonfatal errors, probably
     * reporting them (perhaps by treating them like fatal errors).
     */
    public void setErrorHandler (ErrorHandler handler)
    {
	if (handler == null)
	    handler = defaultHandler;
	errHandler = handler;
	if (parser != null)
	    parser.setErrorHandler (handler);
    }

    /**
     * <b>SAX2</b>: Returns the object used to report the logical
     * content of an XML document.
     */
    public ContentHandler getContentHandler ()
    {
	return contentHandler;
    }

    /**
     * <b>SAX2</b>: Assigns the object used to report the logical
     * content of an XML document.
     */
    public void setContentHandler (ContentHandler handler)
    {
	if (handler == null)
	    handler = defaultHandler;
	contentHandler = handler;
    }

    /**
     * <b>SAX2</b>: Returns the object used to process declarations related
     * to notations and unparsed entities.
     */
    public DTDHandler getDTDHandler ()
    {
	return dtdHandler;
    }

    /**
     * <b>SAX1</b>: Provides an object which may be used to intercept
     * declarations related to notations and unparsed entities.
     */
    public void setDTDHandler (DTDHandler handler)
    {
	if (handler == null)
	    handler = defaultHandler;
	dtdHandler = handler;
	if (parser != null)
	    parser.setDTDHandler (handler);
    }

    /**
     * <b>SAX2</b>: Returns the object used when resolving external
     * entities during parsing (both general and parameter entities).
     */
    public EntityResolver getEntityResolver ()
    {
	return resolver;
    }

    /**
     * <b>SAX1</b>: Provides an object which may be used when resolving external
     * entities during parsing (both general and parameter entities).
     */
    public void setEntityResolver (EntityResolver resolver)
    {
	if (resolver == null)
	    resolver = defaultHandler;
	this.resolver = resolver;
	if (parser != null)
	    parser.setEntityResolver (resolver);
    }

    /**
     * <b>SAX1</b>: Identifies the locale which the parser should use for the
     * diagnostics it provides.
     *
     * @exception SAXException as defined in the specification for
     *	<em>org.xml.sax.Parser.setLocale()</em>
     */
    public void setLocale (Locale locale)
    throws SAXException
    {
	if (locale == null)
	    locale = Locale.getDefault ();
	this.locale = locale;
	if (parser != null)
	    parser.setLocale (locale);
    }

    private void initParser ()
    throws SAXException
    {
	if (validation)
	    parser = new ValidatingParser ();
	else
	    parser = new com.sun.xml.parser.Parser ();

	parser.setLocale (locale);
	parser.setErrorHandler (errHandler);
	parser.setEntityResolver (resolver);

	// convert (if needed) between Sun and SAX2 events
	parser.setDTDHandler (adapter);
	parser.setDocumentHandler (adapter);
    }

    /**
     * <b>SAX1</b>:  Parse the XML text at the given input URI.
     *
     * @exception SAXException as defined in the specification for
     *	<em>org.xml.sax.Parser.parse()</em>
     * @exception IOException as defined in the specification for
     *	<em>org.xml.sax.Parser.parse()</em>
     */
    public void parse (String uri) throws SAXException, IOException
    {
	initParser ();
	parser.parse (uri);
    }

    /**
     * <b>SAX1</b>:  parse the XML text in the given input source.  Note that
     * if no system identifier is available for the input stream, then the
     * parse will not be able to interpret any relative URIs in that XML
     * source text; provide the system identifier, if you know it.
     *
     * @exception SAXException as defined in the specification for
     *	<em>org.xml.sax.Parser.parse()</em>
     * @exception IOException as defined in the specification for
     *	<em>org.xml.sax.Parser.parse()</em>
     */
    public void parse (InputSource input) throws SAXException, IOException
    {
	initParser ();
	parser.parse (input);
    }

    private static final String FEATURES = "http://xml.org/sax/features/";
    private static final String HANDLERS = "http://xml.org/sax/properties/";

    /**
     * <b>SAX2</b>: Tells whether this parser supports the specified feature.
     * At this time, this directly parallels the underlying parser, except
     * that the use of validation can be enabled or disabled (since Sun's
     * package has two separate SAX1 parsers).
     */
    public boolean getFeature (String featureId)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
	// external entities (both types) are currently always included
	if ((FEATURES + "external-general-entities")
		    .equals (featureId)
		|| (FEATURES + "external-parameter-entities")
		    .equals (featureId))
	    return true;

// XXX settable (default true)
	// element/attribute names are as written in document; no mangling
	if ((FEATURES + "namespaces").equals (featureId))
	    return false;

// XXX settable (default false)
	// element/attribute names are as written in document; no mangling
	if ((FEATURES + "namespace-prefixes").equals (featureId))
	    return true;

	// names are interned
	if ((FEATURES + "string-interning").equals (featureId))
	    return true;

	// validate or not, by choice of underlying parser
	if ((FEATURES + "validation").equals (featureId))
	    return validation;

	throw new SAXNotRecognizedException (featureId);
    }

    /**
     * <b>SAX2</b>:  Returns the specified property.  At this time only
     * declaration and lexical handlers are supported (Sun's parser exposes
     * this state already).
     */
    public Object getProperty (String propertyId)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
	if ((HANDLERS + "declaration-handler").equals (propertyId))
	    return declHandler;
	if ((HANDLERS + "lexical-handler").equals (propertyId))
	    return lexicalHandler;

	// unknown properties
	throw new SAXNotRecognizedException (propertyId);
    }

    /**
     * <b>SAX2</b>:  Sets the state of features supported in this parser.  At
     * this writing, only one feature's state is mutable:  validation can be
     * enabled or disabled, though not in the middle of a parse.
     */
    public void setFeature (String featureId, boolean state)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
	boolean current = getFeature (featureId);
	
	if (state == current)
	    return;

	// features with mutable state:
	if ((FEATURES + "validation").equals (featureId)) {
	    if (parser != null) 
		throw new SAXNotSupportedException (
		    "feature is readonly during parse:  " + featureId);
	    validation = state;
	    return;
	}

	// unsupported feature values
	throw new SAXNotSupportedException (featureId);
    }

    /**
     * <b>SAX2</b>:  Assigns the specified property.  At this time only
     * declaration and lexical handlers are supported, and these must not
     * be changed to values of the wrong type.  Like SAX1 handlers, these
     * may be changed at any time.
     */
    public void setProperty (String propertyId, Object property)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
	if (property == null)
	    property = defaultHandler;

	if ((HANDLERS + "declaration-handler").equals (propertyId)) {
	    if (!(property instanceof DeclHandler))
		throw new SAXNotSupportedException (propertyId);
	    declHandler = (DeclHandler) property;
	    return;
	}
	if ((HANDLERS + "lexical-handler").equals (propertyId)) {
	    if (!(property instanceof LexicalHandler))
		throw new SAXNotSupportedException (propertyId);
	    lexicalHandler = (LexicalHandler) property;
	    return;
	}

	// unknown properties
	throw new SAXNotRecognizedException (propertyId);
    }

    //
    // This inner class is a little adapter between the events provided by
    // Sun's parsers (based on the first proposals for what SAX2 should be,
    // which unfortunately ended up being the final ones despite bugs found
    // in them) and the SAX2 events.
    //
    final class Adapter implements
	DocumentHandler, LexicalEventListener,
	DTDHandler, DtdEventListener
    {
	// only set within DTD
	private String		rootName;
	private boolean		startedDtd;
	// private boolean		startedExternalSubset;

	// temp used in attribute decls
	private StringBuffer	temp = new StringBuffer ();

	// used to detect otherwise unnamed "[dtd]" PE
	private String		extSubsetSysId;
	private Locator		locator;

	// SAX1 to SAX2 conversions
	private AttributesImpl	atts = new AttributesImpl ();

	Adapter () { }

	//
	// SAX1 DocumentHandler callbacks
	//	... no loss of information here
	//
	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void setDocumentLocator (Locator locator)
	{
	    this.locator = locator;
	    contentHandler.setDocumentLocator (locator);
	}
    
	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void startDocument ()
	throws SAXException
	    { contentHandler.startDocument (); startedDtd = false; }
    
// XXX endDocument() needs to be in a "finally" clause

	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void endDocument ()
	throws SAXException
	    { contentHandler.endDocument (); }
    
	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void startElement (String name, AttributeList attrs)
	throws SAXException
	{
// XXX namespace stuff
	    toAttributes (attrs);
	    contentHandler.startElement ("", "", name, atts);
	    atts.clear ();
	}

	private void toAttributes (AttributeList attrs)
	{
	    // precondition:  atts is cleared
	    if (attrs == null)
		return;

	    int length = attrs.getLength ();

	    for (int i = 0; i < length; i++) {
// XXX namespace stuff
		atts.addAttribute ("", "",
		    attrs.getName (i),
		    attrs.getType (i),
		    attrs.getValue (i)
		    );
	    }
	}
    
	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void endElement (String name)
	throws SAXException
	{
// XXX namespace stuff
	    contentHandler.endElement ("", "", name);
	}
    
	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void characters (char buf [], int off, int len)
	throws SAXException
	    { contentHandler.characters (buf, off, len); }

	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void ignorableWhitespace (char buf [], int off, int len)
	throws SAXException
	    { contentHandler.ignorableWhitespace (buf, off, len); }

	/** @deprecated DocumentHandler is not the preferred SAX2 API */
	public void processingInstruction (String target, String data)
	throws SAXException
	{
	    checkExternalSubset ();
	    contentHandler.processingInstruction (target, data);
	}
	
	//
	// Sun LexicalEventListener callbacks
	//	... Sun's parsers don't report PEs at all, but they're
	//	    visible through the locator in callbacks they cause.
	//
	public void startCDATA ()
	throws SAXException
	    { lexicalHandler.startCDATA (); }

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

	public void comment (String data)
	throws SAXException
	{
	    checkExternalSubset ();
	    lexicalHandler.comment (data.toCharArray (), 0, data.length ());
	}

	public void startParsedEntity (String name)
	throws SAXException
	{
	    // Sun's parser does NOT report confusing entities
	    // (namely, anything except refs between markup tags
	    // in content).
	    lexicalHandler.startEntity (name);
	}

	public void endParsedEntity (String name, boolean ignored)
	throws SAXException
	{
	    lexicalHandler.endEntity (name);
	}

	//
	// SAX1 DTDHandler callbacks:
	//	... no loss of information here
	//
	private void checkExternalSubset ()
	throws SAXException
	{
	    if (!startedDtd && rootName != null) {
		lexicalHandler.startDTD (rootName, null, null);
		startedDtd = true;
	    }

	    // if (startedExternalSubset)
	    // 	    return;
	    if (extSubsetSysId == null
		    || !extSubsetSysId.equals (locator.getSystemId ()))
		return;
	    // lexicalHandler.startEntity ("[dtd]");
	    // startedExternalSubset = true;
	}

	public void unparsedEntityDecl (String name,
	    String publicId, String systemId,
	    String notation)
	throws SAXException
	{
	    checkExternalSubset ();
	    dtdHandler.unparsedEntityDecl (name, publicId,
		    systemId, notation);
	}

	public void notationDecl (String name,
	    String publicId, String systemId)
	throws SAXException
	{
	    checkExternalSubset ();
	    dtdHandler.notationDecl (name, publicId, systemId);
	}

	//
	// Sun DtdEventListener callbacks
	//	... SAX2 mutates/discards internal DTD subset
	//	... SAX2 discards NOTATION enumerated attr values 
	//
	public void startDtd (String name)
	throws SAXException
	{
	    rootName = name; /* save and use later */
	    extSubsetSysId = null;
	    startedDtd = false;
	}

	public void internalDtdDecl (String subset)
	throws SAXException
	{
	    // SAX2 discards the internal DTD subset (sigh); no real work.
	    if (startedDtd)
		return;

	    // SAX2 groups a mandatory (rootName) part of the DOCTYPE with
	    // an optional (external subset) one, which may never arrive.
	    // This handles the "no external subset" case.
	    lexicalHandler.startDTD (rootName, null, null);
	    startedDtd = true;
	}

	public void externalDtdDecl (String publicId, String systemId)
	throws SAXException
	{
	    lexicalHandler.startDTD (rootName, publicId, systemId);
	    extSubsetSysId = systemId;
	    startedDtd = true;
	}

	public void endDtd ()
	throws SAXException
	{
	    // SAX2 groups a mandatory (rootName) part of the DOCTYPE with
	    // an optional (external subset) one, which may never arrive.
	    // This handles the "neither external nor internal subset" case.
	    if (!startedDtd) {
		lexicalHandler.startDTD (rootName, null, null);
		startedDtd = true;

	    // SAX2 shows parameter entities, while Sun's parser hides most
	    // of them since seeing their expansions is in general useless.
	    } // else if (startedExternalSubset)
		// lexicalHandler.endEntity ("[dtd]");

	    lexicalHandler.endDTD ();
	    rootName = null;
	}

	public void attributeDecl (
	    String elementName,
	    String attributeName,
	    String type,
	    String options [],
	    String defaultValue,
	    boolean isFixed, boolean isRequired)
	throws SAXException
	{
	    String	valueDefaultType = null;

	    checkExternalSubset ();

	    // avoid the work below, if we clearly can do so
	    if (declHandler == defaultHandler)
		return;

	    if (isRequired)
		valueDefaultType = "#REQUIRED";
	    else if (isFixed)
		valueDefaultType = "#FIXED";
	    else if (defaultValue == null)
		valueDefaultType = "#IMPLIED";
	    
	    //
	    // if "type" is ENUMERATION, SAX2 needs us to
	    // rewrite it into "(option1|option2|...)"
	    //
	    if ("ENUMERATION".equals (type)) {
		// precondition: 'temp' is empty
		temp.append ('(');
		for (int i = 0; i < options.length; i++) {
		    if (i != 0)
			temp.append ('|');
		    temp.append (options [i]);
		}
		temp.append (')');
		type = temp.toString ();
		temp.setLength (0);
		// postcondition: 'temp' is empty
	    }

	    // if "type" is NOTATION, SAX2 discards important information,
	    // namely the particular notations which are permitted!

	    declHandler.attributeDecl (elementName, attributeName,
		type, valueDefaultType, defaultValue);
	}

	public void elementDecl (String name, String model)
	throws SAXException
	{
	    checkExternalSubset ();
	    declHandler.elementDecl (name, model);
	}

	public void externalEntityDecl (String name,
	    String publicId, String systemId)
	throws SAXException
	{
	    // Only general entities, not parameter entities
	    checkExternalSubset ();
	    declHandler.externalEntityDecl (name, publicId, systemId);
	}

	public void internalEntityDecl (String name, String value)
	throws SAXException
	{
	    // Only general entities, not parameter entities
	    checkExternalSubset ();
	    declHandler.internalEntityDecl (name, value);
	}
    }
}
