/*
 * (C) Copyright IBM Corp. 1997-1998  All rights reserved.
 *
 * US Government Users Restricted Rights Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * IBM will not be liable for any damages suffered by you as a result
 * of using the Program. In no event will IBM be liable for any
 * special, indirect or consequential damages or lost profits even if
 * IBM has been advised of the possibility of their occurrence. IBM
 * will not be liable for any third party claims against you.
 */

package com.ibm.xml.parser;

import java.io.*;
import java.util.*;
import org.w3c.dom.AttributeDefinition;

/**
 * Parser is the XML4J parser that examines an XML document and generates an object tree
 * of Document Object Model (DOM) Nodes.  Details of these Nodes are provided by the
 * references below.
 * 
 * @version 1.1
 * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
 * @see com.ibm.xml.parser.TXDocument
 * @see com.ibm.xml.parser.TXDocumentContext
 * @see com.ibm.xml.parser.TXElement
 * @see com.ibm.xml.parser.TXText
 * @see com.ibm.xml.parser.TXComment
 * @see com.ibm.xml.parser.TXPI
 * @see com.ibm.xml.parser.NamespacePI
 * @see com.ibm.xml.parser.StylesheetPI
 * @see com.ibm.xml.parser.TXCDATASection
 * @see com.ibm.xml.parser.TXNotation
 * @see com.ibm.xml.parser.TXAttributeList
 * @see com.ibm.xml.parser.TXAttribute
 * @see com.ibm.xml.parser.DTD
 * @see com.ibm.xml.parser.ElementDecl
 * @see com.ibm.xml.parser.Attlist
 * @see com.ibm.xml.parser.AttDef
 * @see com.ibm.xml.parser.Entity
 */
public class Parser {
    
            StreamProducer      streamProducer              =   null;
            EntityPool          entityPool                  =   null;
            ElementFactory      elementFactory              =   null;
            int                 errorCount                  =   0;
            int                 warnCount                   =   0;
            boolean             doExternalDTD               =   true;
            boolean             isKeepComment               =   true;
            boolean             isErrorNoByteMark           =   true;
            boolean             isAllowJavaEncodingName     =   false;
            boolean             isPreserveSpace             =   false;
            boolean             isEndBy1stError             =   false;
            TXDocument          document                    =   null;
            DTD                 dtd                         =   null;
            Hashtable           attributeErrors             =   null;
            Hashtable           contentModelErrors          =   null;
            Vector              generalReferences           =   new Vector();
            Vector              parameterEntityReferences   =   new Vector();
            ReferenceHandler    referenceHandler            =   null;
            int                 readerBufferSize            =   32*1024;
    
    private static final String s_enameall                  =   " ALL ";
    
    private ResourceBundle      resourceBundle              =   null;
    private ErrorListener       errorListener               =   null;
    private TagHandler          tagHandler                  =   null;
    private Vector              preRootHandlers             =   null;
    private Vector              piHandlers                  =   null;
    private Hashtable           elementHandlerHash          =   null;
    private Vector              requiredAttributeHandlers   =   null;
    private boolean             isProcessNamespace          =   false;
    private boolean             isWarningRedefinedEntity    =   true;
    private boolean             isWarningNoXMLDecl          =   true;
    private boolean             isWarningNoDoctypeDecl      =   false;
    private Vector              idRefPool                   =   null;
    private Vector              notationNames               =   null;
    private CMNode              contentModelPCData          =   null;   
    private int                 parenLevel                  =   -1;

    /**
     * Constructor when using the default <code>ErrorListener</code> and <code>StreamProducer</code>.
     * The default <code>ErrorListener</code> and <code>StreamProducer</code> are defined by
     * the <code>Stderr</code> class.
     * @param   name    URL or filespec to use as the default input stream; if a filespec is
     *                  provided, this value can include a drive and directory spec.
     *                  This value is also used to associate a name with errors reported to the
     *                  default error listener that have a <var>file</var> parameter 
     *                  <code>=null</code>.
     * @see com.ibm.xml.parser.Stderr
     * @see com.ibm.xml.parser.ErrorListener
     * @see com.ibm.xml.parser.StreamProducer
     */
    public Parser(String name) {
        this(name, null, null);
    }

    /**
     * Constructor when NOT using the default <CODE>ErrorListener</CODE> or <CODE>StreamProducer</CODE>.
     * The default <code>ErrorListener</code> and <code>StreamProducer</code> are defined by
     * the <code>Stderr</code> class.
     * @param   name    This parameter is only used if either <var>errorListener</var> or
     *                  <var>streamProducer</var> are allowed to default.  If <var>streamProducer</var>
     *                  is allowed to default, this value is the URL or filespec to use as 
     *                  the default input stream; if a filespec is provided, this value can 
     *                  include a drive and directory spec.  If <var>errorListener</var> is
     *                  allowed to default, this value is used to associate a name with 
     *                  errors reported to the default error listener that have a 
     *                  <var>file</var> parameter <code>=null</code>.
     * @param errorListener Handler for error and warning events that are recognized by this 
     *                  parser, or <var>null</var> if to use the default listener.
     * @param streamProducer Mechanism for manipulating this parser's input stream, or
     *                  <var>null</var> if to use the default stream producer.
     * @see com.ibm.xml.parser.Stderr
     * @see com.ibm.xml.parser.ErrorListener
     * @see com.ibm.xml.parser.StreamProducer
     */
    public Parser(String name, ErrorListener errorListener, StreamProducer streamProducer) {
        Stderr se = null;
        if (null == errorListener || null == streamProducer)  se = new Stderr(name);
        this.errorListener = null == errorListener ? se : errorListener;
        this.streamProducer = null == streamProducer ? se : streamProducer;
        init();
    }
    
   /**
     * Sets a locale for error and warning messages.
     * <p>By default, the current user locale's resource bundle is used.  If no messages
     * exist for the requested <var>locale</var>, US English messages are used.
     * @param   locale  A Java Locale object.
     */
    public void setLocale(java.util.Locale locale) {
        this.resourceBundle = ListResourceBundle.getBundle("com.ibm.xml.parser.r.Message", locale);
    }

    /**
     * Sets the code to receive control when the various components of the Document Object
     * Model (DOM) and XML4J need to be created and initialized.  By default, the
     * <code>DefaultElementFactory</code> is used.
     * @param   elementFactory  Factory to use to create and initialize objects.
     * @see com.ibm.xml.parser.ElementFactory
     * @see com.ibm.xml.parser.DefaultElementFactory
     */
    public void setElementFactory(ElementFactory elementFactory) {
        this.elementFactory = elementFactory;
    }

   /**
     * <p>Returns the source of the input stream (could be a character stream or a byte stream) 
     * based on the entity specified by the system ID and/or public ID, and the 
     * <var>streamProducer</var> specified when this parser was constructed.
     * @param   name        The name to be associated with the input stream.  For example, 
     *                      this name could be the public ID of an external ID, or a local filename.
     * @param   publicID    Entity's public ID, or <var>null</var> if no public ID (see ExternalID for details).    
     * @param   systemID    Entity's system ID.    
     * @return              The resolved source of the input stream, or <var>null</var> if unable to resolve.
     * @exception IOException   Thrown if unable to open the source defined by the specified IDs.
     * @see com.ibm.xml.parser.ExternalID
     * @see #closeInputStream
     * @see com.ibm.xml.parser.StreamProducer#getInputStream
     */
    public Source getInputStream(String name, String publicID, String systemID) throws IOException {
        return this.streamProducer.getInputStream(name, publicID, systemID);
    }
    
   /**
     * <p>Removes the input stream currently in use. 
     * @param   source      Source of the input stream to be closed.
     * @see #getInputStream
     * @see com.ibm.xml.parser.StreamProducer#closeInputStream
     */
    public void closeInputStream(Source source) {
        this.streamProducer.closeInputStream(source);
    }
    
   /**
     * Register a handler for receiving control when tag start and tag end events are 
     * recognized by this parser.
     * By default, no tag handler is registered to this parser.
     * @param   tagHandler  Handler to be registered.
     * @see com.ibm.xml.parser.TagHandler
     */
    public void setTagHandler(TagHandler tagHandler) {
        this.tagHandler = tagHandler;
    }

   /**
     * Register a handler for receiving control when a General Reference is recognized by 
     * this parser.
     * By default, no reference handler is registered to this parser.
     * @param   referenceHandler    Handler to be registered.
     * @see com.ibm.xml.parser.ReferenceHandler
     */
    public void setReferenceHandler(ReferenceHandler referenceHandler) {
        this.referenceHandler = referenceHandler;
    }
    
   /**
     * Register a handler for receiving control from this parser after the internal and/or 
     * external DTD subsets are parsed, but before the document's root Element is parsed.
     * By default, no preroot handlers are registered to this parser.
     * @param   preRootHandler  Handler to be registered.
     * @see com.ibm.xml.parser.PreRootHandler
     */
    public void addPreRootHandler(PreRootHandler preRootHandler) {
        if (null == this.preRootHandlers)  this.preRootHandlers = new Vector();
        this.preRootHandlers.addElement(preRootHandler);
    }

   /**
     * Register a handler for receiving control when PIs are recognized by this parser.
     * By default, no PI handlers are registered to this parser.
     * @param   piHandler   Handler to be registered.
     * @see com.ibm.xml.parser.PIHandler
     */
    public void addPIHandler(PIHandler piHandler) {
        if (null == this.piHandlers)  this.piHandlers = new Vector();
        this.piHandlers.addElement(piHandler);
    }
    
   /**
     * Register a handler for receiving control when ANY element tag is recognized by this
     * parser.
     * By default, no element handlers are registered to this parser.
     * @param   elementHandler  Handler to be registered.
     * @see com.ibm.xml.parser.ElementHandler
     */
    public void addElementHandler(ElementHandler elementHandler) {
        addElementHandler(elementHandler, s_enameall);
    }
    
   /**
     * Register a handler for receiving control when the specified <var>elementTagName</var>
     * element tag is recognized by this parser.
     * By default, no element handlers are registered to this parser.
     * @param   elementHandler  Handler to be registered for the specified <var>elementTagName</var>.
     * @param   elementTagName  Element name to be watched for by this parser.  If the 
     *                          Element's name has a namespace prefix, that prefix should
     *                          be included.   
     * @see com.ibm.xml.parser.ElementHandler
     */
    public void addElementHandler(ElementHandler elementHandler, String elementTagName) {
        if (null == this.elementHandlerHash)  this.elementHandlerHash = new Hashtable();
        Vector v = (Vector)this.elementHandlerHash.get(elementTagName);
        if (null == v) {
            v = new Vector();
            this.elementHandlerHash.put(elementTagName, v);
        }
        v.addElement(elementHandler);
    }

   /**
     * Register a handler for receiving control when a "no required attribute" event is 
     * recognized by this parser.  A "no required attribute" event occurs when an Element 
     * is parsed and a required Attribute for this Element is not seen; Attributes are 
     * declared as required as part of the internal and/or external DTD declaration of the 
     * XML document.
     * <p>By default, no required attribute handlers are registered to this parser.
     * @param   noRequiredAttributeHandler  Handler to be registered.
     * @see com.ibm.xml.parser.NoRequiredAttributeHandler
     */
    public void addNoRequiredAttributeHandler(NoRequiredAttributeHandler noRequiredAttributeHandler) {
        if (null == this.requiredAttributeHandlers)  this.requiredAttributeHandlers = new Vector();
        this.requiredAttributeHandlers.addElement(noRequiredAttributeHandler);
    }

    /**
     * Returns the number of errors and exceptions currently encountered by this parser.
     * Warning messages do not count in this number.
     * @return      Number of errors and exceptions encountered by this parser.
     * @see com.ibm.xml.parser.ErrorListener
     */
    public int getNumberOfErrors() {
        return this.errorCount;
    }

    /**
     * Returns the number of warnings currently encountered by this parser.
     * @return      Number of warnings encountered by this parser.
     * @see com.ibm.xml.parser.ErrorListener
     */
    public int getNumberOfWarnings() {
        return this.warnCount;
    }

    /**
     * Sets whether <code>TXComment</code> Nodes are created by this parser when XML
     * comments are encountered.
     * <p>By default, <code>TXComment</code> Nodes are created by this parser.
     * @param   isKeepComment   <code>=true</code> means maintain parsed comments; 
     *                          <code>=false</code> means drop comments when creating object tree.
     * @see com.ibm.xml.parser.TXComment
     */
    public void setKeepComment(boolean isKeepComment) {
        this.isKeepComment = isKeepComment;
    }

    /**
     * Sets whether namespaces are respected by this parser.  Namespace notation can occur
     * on the names of <code>TXElement, TXPI, and TXAttribute</code> Nodes.  If namespaces
     * are respected, the methods defined by the <code>Namespace</code> interface will
     * function properly in these Nodes.
     * <p>By default, namespaces are NOT respected by this parser.
     * @param   isProcessNamespace  <code>=true</code> means parse for namespaces; 
     *                              <code>=false</code> means ignore namespaces when parsing.
     * @see com.ibm.xml.parser.Namespace
     * @see com.ibm.xml.parser.TXElement
     * @see com.ibm.xml.parser.TXPI
     * @see com.ibm.xml.parser.TXAttribute
     */
    public void setProcessNamespace(boolean isProcessNamespace) {
        this.isProcessNamespace = isProcessNamespace;
    }

    /**
     * Sets whether this parser allows Java encoding names to be specified in place of
     * MIME charset names on <var>xmlEncoding</var> parameters.  The <code>MIME2Java</code>
     * class is provided to facilitate conversion when Java encoding names are not allowed.
     * <p>By default, Java encoding names are NOT allowed on <var>xmlEncoding</var> parameters
     * by this parser.
     * @param   isAllowJavaEncodingName <code>=true</code> means Java encoding names are allowed; 
     *                                  <code>=false</code> means MIME charset names must be used.
     * @see com.ibm.xml.parser.TXDocument#setEncoding
     * @see com.ibm.xml.parser.DTD#setEncoding
     * @see com.ibm.xml.parser.MIME2Java
     */
    public void setAllowJavaEncodingName(boolean isAllowJavaEncodingName) {
        this.isAllowJavaEncodingName = isAllowJavaEncodingName;
    }

    /**
     * Sets, at the document level, whether space is to be preserved in the parsed document.  
     * Regardless of the value of <var>isPreserveSpace</var>, this parser will preserve 
     * whitespace.  This value is used, for example, to determine if space is to be preserved
     * in Text Nodes during print() operations.  Note that the document-level value will be 
     * overridden at the Node level if this parser recognizes a <code>xml:space</code> 
     * attribute.
     * <p>By default, space is not to be preserved in the parsed document.
     * @param   isPreserveSpace <code>=true</code> space is to be preserved; 
     *                          <code>=false</code> space is to be ignored.
     * @see com.ibm.xml.parser.TXElement#setPreserveSpace
     * @see com.ibm.xml.parser.TXText#setIsIgnorableWhitespace
     */
    public void setPreserveSpace(boolean isPreserveSpace) {
        this.isPreserveSpace = isPreserveSpace;
    }

    /**
     * Sets whether this parser treats missing byte marks in the input stream as an error,
     * and reports this condition through the defined <code>ErrorListener</code>.
     * <p>By default, this condition is reported by this parser as an error.
     * @param   isErrorNoByteMark   <code>=true</code> means report condition as an error; 
     *                              <code>=false</code> means ignore this condition.
     */
    public void setErrorNoByteMark(boolean isErrorNoByteMark) {
        this.isErrorNoByteMark = isErrorNoByteMark;
    }

    /**
     * Sets whether this parser treats redefined entities as a warning,
     * and reports this condition through the defined <code>ErrorListener</code>.
     * <p>By default, this condition is reported by this parser as a warning.
     * @param   isWarningRedefinedEntity    <code>=true</code> means report condition as a warning; 
     *                                      <code>=false</code> means ignore this condition.
     * @see com.ibm.xml.parser.Entity
     */
    public void setWarningRedefinedEntity(boolean isWarningRedefinedEntity) {
        this.isWarningRedefinedEntity = isWarningRedefinedEntity;
    }

    /**
     * Sets whether this parser treats a missing XML prolog PIs as a warning,
     * and reports this condition through the defined <code>ErrorListener</code>.
     * <p>By default, this condition is reported by this parser as a warning.
     * @param   isWarningNoXMLDecl  <code>=true</code> means report condition as a warning; 
     *                              <code>=false</code> means ignore this condition.
     * @see com.ibm.xml.parser.TXPI
     */
    public void setWarningNoXMLDecl(boolean isWarningNoXMLDecl) {
        this.isWarningNoXMLDecl = isWarningNoXMLDecl;
    }

    /**
     * Sets whether this parser treats a missing DTD declaration as a warning,
     * and reports this condition through the defined <code>ErrorListener</code>.
     * <p>By default, this condition is NOT reported by this parser as a warning.
     * @param   isWarningNoXMLDecl  <code>=true</code> means report condition as a warning; 
     *                              <code>=false</code> means ignore this condition.
     * @see com.ibm.xml.parser.DTD
     */
    public void setWarningNoDoctypeDecl(boolean isWarningNoDoctypeDecl) {
        this.isWarningNoDoctypeDecl = isWarningNoDoctypeDecl;
    }

    /**
     * Sets whether this parser ends job by the first error.
     * <P>By default, this parser continues parsing after an error occurs
     */
    public void setEndBy1stError(boolean isEndBy1stError) {
        this.isEndBy1stError = isEndBy1stError;
    }

    /**
     * Returns a parsed XML document based on the specified <var>inputStream</var>.
     * The returned document can then be accessed by methods such as <code>TXDocument#getDocumentElement</code>.
     * <p>Once this method is invoked, the parser instance is of no further use, and should NOT be reused.
     * @param   inputStream Byte-stream-oriented data source.
     * @return              Parsed XML document (should never be <var>null</var>).
     * @see com.ibm.xml.parser.Source
     * @see com.ibm.xml.parser.TXDocument#getDocumentElement
     * @see #readDTDStream
     */
    public TXDocument readStream(InputStream inputStream) {
        return readStream(new Source(inputStream));
    }
    
    /**
     * Returns a parsed XML document based on the specified <var>reader</var>.
     * The returned document can then be accessed by methods such as <code>TXDocument#getDocumentElement</code>.
     * <p>Once this method is invoked, the parser instance is of no further use, and should NOT be reused.
     * @param   reader      Character-stream-oriented data source.
     * @return              Parsed XML document (should never be <var>null</var>).
     * @see com.ibm.xml.parser.Source
     * @see com.ibm.xml.parser.TXDocument#getDocumentElement
     * @see #readDTDStream
     */
    public TXDocument readStream(Reader reader) {
        return readStream(new Source(reader));
    }
    
    /**
     * Returns a parsed XML document based on the specified <var>source</var>.
     * The returned document can then be accessed by methods such as <code>TXDocument#getDocumentElement</code>.
     * <p>Once this method is invoked, the parser instance is of no further use, and should NOT be reused.
     * @param   source      Byte-stream-oriented or character-stream-oriented data source.
     * @return              Parsed XML document (should never be <var>null</var>).
     * @see com.ibm.xml.parser.Source
     * @see com.ibm.xml.parser.TXDocument#getDocumentElement
     * @see #readDTDStream
     */
    public TXDocument readStream(Source source) {
        init2();

        this.document = this.elementFactory.createDocument();
        Token tok = null;

        try {
            tok = new Token(this, null, source);

            /*
             * document ::= XMLDecl? Misc* (doctypedecl Misc*)? elemnt Misc*
             * Misc ::= Comment | PI | S
             */
            int state = 0;
            int ch;
            while (0 <= (ch = tok.m_next)) {
                if ('<' == ch) {
                    tok.getChar();              // '<'
                    ch = tok.m_next;
                    if ('?' == ch) {            // PI (including ?XML)
                        TXPI p = parsePI(tok, 0 == state);
                        if (null != p) {
                            this.document.addElement(p);
                            applyPIHandlers(p);
                        }
                    
                    } else if ('!' == ch) {     // comment or DOCTYPE
                        tok.getChar();          // !
                                                // <!-- | <![CDATA[
                        ch = tok.m_next;
                        if ('-' == ch) {
                            tok.getChar();      // 1st '-'
                            ch = tok.m_next;
                            if ('-' == ch) {    // comment
                                tok.getChar();  // 2nd '-'
                                if (this.isKeepComment)
                                    this.document.addElement(this.elementFactory.createComment(tok.getComment()));
                                else
                                    tok.getComment();
                            } else {
                                format0(tok, "E_COM0");
                                tok.skipTagEnd();
                            }
                        } else {
                            String n = tok.getName();
                            if (null == n || !"DOCTYPE".equals(n)) {
                                format0(tok, "E_DOCTYPE0");
                                tok.skipTagEnd();
                            } else {
                                if (2 > state)
                                    state = 2;
                                else
                                    format0(tok, "E_STRUCT3");
                                parseDOCTYPE(tok);
                            }
                        }

                    } else if (3 > state) {     // root element
                        Exception ex = null;
                        TXElement t = null;
                        try {
                            t = parseElement(tok, this.document, this.isPreserveSpace, true, true);
                        } catch (Exception e) {
                            ex = e;
                        }
                        if (null != t) {
                            t.setParent(this.document);
                            if (null != this.tagHandler)
                                this.tagHandler.handleEndTag(t, t.isEmpty());
                            t = applyHandlers(t);
                            if (null != t)
                                this.document.addElement(t);
                        }
                        state = 3;
                        if (null != ex)  throw ex;
                    }

                } else if (XMLChar.isSpace((char)ch)) {
                    tok.getWhitespaces();
                    this.document.addElement(this.elementFactory.createText(tok.m_tstrbuf, 0, tok.m_tbufpos, true));

                } else {                        // !Char
                    format0(tok, "E_STRUCT0");
                    tok.nextTagStart();
                }
                if (0 == state)  state = 1;
            }

            if (isWarningNoXMLDecl()) {
                if (null == this.document.getVersion())
                    format0(tok, "W_STRUCT4");
            }
            if (isWarningNoDoctypeDecl()) {
                if (null == this.document.getDTD())
                    format0(tok, "W_STRUCT5");
            }

            // Error if an element isn't processed
            if (null == this.document.getDocumentElement()) {
                format0(tok, "E_STRUCT2");
            }

            tok.close();
            //tok.report();

            checkIdref();
        } catch (TerminateSignal ts) {
                                                // Nothing to do.
        } catch (Exception e) {
            this.errorCount ++;
            String mes = null == e.getMessage() ? e.toString() : e.getMessage();
            if (null == tok) {
                this.errorListener.error(null, 0, 0, e, mes);
            } else {
                this.errorListener.error(tok.getFileName(), tok.getLineNumber(),
                                         tok.getCharLocation(), e, mes);
                try {
                    tok.close();
                } catch (Exception ex) {}
            }
            if (!(e instanceof IOException))
                e.printStackTrace();
        }
        TXDocument doc = this.document;
        clean();
        return doc;
    }

    /**
     * Returns a parsed external DTD subset based on the specified <var>inputStream</var>.
     * The returned document can then be accessed by methods such as <code>DTD#externalElements</code>.
     * <p>Once this method is invoked, the parser instance is of no further use, and should NOT be reused.
     * @param   inputStream Byte-stream-oriented data source.
     * @return              Parsed DTD (should never be <var>null</var>).
     * @see com.ibm.xml.parser.Source
     * @see com.ibm.xml.parser.DTD
     * @see #readStream
     */
    public DTD readDTDStream(InputStream inputStream) throws IOException {
        return readDTDStream(new Source(inputStream));
    }
    
    /**
     * Returns a parsed external DTD subset based on the specified <var>reader</var>.
     * The returned document can then be accessed by methods such as <code>DTD#externalElements</code>.
     * <p>Once this method is invoked, the parser instance is of no further use, and should NOT be reused.
     * @param   reader      Character-stream-oriented data source.
     * @return              Parsed DTD (should never be <var>null</var>).
     * @see com.ibm.xml.parser.Source
     * @see com.ibm.xml.parser.DTD
     * @see #readStream
     */
    public DTD readDTDStream(Reader reader) throws IOException {
        return readDTDStream(new Source(reader));
    }
    
    /**
     * Returns a parsed external DTD subset based on the specified <var>source</var>.
     * The returned document can then be accessed by methods such as <code>DTD#externalElements</code>.
     * <p>Once this method is invoked, the parser instance is of no further use, and should NOT be reused.
     * @param   source      Byte-stream-oriented or character-stream-oriented data source.
     * @return              Parsed DTD (should never be <var>null</var>).
     * @see com.ibm.xml.parser.Source
     * @see com.ibm.xml.parser.DTD
     * @see #readStream
     */
    public DTD readDTDStream(Source source) throws IOException {
        init2();
        Token extoken = null;
        this.dtd = this.elementFactory.createDTD();
        this.dtd.setEntityPool(this.entityPool);
        try {
            extoken = new Token(this, null, source);
            this.dtd.setParsingExternal(true);
            parseMarkupDecl(this.dtd, extoken, true);
            this.dtd.setParsingExternal(false);
            extoken.close();
        } catch (Exception e) {
            if (null == extoken)
                this.errorListener.error(null, 0, 0, e, e.toString());
            else
                error(extoken, e);
        }
        DTD d = this.dtd;
        clean();
        return d;
    }
    
    /**
     * Returns the size of this parser's character-stream-oriented input source buffer.  
     * Characters are buffered during I/O in order to provide efficient reading.  
     * This method has no meaning for byte-stream-oriented input sources.
     * @return      Size of this parser's character-stream-oriented input buffer.
     * @see #setReaderBufferSize
     */
    public int getReaderBufferSize() {
        return this.readerBufferSize;
    }

    /**
     * Sets the size of this parser's character-stream-oriented input source buffer.  
     * Characters are buffered during I/O in order to provide efficient reading.  
     * This method has no meaning for byte-stream-oriented input sources.
     * <p>The default buffer size is large enough for most purposes.
     * @param   readerBufferSize    Size of this parser's character-stream-oriented input buffer.
     * @see #getReaderBufferSize
     */
    public void setReaderBufferSize(int readerBufferSize) {
        this.readerBufferSize = readerBufferSize;
    }

    String getString(String key) {
        String s = this.resourceBundle.getString(key);
        return null == s ? "**** Internal Error: No message ***" : s;
    }
    
    void format0(FileScanner fileScanner, String key) {
        error(fileScanner, key, getString(key));
    }
    
    String format1(String key, String param1) {
        Object[] ao = new Object[1];
        ao[0] = param1;
        return java.text.MessageFormat.format(getString(key), ao);
    }
    
    void format1(FileScanner fileScanner, String key, String param1) {
        error(fileScanner, key, format1(key, param1));
    }
    
    String format2(String key, String param1, String param2) {
        Object[] ao = new Object[2];
        ao[0] = param1;
        ao[1] = param2;
        return java.text.MessageFormat.format(getString(key), ao);
    }
    
    void format2(FileScanner fileScanner, String key, String param1, String param2) {
        error(fileScanner, key, format2(key, param1, param2));
    }
    
    String format3(String key, String param1, String param2, String param3) {
        Object[] ao = new Object[3];
        ao[0] = param1;
        ao[1] = param2;
        ao[2] = param3;
        return java.text.MessageFormat.format(getString(key), ao);
    }
    
    void format3(FileScanner fileScanner, String key, String param1, String param2, String param3) {
        error(fileScanner, key, format3(key, param1, param2, param3));
    }
    
    String format4(String key, String param1, String param2, String param3, String param4) {
        Object[] ao = new Object[4];
        ao[0] = param1;
        ao[1] = param2;
        ao[2] = param3;
        ao[3] = param4;
        return java.text.MessageFormat.format(getString(key), ao);
    }

    private static class TerminateSignal extends RuntimeException {
    }

    void error(FileScanner fileScanner, Object key, String msg) {
        boolean fatal = key instanceof String && ((String)key).startsWith("E_");
        if (fatal)
            this.errorCount ++;
        else
            this.warnCount ++;
        this.errorListener.error(fileScanner.getFileName(), fileScanner.getLineNumber(), 
                                 fileScanner.getCharLocation(), key, msg);
        if (this.isEndBy1stError && fatal)  throw new TerminateSignal();
    }
    
    void error(FileScanner fileScanner, Exception e) {
        this.errorCount ++;
        this.errorListener.error(fileScanner.getFileName(), fileScanner.getLineNumber(), 
                                 fileScanner.getCharLocation(), e, e.toString());
        if (this.isEndBy1stError)  throw new TerminateSignal();
    }
    
    boolean isErrorNoByteMark() {
        return this.isErrorNoByteMark;
    }
    
    boolean isWarningRedefinedEntity() {
        return this.isWarningRedefinedEntity;
    }
    
    boolean isWarningNoXMLDecl() {
        return this.isWarningNoXMLDecl;
    }

    TXPI parsePI(Token tok, boolean acceptxml) throws IOException {
        return parsePI(tok, acceptxml, false);
    }
    
    TXPI parsePI(Token tok, boolean acceptxml, boolean allowtextdecl) throws IOException {
        tok.getChar();              // '?'
        String n = tok.getName();
        if (null == n) {
            format0(tok, "E_PI0");
            tok.skipTagEnd();
            return null;
        }

        if ("xml".equals(n)) {                  // <?xml
            if (allowtextdecl) {
                TXAttribute[] aa = tok.getPIAttributes(true);
                String enc = null;
                for (int i = 0;  i < aa.length;  i ++) {
                    String aname = aa[i].getName();
                    if ("encoding".equals(aname)) {
                        enc = aa[i].getValue();
                    } else if ("version".equals(aname)) {
                                                // ignore
                    } else {
                        format1(tok, "E_PI6", aname);
                    }
                }
                if (null == enc)
                    format0(tok, "E_PI7");
                tok.getChar();
                return null;
            }            // Process <?xml ...?>
            if (!acceptxml) {
                format0(tok, "E_STRUCT1");
                tok.skipTagEnd();
                return null;
            }
            TXAttribute[] aa = tok.getPIAttributes(true);
            tok.getChar();
            if (0 == aa.length) {
                format0(tok, "E_XML1");
                return null;
            }
            if (!"version".equals(aa[0].getName())) {
                format0(tok, "E_XML2");
                return null;
            }
            if (!Util.checkVersionNum(aa[0].getValue())) {
                format1(tok, "E_XML6", aa[0].getValue());
                return null;
            }
            if (!"1.0".equals(aa[0].getValue())) {
                format0(tok, "E_XML4");
                return null;
            }
            this.document.setVersion(aa[0].getValue());

            int i = 1;
            boolean encoding = false;
            if (i < aa.length
                && "encoding".equals(aa[i].getName())) {
                String enc = aa[i].getValue();
                this.document.setEncoding(enc);
                i ++;
                encoding = true;
            }
            if (i < aa.length
                && "standalone".equals(aa[i].getName())) {
                String rv = aa[i].getValue();
                if ("yes".equals(rv)) {
                    this.document.setStandalone(rv);
                    this.doExternalDTD = false;
                } else if ("no".equals(rv)) {
                    this.document.setStandalone(rv);
                } else {
                    format1(tok, "E_XML3", rv);
                }
                i ++;
            }
            if (!encoding)
                tok.getReading().setEncoding("UTF-8", false);
            if (i < aa.length) {
                format1(tok, "E_XML5", aa[i].getName());
            }
            return null;

        } else if ("xml:namespace".equals(n)) {
            TXAttribute[] aa = tok.getPIAttributes(false);
            tok.getChar();
            String url = null, as = null, src = null;
            StringBuffer sb = new StringBuffer(256);
            sb.append(" ");
            for (int i = 0;  i < aa.length;  i ++) {
                TXAttribute a = aa[i];
                if ("ns".equals(a.getName())) {
                    url = a.getValue();
                } else if ("prefix".equals(a.getName())) {
                    as = a.getValue();
                } else if ("src".equals(a.getName())) {
                    src = a.getValue();
                } else {
                    format1(tok, "E_XMLNS4", a.getName());
                }
                if (i != 0)  sb.append((char)' ');
                sb.append(a.toXMLString());
            }
            if (null == url) {
                format0(tok, "E_XMLNS0");
                return this.elementFactory.createPI(n, sb.toString());
            }
            int iv = Util.getInvalidURIChar(url);
            if (0 <= iv) {
                format2(tok, "E_EXT4", intstring(url.charAt(iv)), url);
            }
            if (null == as) {
                format0(tok, "E_XMLNS1");
                return this.elementFactory.createPI(n, sb.toString());
            }
            if (!Util.checkName(as) || 0 <= as.indexOf(':') || "xml".equals(as)) {
                format1(tok, "E_XMLNS2", as);
            }
            if (null != src) {
                iv = Util.getInvalidURIChar(src);
                if (0 <= iv)
                    format2(tok, "E_EXT4", intstring(src.charAt(iv)), src);
            }
            
            NamespacePI npi = this.elementFactory.createNamespacePI(n, sb.toString(), url, as, src);
            if (null != this.document.getNamespaceFor(as)) {
                format1(tok, "E_XMLNS3", as);
            }
            return npi;

        } else if (StylesheetPI.S_XMLSTYLESHEET.equals(n)
                   || StylesheetPI.S_XMLALTSTYLESHEET.equals(n)) {
            TXAttribute[] aa = tok.getPIAttributes(false);
            tok.getChar();
            String type = null, href = null, title = null;
            StringBuffer sb = new StringBuffer(256);
            sb.append(" ");
            for (int i = 0;  i < aa.length;  i ++) {
                TXAttribute a = aa[i];
                if ("type".equals(a.getName())) {
                    type = a.getValue();
                } else if ("href".equals(a.getName())) {
                    href = a.getValue();
                } else if ("title".equals(a.getName())) {
                    title = a.getValue();
                } else {
                    format1(tok, "E_XMLSS0", a.getName());
                }
                if (i != 0)  sb.append((char)' ');
                sb.append(a.toXMLString());
            }
            if (null == type) {
                format0(tok, "E_XMLSS1");
                return this.elementFactory.createPI(n, sb.toString());
            }
            if (null == href) {
                format0(tok, "E_XMLSS2");
                return this.elementFactory.createPI(n, sb.toString());
            }
            int iv = Util.getInvalidURIChar(href);
            if (0 <= iv) {
                format2(tok, "E_EXT4", intstring(href.charAt(iv)), href);
            }
            
            StylesheetPI spi = this.elementFactory.createStylesheetPI(n, sb.toString(),
                                                                   type, href, title);
            return spi;

        } else if (n.length() >= 3 && "xml".equalsIgnoreCase(n.substring(0, 3))) {
            format0(tok, "E_PI5");
            return null;

        } else {                // PI except ?xml / ?xml:namespace
            /*
            if (!XMLChar.isSpace((char)tok.getChar())) {
                format0(tok, "E_PI1");
                tok.nextTagStart();
                return null;
            }
            tok.skipSpaces();
            */
            /*
            if (XMLChar.isSpace((char)tok.m_next)) {
                tok.skipSpaces();
            }
            */
            String pidata = tok.getPIData();
            if (null == pidata) {
                format0(tok, "E_PI2");
                tok.nextTagStart();
                return null;
            }

            TXPI pi = this.elementFactory.createPI(n, pidata);
                                                // Namespace
            if (this.isProcessNamespace) {
                String ret = this.document.setNamespaceParameters(pi);
                if (null != ret)
                    format1(tok, ret, n);
            }
            return pi;
        }
    }

    void checkNamespace(Token tok, String q, String ns, String lp) {
        if (null == this.document.getNamespaceFor(ns)) {
            format1(tok, "E_TAGf", q);
        }
        if (0 <= lp.indexOf(':')) {
            format1(tok, "E_TAGe", q);
        }
    }

    /*
     * tok.m_next is next character of '<'.
     *
     * ispreservespace: t:preserve, nil:default(check this.isPreserveSpace)
     */
    TXElement parseElement(Token tok, Parent par, boolean ispreservespace)
        throws IOException {
        return parseElement(tok, par, ispreservespace, false, false);
    }
    
    TXElement parseElement(Token tok, Parent par, boolean ispreservespace,
                         boolean checktagname, boolean dohandler) throws IOException {
        String name = tok.getName();
        if (null == name) {
            format0(tok, "E_TAG0");
            tok.nextTagStart();
            return null;
        }

        if (dohandler)
            applyPreRootHandlers(name);

                                                // check DOCTYPE
        if (checktagname && null != this.document.getDTD()
            && !name.equals(this.document.getDTD().getName())) {
            format2(tok, "E_TAG5", this.document.getDTD().getName(), name);
        }
                                                // 
        TXAttribute[] aa = tok.getAttributes(this.isProcessNamespace);
        int ch = tok.getChar();
        if (0 > ch)  throw new EOFException("com.ibm.xml.parser.Parser#parseElement(): "+getString("E_EOF"));
        if ('/' != ch && '>' != ch) {
            format0(tok, "E_TAG1");
            tok.nextTagStart();
        }
        boolean isempty = false;
        if ('/' == ch) {
            isempty = true;
            ch = tok.getChar();
            if (0 > ch)  throw new EOFException("com.ibm.xml.parser.Parser#parseElement(): "+getString("E_EOF"));
            if ('>' != ch) {
                format0(tok, "E_TAG1");
                tok.nextTagStart();
            }
        }

        TXElement tag = this.elementFactory.createElement(name);
                                                // Namespace
        if (this.isProcessNamespace) {
            String ret = this.document.setNamespaceParameters(tag);
            if (null != ret)  format1(tok, ret, name);
        }
        tag.setParent(par);
        for (int i = 0;  i < aa.length;  i ++)
            tag.setAttributeNode(aa[i]);
        tag.setPreserveSpace(ispreservespace);

        if (isApplySyntax())  checkSyntax(tok, tag, isempty);

        if (null != this.tagHandler)
            this.tagHandler.handleStartTag(tag, isempty);
                                                //
        String xmls = tag.getAttribute("xml:space");
        if (null != xmls) {
            if ("preserve".equals(xmls)) {
                ispreservespace = true;
            } else if ("default".equals(xmls)) {
                ispreservespace = this.isPreserveSpace;
            }
            tag.setPreserveSpace(ispreservespace);
        }

        if (isempty)  return tag;
        /*
        if (null != this.document.getDTD()) {
            ContentModel cm = this.document.getDTD().getContentModel(tag.getName());
            if (null != cm && ElementDecl.EMPTY == cm.getType()) {
                format1(tok, "E_TAGk", tag.getName());
                return tag;
            }
        }
        */

        /*
         * content ::= (element | PCData | Reference | CDSect | PI | Commnet)*
         * ETag ::= '</' Name S? '>'
         */
        Child chi = null;
        while (0 <= (ch = tok.m_next)) {
            chi = parseSingleContent(tok, tag, ispreservespace);
            if (chi == tag)  break;
            else if (null != chi) {
                if (chi instanceof TXPI) {
                    tag.addElement(chi);
                    applyPIHandlers((TXPI)chi);
                } else if (chi instanceof TXElement) {
                    TXElement txel = (TXElement)chi;
                    txel.setParent(tag);
                    if (null != this.tagHandler)
                        this.tagHandler.handleEndTag(txel, txel.isEmpty());
                    chi = applyHandlers(txel);
                    if (null != chi)  tag.addElement(chi);
                } else if (chi instanceof TXCDATASection) {
                    tag.addElement(chi);
                } else if (chi instanceof TXText) {
                    tag.addTextElement((TXText)chi);
                } else if (chi instanceof GeneralReference) {
                    expandGeneralReference(tag, (GeneralReference)chi);
                } else {
                    tag.addElement(chi);
                }
            }
        }
        if (tag != chi)
            format1(tok, "E_TAG3", tag.getName());
        /*
        if (!ispreservespace && 0 < tag.size()) {
            Child chi = tag.elementAt(tag.size()-1);
            if (chi instanceof TextElement
                && Util.checkAllSpace(chi.getText()))
                tag.removeElementAt(tag.size()-1);
        }
        */
        //if (0 > ch)  throw new EOFException(getString("E_EOF"));

        if (isApplySyntax())
            checkContentModel(tok, tag);

        return tag;
    }

    void expandGeneralReference(TXElement el, GeneralReference gr) {
        org.w3c.dom.NodeIterator ne = gr.getChildNodes();
        Child ch;
        while (null != (ch = (Child)ne.toNextNode()))
            if (ch instanceof GeneralReference)
                expandGeneralReference(el, (GeneralReference)ch);
            else if (TXText.isText(ch))
                el.addTextElement((TXText)ch);
            else
                el.addElement(ch);
    }

    /*
     * content ::= (element | PCData | Reference | CDSect | PI | Commnet)*
     * ETag ::= '</' Name S? '>'
     */
    Child parseSingleContent(Token tok, TXElement parent,
                             boolean ispreservespace) throws IOException {
        return parseSingleContent(tok, parent, ispreservespace, false);
    }
    
    Child parseSingleContent(Token tok, TXElement parent,
                             boolean ispreservespace, boolean allowtextdecl) throws IOException {
        int ch;
        if (0 > (ch = tok.m_next))  return null;
        if ('<' == ch) {
            tok.getChar();                      // '<'
            ch = tok.m_next;
            if ('?' == ch) {                // PI
                TXPI p = parsePI(tok, false, allowtextdecl);
                if (allowtextdecl && null == p)
                    return parseSingleContent(tok, parent, ispreservespace, false);
                /*
                if (null != p) {
                    tag.addElement(p);
                    applyPIHandlers(p);
                }*/
                return p;

            } else if ('!' == ch) {             // comment | CDSect
                                                // <!-- | <![CDATA[
                tok.getChar();                  // '!'
                ch = tok.m_next;
                if ('-' == ch) {
                    tok.getChar();              // 1st '-'
                    if ('-' == tok.m_next) {    // comment
                        tok.getChar();          // 2nd '-'
                        if (this.isKeepComment)
                            return this.elementFactory.createComment(tok.getComment());
                        else
                            tok.getComment();
                    } else {
                        format0(tok, "E_COM0");
                        tok.skipTagEnd();
                    }

                } else if ('[' == ch) {         // CDATA
                    tok.getChar();              // 1st '['
                    String n = tok.getName();
                    if (null == n || !n.equals("CDATA") || '[' != tok.m_next) {
                        format0(tok, "E_CDATA0");
                        tok.skipTagEnd();
                    } else {
                        tok.getChar();          // 2nd '['
                        String d = tok.getCDATA();
                        if (null != d)
                            return this.elementFactory.createCDATA(d);
                    }
                } else {
                    format0(tok, "E_!0");
                    tok.skipTagEnd();
                }

            } else if ('/' == ch) {             // ETag  '</' Name S? '>'
                tok.getChar();                  // '/'
                if (null == parent) {
                    format0(tok, "E_TAGg");
                    return null;
                }
                String n = tok.getName();
                boolean nomatch = false;
                if (null == n || !n.equals(parent.getTagName())) {
                    format1(tok, "E_TAG3", parent.getTagName());
                    nomatch = true;
                }
                tok.skipSpaces();
                if ('>' != tok.m_next) {
                    format0(tok, "E_TAG4");
                } else
                    ch = tok.getChar();         // '>'

                                                // Recovery
                if (nomatch && null != n) {
                    Parent par = (Parent)parent.getParentNode();
                    while (par instanceof TXElement) {
                        if (((TXElement)par).getTagName().equals(n)) {
                            pushPE(null);
                            tok.addReading(new StringReading("</"+n+">"));
                            break;
                        }
                        par = (Parent)par.getParentNode();
                    }
                }

                return parent;                  // a special value which means an endtag

            } else {                            // element
                return parseElement(tok, parent, ispreservespace);
            }

        } else if ('&' == ch) {                 // Reference
            tok.getChar();                      // '&'
            boolean ischarref = '#' == tok.m_next;
            tok.doReference(true);
            if (ischarref) {
                return this.elementFactory.createText(tok.m_tstrbuf, 0, tok.m_tbufpos, false);
            } else {
                String entname = tok.m_lastreference;
                if (null == entname)  return null;
                GeneralReference gr = this.elementFactory.createGeneralReference(entname);
                startGeneralReference(entname);
                if (!pushRef(entname)) {
                    format2(tok, "E_TAGi", entname, makeReferencePath(this.generalReferences, entname));
                    return null;
                }

                Entity ev = tok.m_lastentity;
                if (null == ev)  return null;
                if (ev.isNDATA()) {
                    return null;
                }
                Token toks = ev.createToken(this, tok);
                boolean td = ev.isExternal();
                Child chi;
                /*
                do {
                    chi = parseSingleContent(toks, null, ispreservespace, td);
                    td = false;
                    if (null != chi)  gr.addElement(chi);
                } while (null != chi);
                if (0 <= toks.m_next) {
                    format0(toks, "E_TAGh");
                }
                */
                do {
                    chi = parseSingleContent(toks, null, ispreservespace, td);
                    td = false;
                    if (null != chi)  gr.addElement(chi);
                } while (toks.m_next >= 0);
                toks.close();
                endGeneralReference(entname);
                popRef(entname);
                return gr;
            }

        } else if (XMLChar.isChar((char)ch)) {  // PCData
            tok.getPCData();
            if (0 < tok.m_tbufpos) {
                if (tok.includebrabragt)
                    format0(tok, "E_TAGj");
                boolean ignorable = !ispreservespace && tok.allspacepcdata;
                return this.elementFactory.createText(tok.m_tstrbuf, 0, tok.m_tbufpos, ignorable);
            }

        } else {
            tok.getChar();
            format1(tok, "E_INVCHAR0", Integer.toString(ch, 16));
        }
        return null;
    }

    boolean pushRef(String n) {
        if (0 <= this.generalReferences.indexOf(n))
            return false;
        this.generalReferences.addElement(n);
        return true;
    }
    
    void popRef(String n) {
        this.generalReferences.removeElement(n);
    }
    
    boolean pushPE(String n) {
        if (n == null) {
            this.parameterEntityReferences.addElement(n);
            return true;
        }
        if (0 <= this.parameterEntityReferences.indexOf(n))
            return false;
        this.parameterEntityReferences.addElement(n);
        return true;
    }
    
    String popPE() {
        String ret = null;
        if (this.parameterEntityReferences.size() > 0) {
            int ind = this.parameterEntityReferences.size()-1;
            ret = (String)this.parameterEntityReferences.elementAt(ind);
            this.parameterEntityReferences.removeElementAt(ind);
        }
        return ret;
    }

    /*
     * attributecontent ::= (PCData | Reference)*
     */
    Child parseAttributeContent(Token tok, int startchar) throws IOException {
        int ch;
        if (0 > (ch = tok.m_next))  return null;
        if ('&' == ch) {                        // Reference
            tok.getChar();                      // '&'
            boolean ischarref = '#' == tok.m_next;
            String refv = tok.doReference(false);
            if (ischarref) {
                // Normalize
                if (tok.m_tbufpos == 1 && XMLChar.isSpace(tok.m_tstrbuf[0]))
                    tok.m_tstrbuf[0] = ' ';
                return new TXText(new String(tok.m_tstrbuf, 0, tok.m_tbufpos));
            } else {
                String entname = tok.m_lastreference;
                if (null == entname)  return null;
                GeneralReference gr = this.elementFactory.createGeneralReference(entname);
                startGeneralReference(entname);
                if (!pushRef(entname)) {
                    format2(tok, "E_TAGi", entname, makeReferencePath(this.generalReferences, entname));
                    return null;
                }

                Token toks = new Token(this, refv);
                toks.getReading().setNext(tok.getReading());
                toks.setFollow(false);
                Child chi;
                do {
                    chi = parseAttributeContent(toks, -1);
                    if (null != chi)  gr.addElement(chi);
                } while (null != chi);
                if (0 <= toks.m_next) {
                    format0(toks, "E_TAGh");
                }
                toks.close();
                endGeneralReference(entname);
                popRef(entname);
                return gr;
            }

        } else if (XMLChar.isChar((char)ch)) {  // PCData
            String pcdata = tok.getPCDataForAttribute(startchar);
            if (0 < pcdata.length())
                //return this.elementFactory.createText(pcdata);
                // Don't call factory for SAX driver.
                return new TXText(pcdata);

        } else {
            tok.getChar();
            format1(tok, "E_INVCHAR0", Integer.toString(ch, 16));
        }
        return null;
    }
    
    /*
     * m_next is S after `<!DOCTYPE'.
     * doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' %markupdecl* ']' S?)? '>'
     */
    void parseDOCTYPE(Token tok) throws Exception {
        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
        }
        tok.skipSpaces();
        String docname = tok.getName();
        if (null == docname) {
            format0(tok, "E_DOCTYPE1");
            tok.skipTagEnd();
            return;
        }

        tok.skipSpaces();

                                                // ExternalID | '[' | '>'
        ExternalID eid = tok.parseExternalID();
        this.dtd = this.elementFactory.createDTD(docname, eid);
        this.dtd.setEntityPool(this.entityPool);
        //dtd.setRootName(docname);
        //dtd.setExternalID(eid);
        this.document.addElement(this.dtd);

        tok.skipSpaces();
        if ('[' == tok.m_next) {
            tok.setState(Token.ST_INTERNALDTD);
            tok.getChar();                      // '['
            parseMarkupDecl(this.dtd, tok, false);
            tok.setState(Token.ST_NORMAL);
            if (']' == tok.m_next)  tok.getChar();
            tok.skipSpaces();
        }
        int ch = tok.m_next;
        if (0 > ch)  throw new EOFException("com.ibm.xml.parser.Parser#parseDOCTYPE(): "+getString("E_EOF"));
        if ('>' != ch) {
            format0(tok, "E_TAG4");
        } else {
            tok.getChar();                      // '>'
        }
                                                // load external DTD
        if (null != eid && null != eid.getSystemLiteral() && this.doExternalDTD) {
            Token extoken = null;
            try {
                String name = eid.getSystemLiteral();
                if (null == name)  name = eid.getPubidLiteral();
                if (null == name)  name = "[DTD]";
                extoken = new Token(this, eid.getSystemLiteral(),
                                          this.streamProducer.getInputStream(name, eid.getPubidLiteral(),
                                                              eid.getSystemLiteral()));
                this.dtd.setParsingExternal(true);
                extoken.setState(Token.ST_EXTERNALDTD);
                parseMarkupDecl(this.dtd, extoken, true);
                this.dtd.setParsingExternal(false);
                extoken.close();
            } catch (Exception e) {
                error(null != extoken ? extoken : tok, e);
            }
        }
        checkNotationNames();

        //if (0 == dtd.getInternalSize()+dtd.getExternalSize())
        //    format0(tok, "E_DOCTYPE3");
    }

    void parseMarkupDecl(DTD par, Token tok, boolean allowencodingpi) throws Exception {
        while (parseSingleMarkupDecl(par, tok, allowencodingpi))
            allowencodingpi = false;
    }


    /*  '[' == tok.m_next */
    void parseConditionalSect(DTD par, Token tok) throws Exception {
        tok.getChar();                          // '['
        tok.skipSpaces();

        String n = tok.getName();
        tok.skipSpaces();
        if ('[' != tok.m_next || null == n) {
            format0(tok, "E_COND0");
            tok.skipTagEnd();
            return;
        }
        tok.getChar();                          // '['
        if ("INCLUDE".equals(n)) {
            parseMarkupDecl(par, tok, true);
            if (']' != tok.m_next) {
                format0(tok, "E_COND1");
                return;
            }
            tok.getChar();                      // ']'
            if (']' != tok.m_next) {
                format0(tok, "E_COND1");
                return;
            }
            tok.getChar();                      // ']'
            if ('>' != tok.m_next) {
                format0(tok, "E_COND1");
                return;
            }
            tok.getChar();                      // '>'

        } else if ("IGNORE".equals(n)) {
            int prevst = tok.setState(Token.ST_NORMAL);
            ignoreSectContents(par, tok);
            tok.setState(prevst);

        } else {
            format0(tok, "E_COND0");
            tok.skipTagEnd();
        }
    }
    
    /*
      ignoreSectContents ::= ((SkipLit | Comment | PI) - (Char* ']]>' Char*))
                           | ('<![' ignoreSectContents* ']]>')  
                           | (Char - (']' | [<'"]))  
                           | ('<!' (Char - ('-' | '[')))

      "..."
      '...'
      <!-- ... -->
      <?Name ...?>
      <![ ignore ]]>
      Char (not ]<'")
      <!
    */
    void ignoreSectContents(Parent par, Token tok) throws Exception {
        int ch;
        while (0 <= (ch = tok.m_next) && ']' != ch) {
            if ('"' == ch || '\'' == ch) {      // SkipLit
                tok.getChar();
                int start = ch;
                while (0 <= (ch = tok.m_next)) {
                    if (start == ch)  break;
                    if (!XMLChar.isChar((char)ch)) {
                        format1(tok, "E_INVCHAR0", Integer.toString(ch, 16));
                        break;
                    }
                    tok.getChar();
                }
                if (0 >= ch)  throw new EOFException("com.ibm.xml.parser.Parser#ignoreSectContents(): "+getString("E_EOF"));
                tok.getChar();

            } else if ('<' == ch) {
                tok.getChar();                  // '<'
                ch = tok.m_next;
                if ('?' == ch) {                // PI
                    TXPI p = parsePI(tok, false);
                    if (null != p) {
                        par.addElement(p);
                        applyPIHandlers(p);
                    }

                } else if ('!' == ch) {
                    tok.getChar();              // '!'
                    ch = tok.m_next;
                    if ('[' == ch) {
                        tok.getChar();          // '['
                        ignoreSectContents(par, tok);
                    } else if ('-' == ch) {
                        tok.getChar();          // '-'
                        if ('-' == tok.m_next) { // comment
                            tok.getChar();      // '-'
                            tok.getComment();
                        } else {
                            tok.getChar();
                            format0(tok, "E_COM0");
                        }
                    } else if (XMLChar.isChar((char)ch)) {
                        tok.getChar();
                    } else {
                        format1(tok, "E_INVCHAR0", Integer.toString(ch, 16));
                    }

                } else {
                    format0(tok, "E_COND3");
                }

            } else if (XMLChar.isChar((char)ch)) {
                tok.getChar();

            } else {
                tok.getChar();
                format1(tok, "E_INVCHAR0", Integer.toString(ch, 16));
            }
        }
        if (0 > ch)  throw new EOFException("com.ibm.xml.parser.Parser#ignoreSectContents(): "+getString("E_EOF"));
        tok.getChar();                          // ']'
        if (']' != tok.m_next) {
            format0(tok, "E_COND1");
            return;
        }
        tok.getChar();                          // ']'
        if ('>' != tok.m_next) {
            format0(tok, "E_COND1");
            return;
        }
        tok.getChar();                          // '>'
    }

    void parseNotation(Parent par, Token tok) throws Exception {
        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
        }
        tok.skipSpaces();

        String n = tok.getName();
        if (null == n) {
            format0(tok, "E_NOT0");
            tok.skipTagEnd();
            return;
        }
        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
        }
        tok.skipSpaces();

        ExternalID eid = tok.parseExternalID(true);
        if (null != eid)
            par.addElement(this.elementFactory.createNotation(n, eid));
        tok.skipSpaces();
        if ('>' == tok.m_next)  tok.getChar();
    }
    
    static String makeReferencePath(Vector v, String refn) {
        StringBuffer sb = new StringBuffer(256);
        sb.append("(top-level)");
        synchronized (v) {
            for (int i = 0;  i < v.size();  i ++) {
                sb.append("-&");
                sb.append((String)v.elementAt(i));
                sb.append((char)';');
            }
        }
        sb.append("-&");
        sb.append(refn);
        sb.append((char)';');
        return sb.toString();
    }

    static String intstring(int ch) {
        return new Character((char)ch).toString();
    }
    
    
    boolean isWarningNoDoctypeDecl() {
        return this.isWarningNoDoctypeDecl;
    }
                                                // This method is called in constructors.
    private void init() {
                                /* Error messages:
                                 *   In Japanese environment, load parser.r.Message_ja
                                 *   Otherwise, load parser.r.Message (English messages)
                                 */
        this.resourceBundle = ListResourceBundle.getBundle("com.ibm.xml.parser.r.Message");
        this.entityPool = new EntityPool();
    }

    private void init2() {
        if (null == this.elementFactory)
            this.elementFactory = new DefaultElementFactory();
        this.entityPool.add(this.elementFactory.createEntity("lt", "&#60;", false));
        this.entityPool.add(this.elementFactory.createEntity("gt", "&#62;", false));
        this.entityPool.add(this.elementFactory.createEntity("amp", "&#38;", false));
        this.entityPool.add(this.elementFactory.createEntity("apos", "&#39;", false));
        this.entityPool.add(this.elementFactory.createEntity("quot", "&#34;", false));
    }
    
    private void applyPreRootHandlers(String rootn) {
        if (null == this.preRootHandlers)  return;
        for (int i = 0;  i < this.preRootHandlers.size();  i ++) {
            ((PreRootHandler)this.preRootHandlers.elementAt(i)).handlePreRoot(this.document, rootn);
        }
    }
    
    private void applyPIHandlers(TXPI p) {
        if (null == this.piHandlers)  return;
        String target = p.getName();
        String data = p.getData();
        synchronized (this.piHandlers) {
            for (int i = 0;  i < this.piHandlers.size();  i ++) {
                PIHandler ph = (PIHandler)this.piHandlers.elementAt(i);
                ph.handlePI(target, data);
            }
        }
    }
    
    private TXElement applyHandlers(TXElement e) {
        if (null == this.elementHandlerHash)  return e;
        String u = e.getTagName();
        Vector v = (Vector)this.elementHandlerHash.get(u);
        if (null != v) {
            for (int i = 0;  i < v.size();  i ++) {
                e = ((ElementHandler)v.elementAt(i)).handleElement(e);
                if (null == e)  return null;
            }
        }
        v = (Vector)this.elementHandlerHash.get(s_enameall);
        if (null != v) {
            for (int i = 0;  i < v.size();  i ++) {
                e = ((ElementHandler)v.elementAt(i)).handleElement(e);
                if (null == e)  return null;
            }
        }
        return e;
    }
    
    private TXElement applyNoRequiredAttributeHandlers(TXElement el, String attname) {
        if (null == this.requiredAttributeHandlers)  return el;
        for (int i = 0;  i < this.requiredAttributeHandlers.size();  i ++) {
            NoRequiredAttributeHandler nrah
                = (NoRequiredAttributeHandler)this.requiredAttributeHandlers.elementAt(i);
            el = nrah.handleNoRequiredAttribute(el, attname);
            if (null != el.getAttribute(attname))
                break;
        }
        return el;
    }
    
    private void clean() {
        this.resourceBundle = null;
        this.errorListener = null;
        this.streamProducer = null;
        this.entityPool = null;
        this.elementFactory = null;
        this.tagHandler = null;
        this.document = null;
        this.dtd = null;
        this.generalReferences = null;
        this.parameterEntityReferences = null;
        this.preRootHandlers = null;
        this.piHandlers = null;
        this.elementHandlerHash = null;
        this.requiredAttributeHandlers = null;
        this.contentModelErrors = null;
        this.idRefPool = null;
        this.notationNames = null;
        this.attributeErrors = null;
        this.referenceHandler = null;
    }

    private void addIdrefPool(Token tok, String id) {
        if (null == this.idRefPool)  this.idRefPool = new Vector();
        this.idRefPool.addElement(new ParsePoint(tok, id));
    }
    
    private void checkIdref() {
        if (this.document.isApplySyntax()) {
            DTD d = this.document.getDTD();
            if (null != d && null != this.idRefPool) {
                for (int i = 0;  i < this.idRefPool.size();  i ++) {
                    ParsePoint pp = (ParsePoint)this.idRefPool.elementAt(i);
                    String idref = (String)pp.m_target;
                    if (null == d.checkID(idref))
                        format1(pp, "E_IDREF0", idref);
                }
            }
        }
        this.idRefPool = null;
    }

    private void addNotationName(Token tok, String notn) {
        if (null == this.notationNames)  this.notationNames = new Vector();
        this.notationNames.addElement(new ParsePoint(tok, notn));
    }
    
    private void checkNotationNames() {
        DTD d = this.document.getDTD();
        if (null != this.notationNames) {
            for (int i = 0;  i < this.notationNames.size();  i ++) {
                ParsePoint pp = (ParsePoint)this.notationNames.elementAt(i);
                String nn = (String)pp.m_target;
                if (null == d || null == d.getNotation(nn))
                    format1(pp, "E_ENUM5", nn);
            }
        }
        this.notationNames = null;
    }

    private void checkSyntax(Token tok, TXElement tag, boolean isempty) {
        DTD d = this.document.getDTD();
        if (null == d)   return;
        String name = tag.getTagName();
        TXAttribute[] aa = tag.getAttributeArray();
                                                // Check if there is #REQUIRED attribute.
                                                // Add default attribute
        for (Enumeration en = d.getAttributeDeclarations(name);
             en.hasMoreElements(); ) {
            AttDef ad = (AttDef)en.nextElement();
            String aname = ad.getName();
            switch (ad.getDefaultType()) {
              case AttDef.REQUIRED:
                if (null == tag.getAttributeNode(aname))
                    tag = applyNoRequiredAttributeHandlers(tag, aname);
                TXAttribute a = (TXAttribute)tag.getAttributeNode(aname);
                if (null == a)
                    format1(tok, "E_TAGc", aname);
                else
                    a.setSpecified(false);
                break;
              case AttDef.IMPLIED:
                break;
              case AttDef.FIXED:
                String fixs = tag.getAttribute(aname);
                if (null == fixs) {
                    TXAttribute attr = this.elementFactory.createAttribute(aname, ad.getDefaultStringValue());
                    if (this.isProcessNamespace) {
                        String ret = this.document.setNamespaceParameters(attr);
                        if (null != ret)  format1(tok, ret, aname);
                    }
                    attr.setSpecified(false);
                    tag.setAttributeNode(attr);
                } else if (!TXAttribute.normalize(ad.getDeclaredType(), fixs).equals(ad.getDefaultStringValue()))
                    format3(tok, "E_TAGd", aname, ad.getDefaultStringValue(), fixs);
                break;
              case AttDef.NOFIXED:
                if (null == tag.getAttributeNode(aname)) {
                    TXAttribute attr = this.elementFactory.createAttribute(aname, ad.getDefaultStringValue());
                    attr.setSpecified(false);
                    tag.setAttributeNode(attr);
                }
                break;
            }
        }
                                                // Check attribute type
        for (int i = 0;  i < aa.length;  i ++) {
            TXAttribute a = aa[i];
            AttDef ad = d.getAttributeDeclaration(name, a.getName());
            if (null == ad) {
                if (null == this.attributeErrors)  this.attributeErrors = new Hashtable();
                String key = name+" "+a.getName();
                if (!this.attributeErrors.containsKey(key)) {
                    this.attributeErrors.put(key, key);
                    format2(tok, "E_TAG6", a.getName(), name);
                }
                continue;
            }
            int attt = ad.getDeclaredType();
            String[] as = null;
            if (AttributeDefinition.CDATA == attt) {
                as = new String[1];
                as[0] = a.getValue();
                a.setType(AttributeDefinition.CDATA, as);
            } else {

                String v = a.getValue().trim();
                switch (attt) {
                  case AttributeDefinition.NAME_TOKEN_GROUP:
                    if (!Util.checkNmtoken(v)) {
                        format1(tok, "E_TAG7", v);
                    } else if (ad.contains(v)) {
                        as = new String[1];
                        as[0] = v;
                    } else {
                        format1(tok, "E_TAG9", v);
                    }
                    a.setType(AttributeDefinition.NAME_TOKEN_GROUP, as);
                    break;
                  case AttributeDefinition.NOTATION:
                    if (!Util.checkName(v)) {
                        format1(tok, "E_TAG7", v);
                    } else if (null != d.getNotation(v)) {
                        as = new String[1];
                        as[0] = v;
                    } else {
                        format1(tok, "E_TAGb", v);
                    }
                    a.setType(AttributeDefinition.NOTATION, as);
                    break;
                  case AttributeDefinition.ID:
                    if (!Util.checkName(v)) {
                        format1(tok, "E_TAG7", v);
                    } else {
                        if (!d.registID(tag, v))
                            format1(tok, "E_TAG8", v);
                        else {
                            as = new String[1];
                            as[0] = v;
                        }
                    }
                    a.setType(AttributeDefinition.ID, as);
                    break;
                  case AttributeDefinition.IDREF:
                    if (!Util.checkName(v)) {
                        format1(tok, "E_TAG7", v);
                    } else {
                        as = new String[1];
                        as[0] = v;
                        addIdrefPool(tok, v);
                    }
                    a.setType(AttributeDefinition.IDREF, as);
                    break;
                  case AttributeDefinition.IDREFS:
                    as = tok.getNames(v);
                    for (int ai = 0;  ai < as.length;  ai ++)
                        addIdrefPool(tok, as[ai]);
                    a.setType(AttributeDefinition.IDREFS, as);
                    break;
                  case AttributeDefinition.ENTITY:
                    v = a.getValue().trim();
                    if (!Util.checkName(v)) {
                        format1(tok, "E_TAG7", v);
                    } else {
                        Entity ev = this.entityPool.refer(v);
                        if (null == ev || !ev.isExternal() || !ev.isNDATA()) {
                            format1(tok, "E_TAGa", v);
                        } else {
                            as = new String[1];
                            as[0] = v;
                        }
                        a.setType(AttributeDefinition.ENTITY, as);
                    }
                    break;
                  case AttributeDefinition.ENTITIES:
                    v = a.getValue().trim();
                    as = tok.getNames(v);
                    for (int ai = 0;  ai < as.length;  ai ++) {
                        Entity ev = this.entityPool.refer(as[ai]);
                        if (null == ev || !ev.isExternal() || !ev.isNDATA())
                            format1(tok, "E_TAGa", v);
                    }
                    a.setType(AttributeDefinition.ENTITIES, as);
                    break;
                  case AttributeDefinition.NMTOKEN:
                    if (!Util.checkNmtoken(v)) {
                        format1(tok, "E_TAG7", v);
                    } else {
                        as = new String[1];
                        as[0] = v;
                    }
                    a.setType(AttributeDefinition.NMTOKEN, as);
                    break;
                  case AttributeDefinition.NMTOKENS:
                    as = tok.getNmtokens(v);
                    a.setType(AttributeDefinition.NMTOKENS, as);
                }
            }
        }

        if (isempty)
            checkContentModel(tok, tag);
    }

    private void checkContentModel(Token tok, TXElement tag) {
        DTD d = this.document.getDTD();
        if (null != d) {
            String name = tag.getTagName();
            ContentModel cs = d.getContentModel(name);
            if (null == cs) {
                if (null == this.contentModelErrors)  this.contentModelErrors = new Hashtable();
                String key = name;
                if (!this.contentModelErrors.containsKey(key)) {
                    this.contentModelErrors.put(key, name);
                    format1(tok, "E_CONT0", name);
                }
            } else {
                if (!cs.check(tag))
                    format2(tok, "E_CONT1", name, cs.toString());
            }
        }
    }
    
    private boolean isApplySyntax() {
        return this.document.isApplySyntax();
    }

    private boolean parseSingleMarkupDecl(DTD par, Token tok, boolean allowencodingpi) throws Exception {
        int ch = tok.m_next;
        if (0 > ch || ']' == ch)  return false;

        // markupdecl
        //  ::= (elementdecl | AttlistDecl | EntityDecl | NotationDecl
        //       | PI | S | Comment | InternalPERef)*
        // <!ELEMENT <!ATTLIST <!ENTITY <!NOTATION <?Name S <!-- %Name; <![ 
            
        if (XMLChar.isSpace((char)ch)) {
            tok.getWhitespaces();
            par.addElement(this.elementFactory.createText(tok.m_tstrbuf, 0, tok.m_tbufpos, true));

        } else if ('<' == ch) {
            tok.getChar();                      // '<'
            ch = tok.m_next;
            if ('!' == ch) {                    // ELEMENT | ATTLIST | ENTITY | NOTATION | [ | --
                tok.getChar();                  // '!'
                ch = tok.m_next;
                if ('[' == ch) {                // check '<![' %'INCLUDE' | '<![' %'IGNORE'
                    if (!par.isParsingExternal())
                        format0(tok, "E_DTD4");
                    int prevst = tok.setState(Token.ST_TAG);
                    parseConditionalSect(par, tok);
                    tok.setState(prevst);

                } else if ('-' == ch) {         // check '<!--'
                    tok.getChar();              // 1st '-'
                    if ('-' == tok.m_next) {
                        tok.getChar();          // 2nd '-'
                        if (this.isKeepComment)
                            par.addElement(this.elementFactory.createComment(tok.getComment()));
                        else
                            tok.getComment();
                    } else {
                        format0(tok, "E_COM0");
                        tok.skipTagEnd();
                    }

                } else {
                    int prevst = tok.setState(Token.ST_TAG);
                    String name = tok.getName();
                    if (null == name) {
                        format0(tok, "E_DTD3");
                        tok.skipTagEnd();
                    } else if ("ELEMENT".equals(name)) {
                        parseElementDecl(par, tok);
                    } else if ("ATTLIST".equals(name)) {
                        parseAttlist(par, tok);
                    } else if ("ENTITY".equals(name)) {
                        parseEntity(par, tok);
                    } else if ("NOTATION".equals(name)) {
                        parseNotation(par, tok);
                    } else {
                        format1(tok, "E_DTD2", name);
                        tok.skipTagEnd();
                    }
                    tok.setState(prevst);
                }
            } else if ('?' == ch) {             // PI
                TXPI p = parsePI(tok, false, allowencodingpi);
                if (null != p) {
                    par.addElement(p);
                    applyPIHandlers(p);
                }
                        
            } else {
                format0(tok, "E_DTD1");
                tok.nextTagStart();
            }
                    
        } else if ('%' == ch) {                 // PEReference
                                                // This PE must include markup declarations.
            tok.getChar();                      // '%'
            String pename = tok.getName();
            if (null == pename) {
                format0(tok, "E_PEREF0");
            } else {
                if (tok.m_next != ';')
                    format1(tok, "E_PEREF1", pename);
                else
                    tok.getChar();
                if (!pushPE(pename)) {
                    format2(tok, "E_PEREF5", pename,
                            makeReferencePath(this.parameterEntityReferences, pename));
                } else {
                    Entity per = this.entityPool.referPara(pename);
                    if (null == per) {
                        format1(tok, "E_PEREF2", pename);
                    } else {
                        Token toks = per.createToken(this, tok);
                        boolean textdecl = per.isExternal();
                        while (parseSingleMarkupDecl(par, toks, textdecl))
                            textdecl = false;

                        if (']' == toks.m_next)
                            format2(toks, "E_DTD0", intstring(toks.m_next), Integer.toHexString(toks.m_next));
                        toks.close();
                    }
                    popPE();
                }
            }

        } else {
            tok.getChar();
            format2(tok, "E_DTD0", intstring(ch), Integer.toHexString(ch));
            while (0 <= (ch = tok.m_next) && '<' != ch && ']' != ch)
                tok.getChar();
        }
        return true;
    }

    /*
     * elementdecl  ::= '<!ELEMENT' S Name S contentspec S? '>'
     *  contentspec ::= 'EMPTY' | 'ANY' | Mixed | elements
     */
    private void parseElementDecl(DTD par, Token tok) throws Exception {
        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
        }
        tok.skipSpaces();

        String ename = tok.getName();
        if (null == ename) {
            format0(tok, "E_ELEM0");
            tok.skipTagEnd();
            return;
        }

        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
        }
        int prevstate = tok.setState(Token.ST_CONTENTMODEL);
        tok.skipSpaces();

        ContentModel cs = null;
        if ('(' != tok.m_next) {
            String csn = tok.getName();
            if (null == csn) {
                format0(tok, "E_ELEM1");
                tok.skipTagEnd();
            } else if ("EMPTY".equals(csn)) {
                cs = this.elementFactory.createContentModel(ElementDecl.EMPTY);
            } else if ("ANY".equals(csn)) {
                cs = this.elementFactory.createContentModel(ElementDecl.ANY);
            } else {
                format0(tok, "E_ELEM1");
                tok.skipTagEnd();
            }
        } else {                                // regexp-like content model
            CMNode cmn = null;
            this.parenLevel = 0;
            cmn = parseContentModel0(tok);
            if (null != cmn) {
                if (!checkPCDATA(cmn)) {
                    format1(tok, "E_CS9", cmn.toString());
                }
                cs = this.elementFactory.createContentModel(cmn);
            } else
                tok.skipTagEnd();
            this.contentModelPCData = null;
        }
        if (cs != null) {
            if (par.isElementDeclared(ename)) {
                format1(tok, "E_ELEM3", ename);
            } else
                par.addElement(this.elementFactory.createElementDecl(ename, cs));
        }
        tok.setState(prevstate);
        tok.skipSpaces();                       // S?
        int ch = tok.m_next;
        if ('>' == ch)
            tok.getChar();
        else
            format0(tok, "E_ELEM2");
    }

    /*
     *  contentspec ::= 'EMPTY' | 'ANY' | Mixed | children
     *   children   ::= (choice | seq) ('?' | '*' | '+')?
     *    choice    ::= '(' S? cp (S? '|' S? %cp)* S? ')'
     *       cp     ::= (Name | choice | seq) ('?' | '*' | '+')?
     *    seq       ::= '(' S? cp (S? ',' S? %cp)* S? ')'
     *   Mixed      ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*' |
     *                  '(' S? #PCDATA') S? ')'
     */
    private boolean checkPCDATA(CMNode cn) {
        CMNode pcdata = this.contentModelPCData;
        this.contentModelPCData = null;
        if (null == cn)  return false;
        if (null == pcdata)  return true;
        if (cn instanceof CM2op)  return false;
        if (cn instanceof CMLeaf)  return cn == pcdata;
        CM1op c1 = (CM1op)cn;
        if (c1.getType() != '*')  return false;
        CMNode c = c1.getNode();
        while (!(c instanceof CMLeaf)) {
            if (c instanceof CM1op)  return false;
            CM2op c2 = (CM2op)c;
            if (c2.getType() != '|')  return false;
            if (!(c2.getRight() instanceof CMLeaf))  return false;
            c = c2.getLeft();
        }
        return pcdata == c;
    }

    private CMNode parseContentModel0(Token tok) throws Exception {
        CMNode cs = null;
        if ('(' == tok.m_next) {
            tok.getChar();                      // '('
            this.parenLevel ++;
            cs = parseContentModelChoiceSeq(tok, null, 0);
            if (')' == tok.m_next) {
                tok.getChar();
                this.parenLevel --;
            } else
                format0(tok, "E_CS3");
        } else if ('#' == tok.m_next) {
            tok.getChar();                      // '#'
            String pcdata = tok.getName();
            if (null == pcdata || !"PCDATA".equals(pcdata)) {
                format0(tok, "E_CS2");
                while (0 <= tok.m_next && ')' != tok.m_next)
                    tok.getChar();
                if (')' == tok.m_next) {
                    tok.getChar();
                    this.parenLevel --;
                }
                return null;
            }
            if (this.parenLevel != 1) {
                format0(tok, "E_CSa");
            }
            cs = new CMLeaf("#PCDATA");
            this.contentModelPCData = cs;
        } else {
            String csn = tok.getName();
            if (null == csn) {
                format0(tok, "E_CS5");
                return cs;
            }
            cs = new CMLeaf(csn);
        }
        int ch = tok.m_next;
        if ('?' == ch || '*' == ch || '+' == ch) {
            tok.getChar();                      // ?*+
            if (null != cs) cs = new CM1op(ch, cs);
        }
        return cs;
    }

    /*
      ChoiceSeq := node (op node)*
     */
    private CMNode parseContentModelChoiceSeq(Token tok, CMNode prev, int op) throws Exception {
        while (true) {
            tok.skipSpaces();               // S?
            {
                CMNode c = parseContentModel0(tok);
                if (null == c)  return null;
                if (null == prev) {
                    prev = c;
                } else
                    prev = new CM2op(op, prev, c);
            }

            tok.skipSpaces();
            int ch = tok.m_next;
            if (')' == ch) {
                break;
            } else if ('|' == ch || ',' == ch) {
                tok.getChar();                  // '|' or ','
                if (0 != op && op != ch)
                    format2(tok, "E_CS6", intstring(ch), intstring(op));
                op = ch;
            } else if (0 > ch) {
                break;
            } else {
                format0(tok, "E_CS1");
                while (0 <= tok.m_next && ')' != tok.m_next)
                    tok.getChar();
            }
        }
        return prev;
    }

    /*
     * AttlistDecl ::= '<!ATTLIST' S Name S? AttDef+ S? '>'
     * AttDef ::= S Name S AttType S Default
     */
    private void parseAttlist(DTD par, Token tok) throws Exception {
        /*
        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
            }*/
        tok.skipSpaces();

        String ename = tok.getName();
        if (null == ename) {
            format0(tok, "E_ATTL0");
            tok.skipTagEnd();
            return;
        }

        tok.skipSpaces();

        Attlist al = this.elementFactory.createAttlist(ename);
        while (parseAttDefs(par, tok, al));
        par.addElement(al);
        if (0 > tok.m_next)  throw new EOFException("com.ibm.xml.parser.Parser#parseAttlist(): "+getString("E_EOF"));
        tok.getChar();                          // '>'
    }
    
    private boolean parseAttDefs(DTD dtd, Token tok, Attlist al) throws Exception {
        int ch = tok.m_next;
        if (0 > ch || '>' == ch)  return false;

        AttDef ad = parseAttDef(dtd, tok, al);
        if (null != ad) {
            if (dtd.isAttributeDeclared(al.getName(), ad.getName())
                ||null != al.getAttDef(ad.getName())) {
                format1(tok, "W_ATTL2", ad.getName());
            } else {
                if ("xml:space".equals(ad.getName())) {
                    if (AttributeDefinition.NAME_TOKEN_GROUP != ad.getDeclaredType()
                        || 2 != ad.size()) {
                        format0(tok, "W_ATTL1");
                    } else {
                        String s1 = ad.elementAt(0);
                        String s2 = ad.elementAt(1);
                        if (!("default".equals(s1) && "preserve".equals(s2))
                            && !("preserve".equals(s1) && "default".equals(s2))) {
                            format0(tok, "W_ATTL1");
                        }
                    }
                }
                if (AttributeDefinition.ID == ad.getDeclaredType()) {
                    Enumeration en = dtd.getAttributeDeclarations(al.getName());
                    while (en.hasMoreElements()) {
                        AttDef a = (AttDef)en.nextElement();
                        if (AttributeDefinition.ID == a.getDeclaredType()) {
                            format1(tok, "E_ATTL2", a.getName());
                            ad = null;
                            break;
                        }
                    }
                    en = al.elements();
                    while (en.hasMoreElements()) {
                        AttDef a = (AttDef)en.nextElement();
                        if (AttributeDefinition.ID == a.getDeclaredType()) {
                            format1(tok, "E_ATTL2", a.getName());
                            ad = null;
                            break;
                        }
                    }
                }
                if (null != ad)
                    al.addElement(ad);
            }
        }
        tok.skipSpaces();
        return true;
    }
    
    /*
     * AttDef ::= S %Name S %AttType S %Default
     */
    private AttDef parseAttDef(DTD dtd, Token tok, Attlist al) throws Exception {
        tok.skipSpaces();                       // S
        if ('>' == tok.m_next)  return null;
        String aname = tok.getName();           // %Name
        if (null == aname) {
            format0(tok, "E_ATTD0");
            tok.getChar();
            return null;
        }
                                                // S
        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return null;
        }
        tok.skipSpaces();

        AttDef ret = this.elementFactory.createAttDef(aname);

                                                // %AttType
        ret = parseAttType(dtd, tok, ret);
        if (null == ret)  return null;
        if (!XMLChar.isSpace((char)tok.m_next))
            format0(tok, "E_SPACE");
        tok.skipSpaces();

                                                // %DefaultDecl
        ret = parseAttDefaultDecl(tok, ret);
        return ret;
    }
    
    /*
     * AttType ::= 'CDATA' | 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' | 'ENTITIES'
     *           | 'NMTOKEN' | 'NMTOKENS' | NotationType | Enumeration
     * NotationType ::= %'NOTATION' S '(' S? %Ntoks (S? '|' S? %Ntoks)* S? ')'
     * Enumeration ::= '(' S? %Etoks (S? '|' S? %Etoks)* S? ')'
     *  Ntoks      ::= %Name (S? '|' S? %Name)*
     *  Etoks      ::= %Nmtoken (S? '|' S? %Nmtoken)*
     *   Nmtoken   ::= NameChar+
     */
    private AttDef parseAttType(DTD dtd, Token tok, AttDef ret) throws Exception {
        String typen = null;
        int ch = tok.m_next;

        if ('(' == ch) {                 // Enumeration
            ret.setDeclaredType(AttributeDefinition.NAME_TOKEN_GROUP);
            parseEnumeration(dtd, tok, ret, true);
        } else {
            typen = tok.getName();
        }

        if (AttributeDefinition.NAME_TOKEN_GROUP == ret.getDeclaredType()) {
        } else if (null == typen) {
            format0(tok, "E_ATTD2");
            return null;
        } else if ("CDATA".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.CDATA);
        } else if ("ID".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.ID);
        } else if ("IDREF".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.IDREF);
        } else if ("IDREFS".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.IDREFS);
        } else if ("ENTITY".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.ENTITY);
        } else if ("ENTITIES".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.ENTITIES);
        } else if ("NMTOKEN".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.NMTOKEN);
        } else if ("NMTOKENS".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.NMTOKENS);
        } else if ("NOTATION".equals(typen)) {
            ret.setDeclaredType(AttributeDefinition.NOTATION);
            if (!XMLChar.isSpace((char)tok.m_next)) {
                format0(tok, "E_SPACE");
            }
            tok.skipSpaces();
            parseEnumeration(dtd, tok, ret, false);
        } else {
            ret.setDeclaredType(AttDef.CDATA);
            format0(tok, "E_ATTD2");
        }
                                                // S
        return ret;
    }

    /*
     * DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | ((%'#FIXED' S)? %AttValue)
     */
    private AttDef parseAttDefaultDecl(Token tok, AttDef ret) throws Exception {
        int ch = tok.m_next;
        if ('#' == ch) {
            tok.getChar();                      // '#'
            String n = tok.getName();
            if (null == n) {
                format0(tok, "E_ATTD3");
                tok.getChar();
                return null;
            } else if ("REQUIRED".equals(n)) {
                ret.setDefaultType(AttDef.REQUIRED);
            } else if ("IMPLIED".equals(n)) {
                ret.setDefaultType(AttDef.IMPLIED);
            } else if ("FIXED".equals(n)) {
                ret.setDefaultType(AttDef.FIXED);
                if (!XMLChar.isSpace((char)tok.m_next)) {
                    format0(tok, "E_SPACE");
                }
                tok.skipSpaces();
                ret.setDefaultStringValue(tok.getAttValue());
            } else {
                format0(tok, "E_ATTD3");
                return null;
            }
        } else if ('"' == ch || '\'' == ch) {
            int prevstate = tok.setState(Token.ST_VALUE);
            ret.setDefaultStringValue(TXAttribute.normalize(ret.getDeclaredType(),
                                                            tok.getAttValue()));
            tok.setState(prevstate);

        } else {
            format0(tok, "E_ATTD4");
            return null;
        }
        return ret;
    }

    /*
     *  NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'
     *  Enumeration  ::= '(' S? Name (S? '|' S? Name)* S? ')'
     */
    private void parseEnumeration(DTD dtd, Token tok, AttDef ad, boolean isnmtok) throws Exception {
        int ch = tok.m_next;
        if ('(' != ch) {
            format0(tok, "E_ENUM0");
            return;
        }
        tok.getChar();                          // '('
        tok.skipSpaces();                       // S?
        while (true) {
            String n = isnmtok ? tok.getNmtoken() : tok.getName();
            if (null == n) {
                format0(tok, isnmtok ? "E_ENUM2" : "E_ENUM1");
            } else {
                if (!isnmtok)                 // NOTATION
                    addNotationName(tok, n);
                ad.addElement(n);
                    /*
                if (!ad.addElement(n)) {
                    format1(tok, "E_ENUM4", n);
                    }*/
            }
            tok.skipSpaces();
            ch = tok.m_next;
            if (')' == ch)
                break;
            else if ('|' == ch) {
                tok.getChar();
                tok.skipSpaces();
            } else {
                format0(tok, "E_ENUM3");
                break;
            }
        }
        if (')' == ch)  tok.getChar();
    }

    /*
     * EntityDecl ::= '<!ENTITY' S (Name | '%' S Name) S EntityDef S? '>'
     * EntityDef ::= (EntityValue) | (ExternalID (S 'NDATA' S Name)? )
     * EntityValue ::=  '"' ([^%&"] | PEReference | Reference)* '"'  
     *                | "'" ([^%&'] | PEReference | Reference)* "'"
     */
    private void parseEntity(DTD par, Token tok) throws Exception {
        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
        }
        tok.skipSpaces();

        boolean ispara = false;
        String name = null;
        if ('%' == tok.m_next) {
            tok.getChar();                      // '%'
            if (XMLChar.isSpace((char)tok.m_next)) {
                tok.skipSpaces();
                ispara = true;                  // A declaration of Parameter Entity
                name = tok.getName();
            } else {
                //tok.expandPEReferenceWithoutGet();
                name = tok.getName();
            }
        } else
            name = tok.getName();
        if (null == name) {
            format0(tok, "E_ENTITY1");
            tok.skipTagEnd();
            return;
        }

        if (!XMLChar.isSpace((char)tok.m_next)) {
            format0(tok, "E_SPACE");
            tok.skipTagEnd();
            return;
        }
        tok.skipSpaces();

                                                // EntityDef
        int start = tok.m_next;
        if ('"' == start || '\'' == start) {    // EntityValue
            StringBuffer sb = new StringBuffer(256);
            int lv = tok.getLevel();
            int prevstate = tok.setState(Token.ST_VALUE);
            tok.getChar();
            while (true) {
                int n = tok.m_next;
                if ((lv == tok.getLevel() && n == start) || 0 > n)  break;
                tok.getChar();
                int ch = n;
                if (!XMLChar.isChar(ch)) {
                    format1(tok, "E_ENTITY3", Integer.toString(ch, 16));
                    sb.append((char)'?');
                } else if ('&' == ch) {
                    if ('#' == tok.m_next)      // character referece is included.
                        sb.append(tok.m_tstrbuf, 0, tok.m_tbufpos);
                    else {                      // generic reference isn't included.
                        sb.append((char)'&');
                        String nm = tok.getName();
                        if (null == nm) {
                            format1(tok, "E_REFER0", intstring(tok.m_next));
                        } else
                            sb.append(nm);
                        if (';' != tok.m_next) {
                            format0(tok, "E_REFER1");
                        } else {
                            tok.getChar();
                            sb.append((char)';');
                        }
                    }
                } else if ('%' == ch) {
                    format0(tok, "E_ENTITYa");
                } else {
                    sb.append((char)ch);
                }
            }
            tok.setState(prevstate);
            if (0 > tok.m_next)  return;
            tok.getChar();                      // start
            tok.skipSpaces();
            if ('>' != tok.m_next) {
                format0(tok, "E_ENTITY4");
            } else
                tok.getChar();                  // '>'
            String cont = sb.toString();
            Entity ent = this.elementFactory.createEntity(name, cont, ispara);
            par.addElement(ent);
            if (!this.entityPool.add(ent) && isWarningRedefinedEntity())
                format1(tok, "W_ENTITY6", name);

        } else {                                // ExternalID (S %'NDATA' S %Name)?
            ExternalID eid = tok.parseExternalID();
            if (null == eid)  return;
            boolean sp = false;
            if (XMLChar.isSpace((char)tok.m_next)) {
                tok.skipSpaces();
                sp = true;
            }
                                                // '%' or 'NDATA' or '>'
            String ndata = null;
            String ntype = null;
            int ch = tok.m_next;
            if ('>' != ch && ispara) {
                format0(tok, "E_ENTITY4");
                tok.skipTagEnd();

            } else if ('>' != ch) {
                if (!sp)
                    format0(tok, "E_SPACE");
                ndata = tok.getName();
                if (null == ndata || !"NDATA".equals(ndata)) {
                    format0(tok, "E_ENTITY5");
                    tok.skipTagEnd();
                    ndata = null;
                } else {                        // 'NDATA' exists
                    if (!XMLChar.isSpace((char)tok.m_next)) {
                        format0(tok, "E_SPACE");
                        tok.skipTagEnd();
                    } else
                        tok.skipSpaces();
                    ntype = tok.getName();
                    if (null == ntype) {
                        format0(tok, "E_ENTITY5");
                        ntype = null;
                        tok.skipTagEnd();
                    } else {
                        tok.skipSpaces();
                        if ('>' == tok.m_next) {
                            tok.getChar();      // '>'
                        } else {
                            format0(tok, "E_ENTITY4");
                            tok.skipTagEnd();
                        }
                        if (null == par.getNotation(ntype))
                            format1(tok, "E_ENTITY8", ntype);
                    }
                }
            } else
                tok.getChar();                  // '>'

            Entity ent = this.elementFactory.createEntity(name, eid, ispara, ntype);
            par.addElement(ent);
            if (!this.entityPool.add(ent))
                format1(tok, "W_ENTITY6", name);
        }
    }
    
    private void startGeneralReference(String entityname) {
        if (null != this.referenceHandler)
            this.referenceHandler.startReference(entityname);
    }
    
    private void endGeneralReference(String entityname) {
        if (null != this.referenceHandler)
            this.referenceHandler.endReference(entityname);
    }

}
