//*****************************************************************************
/*
** FILE:   xe_DocumentParser.java
**
** (c) 1997 Steve Withall.
**
** HISTORY:
**    06Oct97  stevew  Created.
*/
package xe;

import xg.xg_Document;
import xg.xg_Node;

import eh.eh_Debug;

import java.io.IOException;

//*****************************************************************************
/** Parser for a whole XML document (or a stand-alone snippet).
 *
 *  This is intended to be the top-level parser, applied to whatever body of
 *  XML source is to be processed.
 */
public class xe_DocumentParser extends xe_Parser
{
    /** The XML document created by the most recent parse (or in the process of
     *  being created by the current parse). */
    xg_Document  CurrentDocument = null;

    /** The key parts of a document must appear in the correct order. This
     *  value reflects which of them has already been parsed. It has one of
     *  the constant values below. */
    private int  DocParseState = DOC_STATE_START;

    // Constants used to keep track of where in the document we have got to.
    /** We are at the start of the document. */
    public static final int  DOC_STATE_START          = 0;

    /** We have parsed miscellaneous content (a comment or PI). This is used
     *  purely to detect whether any of this innocuous content has appeared
     *  before an XML declaration - which is a heinous crime and cannot be
     *  tolerated. */
    public static final int  DOC_STATE_MISC           = 1;

    /** We are parsing or have just parsed the XML declaration. */
    public static final int  DOC_STATE_XML_DECL       = 2;

    /** We are parsing or have just parsed namespace declarations. */
    public static final int  DOC_STATE_NAMESPACE_DECL = 3;

    /** We are parsing or have just parsed the DTD. */
    public static final int  DOC_STATE_DTD            = 4;

    /** We are parsing or have just parsed the main body of the document. */
    public static final int  DOC_STATE_ELEMENT        = 5;

    //*****************************************************************************
    /** Parse an XML document, creating a new xg_Document for the purpose.
     *
     *  @return  The parse tree of the parsed source
     */
    public xg_Node parse() throws IOException
    {
        CurrentDocument = new xg_Document();
        return parse(CurrentDocument);
    }

    //*****************************************************************************
    /** Main document parse routine. Look for a sequence of entities, creating a
     *  parser object of the appropriate type to parse each one. Continue until the
     *  end of the source, or an error occurs. An xg_Document is returned, which
     *  contains the full parse tree of the parsed source. Documents roughly have
     *  the form:
     *
     *    [<?xml ... ?>]    [<!-- ... --> | <? ... ?>]*
     *    [<!DOCTYPE ... >] [<!-- ... --> | <? ... ?>]*
     *    <Name ... >       [<!-- ... --> | <? ... ?>]*
     *
     *  @param   IODocument  The document into which the parse results are to be
     *                        placed
     *  @return  The parse tree of the parsed source (= InputDocument)
     */
    public xg_Node parse(xg_Document  IODocument) throws IOException
    {
        eh_Debug.add(5, "xe_DocumentParser.parse: Start parsing document");
        DocParseState = DOC_STATE_START;

        // Clear all previous contents from the resulting document object.
        CurrentDocument = IODocument;
        CurrentDocument.reset();
        TheParseManager.fireStartNodeEvent(CurrentDocument, 0);

        xe_Token  CurrentToken = null;

        // Continue parsing till we reach the end of the source.
        boolean   FinishedParsingFlag = false;
        while (!FinishedParsingFlag)
        {
            // Start by expecting an entity of some kind.
            CurrentToken = TheParseManager.parseNextToken(true);
            eh_Debug.add(7, "xe_DocumentParser.parse: Parsed token (" + CurrentToken + ")");

            switch (CurrentToken.getType() )
            {
                case xe_TokenType.PI_START:
                case xe_TokenType.COMMENT_START:
                    if (DocParseState <= DOC_STATE_START)
                        DocParseState = DOC_STATE_MISC;
                    parseNextEntity(CurrentToken, CurrentDocument);
                    break;

                case xe_TokenType.XML_DECL_START:
                    if (DocParseState > DOC_STATE_START)
                        TheParseManager.throwParseException("XML declaration must appear at the start of the document");
                    DocParseState = DOC_STATE_XML_DECL;
                    parseNextEntity(CurrentToken, CurrentDocument);
                    break;

                case xe_TokenType.NAMESPACE_DECL_START:
                    if (DocParseState > DOC_STATE_NAMESPACE_DECL)
                        TheParseManager.throwParseException("Namespace declaration is too late in document");
                    DocParseState = DOC_STATE_NAMESPACE_DECL;
                    parseNextEntity(CurrentToken, CurrentDocument);
                    break;

                case xe_TokenType.DOCTYPE_START:
                    if (DocParseState >= DOC_STATE_DTD)
                        TheParseManager.throwParseException("<!DOCTYPE illegal here");
                    DocParseState = DOC_STATE_DTD;
                    parseNextEntity(CurrentToken, CurrentDocument);
                    break;

                case xe_TokenType.ELEMENT_START:
                    if (DocParseState >= DOC_STATE_ELEMENT)
                        TheParseManager.throwParseException("Document contains more than one root element");
                    DocParseState = DOC_STATE_ELEMENT;
                    parseNextEntity(CurrentToken, CurrentDocument);
                    break;

                case xe_TokenType.END_OF_SOURCE:
                    if (DocParseState != DOC_STATE_ELEMENT)
                        TheParseManager.throwParseException("Document does not contain an element");
                    //TBD We could store the trailing whitespace in a separate entity.
                    FinishedParsingFlag = true;
                    break;

                default:
                    TheParseManager.throwParseException("Token illegal here (" + CurrentToken + ") - expecting start of entity");
            }
        }

        eh_Debug.add(5, "xe_DocumentParser.parse: Have successfully completed parsing document");
        TheParseManager.fireEndNodeEvent(CurrentDocument);
        return CurrentDocument;
    }

    //*****************************************************************************
    /** Get the last document parsed.
     *
     *  @return  The last document parsed
     */
    public xg_Document getCurrentDocument()
    {
        return CurrentDocument;
    }
}

//*****************************************************************************
