/*
 * $Id: SAXDriver.java,v 1.1.1.1 2003/02/01 02:10:10 cbj Exp $
 * Copyright (C) 1999-2001 David Brownell
 * 
 * This file is part of GNU JAXP, a library.
 *
 * GNU JAXP 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.
 * 
 * GNU JAXP 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
 *
 * As a special exception, if you link this library with other files to
 * produce an executable, this library does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * This exception does not however invalidate any other reasons why the
 * executable file might be covered by the GNU General Public License. 
 */

//
// Copyright (c) 1998 by Microstar Software Ltd.
// From Microstar's README (the entire original license):
//
//	Separate statements also said it's in the public domain.
//	All modifications are distributed under the license
//	above (GPL with library exception).
//
// AElfred is free for both commercial and non-commercial use and
// redistribution, provided that Microstar's copyright and disclaimer are
// retained intact.  You are free to modify AElfred for your own use and
// to redistribute AElfred with your modifications, provided that the
// modifications are clearly documented.
//
// 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.  Please use it AT
// YOUR OWN RISK.
//


package gnu.xml.aelfred2;

import java.io.*;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import java.util.Stack;

// maintaining 1.1 compatibility for now ... more portable, PJava, etc
// Iterator, Hashmap and ArrayList ought to be faster
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.xml.sax.*;
import org.xml.sax.ext.*;
import org.xml.sax.helpers.NamespaceSupport;


// $Id: SAXDriver.java,v 1.1.1.1 2003/02/01 02:10:10 cbj Exp $

/**
 * An enhanced SAX2 version of Microstar's &AElig;lfred XML parser.
 * The enhancements primarily relate to significant improvements in
 * conformance to the XML specification, and SAX2 support.  Performance
 * has been improved.  See the package level documentation for more
 * information.
 *
 * <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>(URL)/external-general-entities</td>
 *	<td>Value defaults to <em>true</em></td></tr>
 * <tr><td>(URL)/external-parameter-entities</td>
 *	<td>Value defaults to <em>true</em></td></tr>
 * <tr><td>(URL)/is-standalone</td>
 *	<td>(PRELIMINARY) Returns true iff the document's parsing
 *	has started (some non-error event after <em>startDocument()</em>
 *	was reported) and the document's standalone flag is set.</td></tr>
 * <tr><td>(URL)/namespace-prefixes</td>
 *	<td>Value defaults to <em>false</em> (but XML 1.0 names are
 *		always reported)</td></tr>
 * <tr><td>(URL)/lexical-handler/parameter-entities</td>
 *	<td>Value is fixed at <em>true</em></td></tr>
 * <tr><td>(URL)/namespaces</td>
 *	<td>Value defaults to <em>true</em></td></tr>
 * <tr><td>(URL)/resolve-dtd-uris</td>
 *	<td>(PRELIMINARY) Value defaults to <em>true</em></td></tr>
 * <tr><td>(URL)/string-interning</td>
 *	<td>Value is fixed at <em>true</em></td></tr>
 * <tr><td>(URL)/use-attributes2</td>
 *	<td>(PRELIMINARY) Value is fixed at <em>true</em></td></tr>
 * <tr><td>(URL)/use-entity-resolver2</td>
 *	<td>(PRELIMINARY) Value defaults to <em>true</em></td></tr>
 * <tr><td>(URL)/validation</td>
 *	<td>Value is fixed at <em>false</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>(URL)/declaration-handler</td>
 *	<td>A declaration handler may be provided.  </td></tr>
 * <tr><td>(URL)/lexical-handler</td>
 *	<td>A lexical handler may be provided.  </td></tr>
 * </table>
 *
 * <p>This parser currently implements the SAX1 Parser API, but
 * it may not continue to do so in the future.
 *
 * @author Written by David Megginson (version 1.2a from Microstar)
 * @author Updated by David Brownell &lt;dbrownell@users.sourceforge.net&gt;
 * @version $Date: 2003/02/01 02:10:10 $
 * @see org.xml.sax.Parser
 */
final public class SAXDriver
    implements Locator, Attributes2, XMLReader, Parser, AttributeList
{
    private final DefaultHandler2	base = new DefaultHandler2 ();
    private XmlParser			parser;

    private EntityResolver		entityResolver = base;
    private EntityResolver2		resolver2 = null;
    private ContentHandler		contentHandler = base;
    private DTDHandler			dtdHandler = base;
    private ErrorHandler 		errorHandler = base;
    private DeclHandler			declHandler = base;
    private LexicalHandler		lexicalHandler = base;

    private String			elementName = null;
    private Stack			entityStack = new Stack ();

    // could use just one vector (of object/struct): faster, smaller
    private Vector			attributeNames = new Vector ();
    private Vector			attributeNamespaces = new Vector ();
    private Vector			attributeLocalNames = new Vector ();
    private Vector			attributeValues = new Vector ();
    private boolean			attributeSpecified [] = new boolean[10];
    private boolean			attributeDeclared [] = new boolean[10];

    private boolean			namespaces = true;
    private boolean			xmlNames = false;
    private boolean			extGE = true;
    private boolean			extPE = true;
    private boolean			resolveAll = true;
    private boolean			useResolver2 = true;

    private int				attributeCount = 0;
    private boolean			attributes;
    private String			nsTemp [] = new String [3];
    private NamespaceSupport		prefixStack;

    //
    // Constructor.
    //

    /** Constructs a SAX Parser.  */
    public SAXDriver () {}


    //
    // Implementation of org.xml.sax.Parser.
    //

    /**
     * <b>SAX1</b>: Sets the locale used for diagnostics; currently,
     * only locales using the English language are supported.
     * @param locale The locale for which diagnostics will be generated
     */
    public void setLocale (Locale locale)
    throws SAXException
    {
	if ("en".equals (locale.getLanguage ()))
	    return ;

	throw new SAXException ("AElfred2 only supports English locales.");
    }


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

    /**
     * <b>SAX1, SAX2</b>: Set the entity resolver for this parser.
     * @param handler The object to receive entity events.
     */
    public void setEntityResolver (EntityResolver resolver)
    {
	if (resolver instanceof EntityResolver2)
	    resolver2 = (EntityResolver2) resolver;
	else
	    resolver2 = null;
	if (resolver == null)
	    resolver = base;
	entityResolver = resolver;
    }


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

    /**
     * <b>SAX1, SAX2</b>: Set the DTD handler for this parser.
     * @param handler The object to receive DTD events.
     */
    public void setDTDHandler (DTDHandler handler)
    {
	if (handler == null)
	    handler = base;
	this.dtdHandler = handler;
    }


    /**
     * <b>SAX1</b>: Set the document handler for this parser.  If a
     * content handler was set, this document handler will supplant it.
     * The parser is set to report all XML 1.0 names rather than to
     * filter out "xmlns" attributes (the "namespace-prefixes" feature
     * is set to true).
     *
     * @deprecated SAX2 programs should use the XMLReader interface
     *	and a ContentHandler.
     *
     * @param handler The object to receive document events.
     */
    public void setDocumentHandler (DocumentHandler handler)
    {
	contentHandler = new Adapter (handler);
	xmlNames = true;
    }

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

    /**
     * <b>SAX2</b>: Assigns the object used to report the logical
     * content of an XML document.  If a document handler was set,
     * this content handler will supplant it (but XML 1.0 style name
     * reporting may remain enabled).
     */
    public void setContentHandler (ContentHandler handler)
    {
	if (handler == null)
	    handler = base;
	contentHandler = handler;
    }

    /**
     * <b>SAX1, SAX2</b>: Set the error handler for this parser.
     * @param handler The object to receive error events.
     */
    public void setErrorHandler (ErrorHandler handler)
    {
	if (handler == null)
	    handler = base;
	this.errorHandler = handler;
    }

    /**
     * <b>SAX2</b>: Returns the object used to receive callbacks for XML
     * errors of all levels (fatal, nonfatal, warning); this is never null;
     */
    public ErrorHandler getErrorHandler ()
	{ return errorHandler == base ? null : errorHandler; }


    /**
     * <b>SAX1, SAX2</b>: Auxiliary API to parse an XML document, used mostly
     * when no URI is available.
     * If you want anything useful to happen, you should set
     * at least one type of handler.
     * @param source The XML input source.  Don't set 'encoding' unless
     *	you know for a fact that it's correct.
     * @see #setEntityResolver
     * @see #setDTDHandler
     * @see #setContentHandler
     * @see #setErrorHandler
     * @exception SAXException The handlers may throw any SAXException,
     *	and the parser normally throws SAXParseException objects.
     * @exception IOException IOExceptions are normally through through
     *	the parser if there are problems reading the source document.
     */
    public void parse (InputSource source)
    throws SAXException, IOException
    {
	synchronized (base) {
	    parser = new XmlParser ();
	    if (namespaces)
		prefixStack = new NamespaceSupport ();
	    else if (!xmlNames)
		throw new IllegalStateException ();
	    parser.setHandler (this);

	    try {

	      Reader r = source.getCharacterStream();
	      InputStream in = source.getByteStream();

	      
		parser.doParse (source.getSystemId (),
			      source.getPublicId (),
			      r,
			      in,
			      source.getEncoding ());
	    } catch (SAXException e) {
		throw e;
	    } catch (IOException e) {
		throw e;
	    } catch (RuntimeException e) {
		throw e;
	    } catch (Exception e) {
		throw new SAXParseException (e.getMessage (), this, e);
	    } finally {
		contentHandler.endDocument ();
		entityStack.removeAllElements ();
		parser = null;
		prefixStack = null;
	    }
	}
    }


    /**
     * <b>SAX1, SAX2</b>: Preferred API to parse an XML document, using a
     * system identifier (URI).
     */
    public void parse (String systemId)
    throws SAXException, IOException
    {
	parse (new InputSource (systemId));
    }

    //
    // Implementation of SAX2 "XMLReader" interface
    //
    static final String	FEATURE = "http://xml.org/sax/features/";
    static final String	PROPERTY = "http://xml.org/sax/properties/";

    /**
     * <b>SAX2</b>: Tells the value of the specified feature flag.
     *
     * @exception SAXNotRecognizedException thrown if the feature flag
     *	is neither built in, nor yet assigned.
     */
    public boolean getFeature (String featureId)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
	if ((FEATURE + "validation").equals (featureId))
	    return false;

	// external entities (both types) are optionally included
	if ((FEATURE + "external-general-entities").equals (featureId))
	    return extGE;
	if ((FEATURE + "external-parameter-entities") .equals (featureId))
	    return extPE;

	// element/attribute names are as written in document; no mangling
	if ((FEATURE + "namespace-prefixes").equals (featureId))
	    return xmlNames;

	// report element/attribute namespaces?
	if ((FEATURE + "namespaces").equals (featureId))
	    return namespaces;

	// all PEs and GEs are reported
	if ((FEATURE + "lexical-handler/parameter-entities").equals (featureId))
	    return true;

	// always interns
	if ((FEATURE + "string-interning").equals (featureId))
	    return true;
	
	// EXTENSIONS 1.1

	// always returns isSpecified info
	if ((FEATURE + "use-attributes2").equals (featureId))
	    return true;
	
	// meaningful between startDocument/endDocument
	if ((FEATURE + "is-standalone").equals (featureId)) {
	    if (parser == null)
		throw new SAXNotSupportedException (featureId);
	    return parser.isStandalone ();
	}

	// optionally don't absolutize URIs in declarations
	if ((FEATURE + "resolve-dtd-uris").equals (featureId))
	    return resolveAll;

	// optionally use resolver2 interface methods, if possible
	if ((FEATURE + "use-entity-resolver2").equals (featureId))
	    return useResolver2;
	
	throw new SAXNotRecognizedException (featureId);
    }

    // package private
    DeclHandler getDeclHandler () { return declHandler; }

    // package private
    boolean resolveURIs () { return resolveAll; }

    /**
     * <b>SAX2</b>:  Returns the specified property.
     *
     * @exception SAXNotRecognizedException thrown if the property value
     *	is neither built in, nor yet stored.
     */
    public Object getProperty (String propertyId)
    throws SAXNotRecognizedException
    {
	if ((PROPERTY + "declaration-handler").equals (propertyId))
	    return declHandler == base ? null : declHandler;

	if ((PROPERTY + "lexical-handler").equals (propertyId))
	    return lexicalHandler == base ? null : lexicalHandler;
	
	// unknown properties
	throw new SAXNotRecognizedException (propertyId);
    }

    /**
     * <b>SAX2</b>:  Sets the state of feature flags in this parser.  Some
     * built-in feature flags are mutable.
     */
    public void setFeature (String featureId, boolean value)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
	boolean	state;
	
	// Features with a defined value, we just change it if we can.
	state = getFeature (featureId);

	if (state == value)
	    return;
	if (parser != null)
	    throw new SAXNotSupportedException ("not while parsing");

	if ((FEATURE + "namespace-prefixes").equals (featureId)) {
	    // in this implementation, this only affects xmlns reporting
	    xmlNames = value;
	    // forcibly prevent illegal parser state
	    if (!xmlNames)
		namespaces = true;
	    return;
	}

	if ((FEATURE + "namespaces").equals (featureId)) {
	    namespaces = value;
	    // forcibly prevent illegal parser state
	    if (!namespaces)
		xmlNames = true;
	    return;
	}

	if ((FEATURE + "external-general-entities").equals (featureId)) {
	    extGE = value;
	    return;
	}
	if ((FEATURE + "external-parameter-entities") .equals (featureId)) {
	    extPE = value;
	    return;
	}
	if ((FEATURE + "resolve-dtd-uris").equals (featureId)) {
	    resolveAll = value;
	    return;
	}

	if ((FEATURE + "use-entity-resolver2").equals (featureId)) {
	    useResolver2 = value;
	    return;
	}

	throw new SAXNotRecognizedException (featureId);
    }

    /**
     * <b>SAX2</b>:  Assigns the specified property.  Like SAX1 handlers,
     * these may be changed at any time.
     */
    public void setProperty (String propertyId, Object value)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
	// see if the property is recognized
	getProperty (propertyId);

	// Properties with a defined value, we just change it if we can.

	if ((PROPERTY + "declaration-handler").equals (propertyId)) {
	    if (value == null)
		declHandler = base;
	    else if (! (value instanceof DeclHandler))
		throw new SAXNotSupportedException (propertyId);
	    else
		declHandler = (DeclHandler) value;
	    return ;
	}

	if ((PROPERTY + "lexical-handler").equals (propertyId)) {
	    if (value == null)
		lexicalHandler = base;
	    else if (! (value instanceof LexicalHandler))
		throw new SAXNotSupportedException (propertyId);
	    else
		lexicalHandler = (LexicalHandler) value;
	    return ;
	}

	throw new SAXNotSupportedException (propertyId);
    }


    //
    // This is where the driver receives XmlParser callbacks and translates
    // them into SAX callbacks.  Some more callbacks have been added for
    // SAX2 support.
    //

    void startDocument ()
    throws SAXException
    {
	contentHandler.setDocumentLocator (this);
	contentHandler.startDocument ();
	attributeNames.removeAllElements ();
	attributeValues.removeAllElements ();
    }

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

    InputSource getExternalSubset (String name, String baseURI)
    throws SAXException, IOException
    {
	if (resolver2 == null || !useResolver2 || !extPE)
	    return null;
	return resolver2.getExternalSubset (name, baseURI);
    }

    InputSource resolveEntity (boolean isPE, String name,
    	InputSource in, String baseURI)
    throws SAXException, IOException
    {
	InputSource	source;

	// external entities might be skipped
	if (isPE && !extPE)
	    return null;
	if (!isPE && !extGE)
	    return null;

	// ... or not
	lexicalHandler.startEntity (name);
	if (resolver2 != null && useResolver2) {
	    source = resolver2.resolveEntity (name, in.getPublicId (),
			baseURI, in.getSystemId ());
	    if (source == null) {
		in.setSystemId (absolutize (baseURI,
				in.getSystemId (), false));
		source = in;
	    }
	} else {
	    in.setSystemId (absolutize (baseURI, in.getSystemId (), false));
	    source = entityResolver.resolveEntity (in.getPublicId (),
			in.getSystemId ());
	    if (source == null)
		source = in;
	}
	startExternalEntity (name, source.getSystemId (), true);
	return source;
    }

    // absolutize a system ID relative to the specified base URI
    // (temporarily) package-visible for external entity decls
    String absolutize (String baseURI, String systemId, boolean nice)
    throws MalformedURLException, SAXException
    {
	// FIXME normalize system IDs -- when?
	// - Convert to UTF-8
	// - Map reserved and non-ASCII characters to %HH

	try {
	    if (baseURI == null) {
		warn ("No base URI; hope this SYSTEM id is absolute: "
			+ systemId);
		return new URL (systemId).toString ();
	    } else
		return new URL (new URL (baseURI), systemId).toString ();

	} catch (MalformedURLException e) {

	    // Let unknown URI schemes pass through unless we need
	    // the JVM to map them to i/o streams for us...
	    if (!nice)
		throw e;

	    // sometimes sysids for notations or unparsed entities
	    // aren't really URIs...
	    warn ("Can't absolutize SYSTEM id: " + e.getMessage ());
	    return systemId;
	}
    }

    void startExternalEntity (String name, String systemId,
    	boolean stackOnly)
    throws SAXException
    {
	if (systemId == null)
	    warn ("URI was not reported to parser for entity " + name);
	if (!stackOnly)		// spliced [dtd] needs startEntity
	    lexicalHandler.startEntity (name);
	entityStack.push (systemId);
    }

    void endExternalEntity (String name)
    throws SAXException
    {
	if (!"[document]".equals (name))
	    lexicalHandler.endEntity (name);
	entityStack.pop ();
    }

    void startInternalEntity (String name)
    throws SAXException
    {
	lexicalHandler.startEntity (name);
    }

    void endInternalEntity (String name)
    throws SAXException
    {
	lexicalHandler.endEntity (name);
    }

    void doctypeDecl (String name, String publicId, String systemId)
    throws SAXException
    {
	lexicalHandler.startDTD (name, publicId, systemId);
	
	// ... the "name" is a declaration and should be given
	// to the DeclHandler (but sax2 doesn't).

	// the IDs for the external subset are lexical details,
	// as are the contents of the internal subset; but sax2
	// doesn't provide the internal subset "pre-parse"
    }

    void notationDecl (String name, String ids [])
    throws SAXException
    {
	try {
	    dtdHandler.notationDecl (name, ids [0],
		(resolveAll && ids [1] != null)
			? absolutize (ids [2], ids [1], true)
			: ids [1]);
	} catch (IOException e) {
	    // "can't happen"
	    throw new SAXParseException (e.getMessage (), this, e);
	}
    }

    void unparsedEntityDecl (String name, String ids [], String notation)
    throws SAXException
    {
	try {
	    dtdHandler.unparsedEntityDecl (name, ids [0],
		    resolveAll
			? absolutize (ids [2], ids [1], true)
			: ids [1],
		    notation);
	} catch (IOException e) {
	    // "can't happen"
	    throw new SAXParseException (e.getMessage (), this, e);
	}
    }

    void endDoctype ()
    throws SAXException
    {
	lexicalHandler.endDTD ();
    }

    private void declarePrefix (String prefix, String uri)
    throws SAXException
    {
	int index = uri.indexOf (':');

	// many versions of nwalsh docbook stylesheets 
	// have bogus URLs; so this can't be an error...
	if (index < 1 && uri.length () != 0)
	    warn ("relative URI for namespace: " + uri);

	// FIXME:  char [0] must be ascii alpha; chars [1..index]
	// must be ascii alphanumeric or in "+-." [RFC 2396]

	uri = uri.intern ();
	prefixStack.declarePrefix (prefix, uri);
	contentHandler.startPrefixMapping (prefix, uri);
    }

    void attribute (String qname, String value, boolean isSpecified)
    throws SAXException
    {
	if (!attributes) {
	    attributes = true;
	    if (namespaces)
		prefixStack.pushContext ();
	}

	// process namespace decls immediately;
	// then maybe forget this as an attribute
	if (namespaces) {
	    int	index;

	    // default NS declaration?
	    if ("xmlns" == qname) {
		declarePrefix ("", value);
		if (!xmlNames)
		    return;
	    }

	    // NS prefix declaration?
	    else if ((index = qname.indexOf (':')) == 5
		    && qname.startsWith ("xmlns")) {
		String		prefix = qname.substring (6);

		if (value.length () == 0) {
		    verror ("missing URI in namespace decl attribute: "
				+ qname);
		} else
		    declarePrefix (prefix, value);
		if (!xmlNames)
		    return;
	    }
	}

	// remember this attribute ...

	if (attributeCount == attributeSpecified.length) { 	// grow array?
	    boolean temp [] = new boolean [attributeSpecified.length + 5];
	    System.arraycopy (attributeSpecified, 0, temp, 0, attributeCount);
	    attributeSpecified = temp;
	}
	attributeSpecified [attributeCount] = isSpecified;

	attributeCount++;
	attributeNames.addElement (qname);
	// attribute type comes from querying parser's DTD records
	attributeValues.addElement (value);

	// ... patching {lname, uri} later, if needed
	attributeNamespaces.addElement ("");
	attributeLocalNames.addElement ("");
    }

    void startElement (String elname)
    throws SAXException
    {
	ContentHandler handler = contentHandler;

	//
	// NOTE:  this implementation of namespace support adds something
	// like six percent to parsing CPU time, in a large (~50 MB)
	// document that doesn't use namespaces at all.  (Measured by PC
	// sampling, with a bug where endElement processing was omitted.)
	// [Measurement referred to older implementation, older JVM ...]
	//
	// It ought to become notably faster in such cases.  Most
	// costs are the prefix stack calling Hashtable.get() (2%),
	// String.hashCode() (1.5%) and about 1.3% each for pushing
	// the context, and two chunks of name processing.
	//

	if (!attributes) {
	    if (namespaces)
		prefixStack.pushContext ();
	} else if (namespaces) {

	    // now we can patch up namespace refs; we saw all the
	    // declarations, so now we'll do the Right Thing
	    for (int i = 0; i < attributeCount; i++) {
		String	qname = (String) attributeNames.elementAt (i);
		int	index;

		// default NS declaration?
		if ("xmlns" == qname)
		    continue;

		index = qname.indexOf (':');

		// NS prefix declaration?
		if (index == 5 && qname.startsWith ("xmlns"))
		    continue;

		// it's not a NS decl; patch namespace info items
		if (prefixStack.processName (qname, nsTemp, true) == null)
		    verror ("undeclared attribute prefix in: " + qname);
		else {
		    attributeNamespaces.setElementAt (nsTemp [0], i);
		    attributeLocalNames.setElementAt (nsTemp [1], i);
		}
	    }
	}

	// save element name so attribute callbacks work
	elementName = elname;
	if (namespaces) {
	    if (prefixStack.processName (elname, nsTemp, false) == null) {
		verror ("undeclared element prefix in: " + elname);
		nsTemp [0] = nsTemp [1] = "";
	    }
	    handler.startElement (nsTemp [0], nsTemp [1], elname, this);
	} else
	    handler.startElement ("", "", elname, this);
	// elementName = null;

	// elements with no attributes are pretty common!
	if (attributes) {
	    attributeNames.removeAllElements ();
	    attributeNamespaces.removeAllElements ();
	    attributeLocalNames.removeAllElements ();
	    attributeValues.removeAllElements ();
	    attributeCount = 0;
	    attributes = false;
	}
    }

    void endElement (String elname)
    throws SAXException
    {
	ContentHandler	handler = contentHandler;

	if (!namespaces) {
	    handler.endElement ("", "", elname);
	    return;
	}
	prefixStack.processName (elname, nsTemp, false);
	handler.endElement (nsTemp [0], nsTemp [1], elname);

	Enumeration	prefixes = prefixStack.getDeclaredPrefixes ();

	while (prefixes.hasMoreElements ())
	    handler.endPrefixMapping ((String) prefixes.nextElement ());
	prefixStack.popContext ();
    }

    void startCDATA ()
    throws SAXException
    {
	lexicalHandler.startCDATA ();
    }

    void charData (char ch[], int start, int length)
    throws SAXException
    {
	contentHandler.characters (ch, start, length);
    }

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

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

    void processingInstruction (String target, String data)
    throws SAXException
    {
	contentHandler.processingInstruction (target, data);
    }

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

    void fatal (String message)
    throws SAXException
    {
	SAXParseException fatal;
	
	fatal = new SAXParseException (message, this);
	errorHandler.fatalError (fatal);

	// Even if the application can continue ... we can't!
	throw fatal;
    }

    // We can safely report a few validity errors that
    // make layered SAX2 DTD validation more conformant
    void verror (String message)
    throws SAXException
    {
	SAXParseException err;
	
	err = new SAXParseException (message, this);
	errorHandler.error (err);
    }

    void warn (String message)
    throws SAXException
    {
	SAXParseException err;
	
	err = new SAXParseException (message, this);
	errorHandler.warning (err);
    }


    //
    // Implementation of org.xml.sax.Attributes.
    //

    /**
     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
     * (don't invoke on parser);
     */
    public int getLength ()
    {
	return attributeNames.size ();
    }

    /**
     * <b>SAX2 Attributes</b> method (don't invoke on parser);
     */
    public String getURI (int index)
    {
	return (String) (attributeNamespaces.elementAt (index));
    }

    /**
     * <b>SAX2 Attributes</b> method (don't invoke on parser);
     */
    public String getLocalName (int index)
    {
	return (String) (attributeLocalNames.elementAt (index));
    }

    /**
     * <b>SAX2 Attributes</b> method (don't invoke on parser);
     */
    public String getQName (int i)
    {
	return (String) (attributeNames.elementAt (i));
    }

    /**
     * <b>SAX1 AttributeList</b> method (don't invoke on parser);
     */
    public String getName (int i)
    {
	return (String) (attributeNames.elementAt (i));
    }

    /**
     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
     * (don't invoke on parser);
     */
    public String getType (int i)
    {
	String	type = parser.getAttributeType (elementName, getQName (i));
	if (type == null)
	    return "CDATA";
	// ... use DeclHandler.attributeDecl to see enumerations
	if (type == "ENUMERATION")
	    return "NMTOKEN";
	return type;
    }


    /**
     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
     * (don't invoke on parser);
     */
    public String getValue (int i)
    {
	return (String) (attributeValues.elementAt (i));
    }


    /**
     * <b>SAX2 Attributes</b> method (don't invoke on parser);
     */
    public int getIndex (String uri, String local)
    {
	int length = getLength ();

	for (int i = 0; i < length; i++) {
	    if (!getURI (i).equals (uri))
		continue;
	    if (getLocalName (i).equals (local))
		return i;
	}
	return -1;
    }


    /**
     * <b>SAX2 Attributes</b> method (don't invoke on parser);
     */
    public int getIndex (String xmlName)
    {
	int length = getLength ();

	for (int i = 0; i < length; i++) {
	    if (getQName (i).equals (xmlName))
		return i;
	}
	return -1;
    }


    /**
     * <b>SAX2 Attributes</b> method (don't invoke on parser);
     */
    public String getType (String uri, String local)
    {
	int index = getIndex (uri, local);

	if (index < 0)
	    return null;
	return getType (index);
    }


    /**
     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
     * (don't invoke on parser);
     */
    public String getType (String xmlName)
    {
	int index = getIndex (xmlName);

	if (index < 0)
	    return null;
	return getType (index);
    }


    /**
     * <b>SAX Attributes</b> method (don't invoke on parser);
     */
    public String getValue (String uri, String local)
    {
	int index = getIndex (uri, local);

	if (index < 0)
	    return null;
	return getValue (index);
    }


    /**
     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
     * (don't invoke on parser);
     */
    public String getValue (String xmlName)
    {
	int index = getIndex (xmlName);

	if (index < 0)
	    return null;
	return getValue (index);
    }


    //
    // Implementation of org.xml.sax.ext.Attributes2
    //


    /** @return false unless the attribute was declared in the DTD.
     * @throws java.lang.ArrayIndexOutOfBoundsException
     *   When the supplied index does not identify an attribute.
     */    
    public boolean isDeclared (int index)
    {
	if (index < 0 || index >= attributeCount) 
	    throw new ArrayIndexOutOfBoundsException ();
	return attributeDeclared [index];
    }

    /** @return false unless the attribute was declared in the DTD.
     * @throws java.lang.IllegalArgumentException
     *   When the supplied names do not identify an attribute.
     */
    public boolean isDeclared (java.lang.String qName)
    {
	int index = getIndex (qName);
	if (index < 0)
	    throw new IllegalArgumentException ();
	return attributeDeclared [index];
    }

    /** @return false unless the attribute was declared in the DTD.
     * @throws java.lang.IllegalArgumentException
     *   When the supplied names do not identify an attribute.
     */
    public boolean isDeclared (java.lang.String uri, java.lang.String localName)
    {
	int index = getIndex (uri, localName);
	if (index < 0)
	    throw new IllegalArgumentException ();
	return attributeDeclared [index];
    }


    /**
     * <b>SAX-ext Attributes2</b> method (don't invoke on parser);
     */
    public boolean isSpecified (int index)
    {
	if (index < 0 || index >= attributeCount) 
	    throw new ArrayIndexOutOfBoundsException ();
	return attributeSpecified [index];
    }

    /**
     * <b>SAX-ext Attributes2</b> method (don't invoke on parser);
     */
    public boolean isSpecified (String uri, String local)
    {
	int index = getIndex (uri, local);

	if (index < 0)
	    throw new IllegalArgumentException ();
	return attributeSpecified [index];
    }

    /**
     * <b>SAX-ext Attributes2</b> method (don't invoke on parser);
     */
    public boolean isSpecified (String xmlName)
    {
	int index = getIndex (xmlName);

	if (index < 0)
	    throw new IllegalArgumentException ();
	return attributeSpecified [index];
    }


    //
    // Implementation of org.xml.sax.Locator.
    //

    /**
     * <b>SAX Locator</b> method (don't invoke on parser);
     */
    public String getPublicId ()
    {
	return null; 		// FIXME track public IDs too
    }

    /**
     * <b>SAX Locator</b> method (don't invoke on parser);
     */
    public String getSystemId ()
    {
	if (entityStack.empty ())
	    return null;
	else
	    return (String) entityStack.peek ();
    }

    /**
     * <b>SAX Locator</b> method (don't invoke on parser);
     */
    public int getLineNumber ()
    {
	return parser.getLineNumber ();
    }

    /**
     * <b>SAX Locator</b> method (don't invoke on parser);
     */
    public int getColumnNumber ()
    {
	return parser.getColumnNumber ();
    }

    // adapter between SAX2 content handler and SAX1 document handler callbacks
    private static class Adapter implements ContentHandler
    {
	private DocumentHandler		docHandler;

	Adapter (DocumentHandler dh)
	    { docHandler = dh; }


	public void setDocumentLocator (Locator l)
	    { docHandler.setDocumentLocator (l); }
	
	public void startDocument () throws SAXException
	    { docHandler.startDocument (); }
	
	public void processingInstruction (String target, String data)
	throws SAXException
	    { docHandler.processingInstruction (target, data); }
	
	public void startPrefixMapping (String prefix, String uri)
	    { /* ignored */ }

	public void startElement (
	    String	namespace,
	    String	local,
	    String	name,
	    Attributes	attrs
	) throws SAXException
	    { docHandler.startElement (name, (AttributeList) attrs); }

	public void characters (char buf [], int offset, int len)
	throws SAXException
	    { docHandler.characters (buf, offset, len); }

	public void ignorableWhitespace (char buf [], int offset, int len)
	throws SAXException
	    { docHandler.ignorableWhitespace (buf, offset, len); }

	public void skippedEntity (String name)
	    { /* ignored */ }

	public void endElement (String u, String l, String name)
	throws SAXException
	    { docHandler.endElement (name); }

	public void endPrefixMapping (String prefix)
	    { /* ignored */ }

	public void endDocument () throws SAXException
	    { docHandler.endDocument (); }
    }
}
