//*****************************************************************************
/*
** FILE:   xe_DtdParser.java
**
** (c) 1997 Steve Withall.
**
** HISTORY:
**    02Nov97  stevew  Created.
*/
package xe;

import xg.xg_Dtd;
import xg.xg_ExternalID;
import xg.xg_Node;

import eh.eh_Debug;

import java.io.IOException;

//*****************************************************************************
/**
*  Class xe_DtdParser - parser for the DTD of an XML document, assuming
*  the beginning ('<!DOCTYPE') has already been parsed. DTDs have the roughly
*  following form:
*
*    <!DOCTYPE Name [[SYSTEM "Url" | PUBLIC "Name" "Url"] [NDATA Name]]
*                   [<!ATTLIST ... >
*                    <!ELEMENT ... >
*                    <!ENTITY ... >
*                    <!NOTATION ...>
*                    <? ... ?>
*                    <!-- ... -->
*                    %Name;]* >
*
*  Most of the individual components are parsed using their own parser classes,
*  but <!NOTATION declarations are parsed within the xe_DtdParser itself (partly
*  because of their simplicity, and partly because the <!DOCTYPE itself contains
*  the construct which comprises the <!NOTATION declaration).
*/
public class xe_DtdParser extends xe_Parser
{
    //*****************************************************************************
    /**
     * Parse the body of a Document Type Definition, putting the result in a
     * newly-created DTD object.
     *
     * @return  The parsed DTD
     */
    public xg_Node parse() throws IOException
    {
        eh_Debug.add(5, "xe_DtdParser.parse: Start parsing DTD");

        // Create new DTD object.
        xg_Dtd  NewDtd = (xg_Dtd)createNode("xg.xg_Dtd");
//        xg_Dtd  NewDtd = (xg_Dtd)TheParseManager.createEntity(EntityName,
//                                                              EntityType,
//                                                              "xg.xg_Dtd",
//                                                              ParentNode);
        // Parse the name of this DTD.
        xe_Token  CurrentToken = TheParseManager.parseNameToken(true);
        EntityName = CurrentToken.getStringValue();
        NewDtd.setName(EntityName);

        // Parse the external subset of the DTD (if there is one).
        CurrentToken = TheParseManager.parseNextToken(true);
        if (CurrentToken.getType() == xe_TokenType.NAME)
            // If we've got a name, it must be 'SYSTEM' or 'PUBLIC' - so we've got
            // an external DTD subset.
            CurrentToken = parseExternalSubset(NewDtd, CurrentToken);

        // Parse the internal subset of the DTD (if there is one).
        if (CurrentToken.getType() == xe_TokenType.OPEN_BRACKET_CHAR)
            CurrentToken = parseInternalSubset(NewDtd, CurrentToken);

        // Check that the DTD ends with an end tag ('>').
        if (CurrentToken.getType() != xe_TokenType.TAG_END_CHAR)
            TheParseManager.throwParseException("DOCTYPE declaration does not end with an end tag ('"
                                                          + xe_TokenType.TAG_END_CHAR
                                                          + "')");

        eh_Debug.add(7, "xe_DtdParser: Completed parsing DTD '" + EntityName + "'");
        return NewDtd;
    }

    //*****************************************************************************
    /**
     * Parse the external subset of the DTD, assuming its first token (SYSTEM or
     * PUBLIC has already been parsed). It should have the form:
     *
     *    SYSTEM "Url" | PUBLIC "Name" "Url" [NDATA Name]
     *
     * @param  InputDtd         The DTD currently being parsed
     * @param  InputStartToken  The first token of the external subset
     * @return                  The next token after the external subset
     */
    public xe_Token parseExternalSubset(xg_Dtd    InputDtd,
                                        xe_Token  InputStartToken) throws IOException
    {
        eh_Debug.add(5, "xe_DtdParser.parseExternalSubset:");

        // Parse the ExternalID (the SYSTEM or PUBLIC part).
        xg_ExternalID  NewExternalID = parseExternalID(InputStartToken);

        // Is there an NDATA entry?
        xe_Token CurrentToken = TheParseManager.parseNextToken(true);
        if (    CurrentToken.getType() == xe_TokenType.NAME
             && CurrentToken.getStringValue().compareTo(xe_TokenType.NDATA_STRING) == 0)
        {
            // Parse NDATA Name.
            CurrentToken = TheParseManager.parseNameToken(true);

            // Parse the next token.
            CurrentToken = TheParseManager.parseNextToken(true);
        }

        eh_Debug.add(5, "xe_DtdParser.parseExternalSubset: Return");
        return CurrentToken;
    }

    //*****************************************************************************
    /**
     * Parse the internal subset of the DTD. It should be a matching set of
     * brackets [ ... ] enclosing a list of declarations. The opening bracket is
     * assumed to have been parsed already (and passed in as InputStartToken).
     *
     * The types of declarations allowed are:
     *
     *   <!ATTLIST ... >
     *   <!ELEMENT ... >
     *   <!ENTITY ... >
     *   <!NOTATION ...>
     *   <? ... ?>
     *   <!-- ... -->
     *   %Name;
     *
     * @param  InputDtd         The DTD currently being parsed
     * @param  InputStartToken  The first token of the external subset
     * @return                  The next token after the internal subset
     */
    public xe_Token parseInternalSubset(xg_Dtd    InputDtd,
                                        xe_Token  InputStartToken) throws IOException
    {
        eh_Debug.add(5, "xe_DtdParser.parseInternalSubset:");

        xe_Token  CurrentToken    = TheParseManager.parseNextToken(true);
        boolean   MoreContentFlag = true;
        while (MoreContentFlag)
        {
            switch (CurrentToken.getType())
            {
                case xe_TokenType.CLOSE_BRACKET_CHAR:
                    // This is the end of the internal subset (']').
                    MoreContentFlag = false;
                    break;

                case xe_TokenType.ATTLIST_DECL_START:
                case xe_TokenType.ELEMENT_DECL_START:
                case xe_TokenType.ENTITY_DECL_START:
                case xe_TokenType.COMMENT_START:
                    // Each of these is handled by its own parser.
                    parseNextEntity(CurrentToken, InputDtd);
                    break;

                case xe_TokenType.NOTATION_DECL_START:
                    parseNotationDecl(InputDtd, CurrentToken);
                    break;

                case xe_TokenType.PE_REFERENCE:
                    // Have parsed a reference.
                    eh_Debug.add(7, "xe_DtdParser.parseInternalSubset: Create new xg_Reference '"
                                          + CurrentToken.getStringValue() + "'");
                    eh_Debug.add(2, "PE reference: TBD");
                    //TBD
//                    xg_Reference  NewReference = new xg_Reference(CurrentToken.getType(),
//                                                                  CurrentToken.getStringValue() );
//                    InputDtd.AddChild(NewReference);
                    break;

                case xe_TokenType.PI_START:
                    // This is the start of a PI section.
                    eh_Debug.add(7, "xe_DtdParser.parseInternalSubset: Create new PI section '"
                                          + CurrentToken.getStringValue() + "'");
                    eh_Debug.add(2, "PI section: TBD");
                    //TBD
                    break;

                default:
                    // Whatever it is, it isn't valid here.
                    TheParseManager.throwParseException("Illegal token ("
                                                          + CurrentToken
                                                          + ") in DTD internal subset");
            }

//            if (MoreContentFlag)
                CurrentToken = TheParseManager.parseNextToken(true);
        }

        eh_Debug.add(5, "xe_DtdParser.parseInternalSubset: Return");
        return CurrentToken;
    }

    //*****************************************************************************
    /**
     * Parse a notation declaration, assuming the opening "<!NOTATION" has already
     * been parsed. It should have the form:
     *
     *    <!NOTATION Name ExternalID >
     *    =========>
     *
     * @param  InputDtd         The DTD currently being parsed
     * @param  InputStartToken  The first token of the external subset
     */
    public void parseNotationDecl(xg_Dtd    InputDtd,
                                  xe_Token  InputStartToken) throws IOException
    {
        eh_Debug.add(5, "xe_DtdParser.parseNotationDecl:");

        //TBD Create notation object.

        // Parse Name.
        xe_Token  CurrentToken = TheParseManager.parseNameToken(true);

        // Parse ExternalID.
        CurrentToken = TheParseManager.parseNextToken(true);
        xg_ExternalID  NewExternalID = parseExternalID(CurrentToken);

        // Parse the tag end ('>').
        CurrentToken = TheParseManager.parseNextTokenExpected(xe_TokenType.TAG_END_CHAR, true);
    }

    //*****************************************************************************
    /**
     * Parse an external ID. It should have the form:
     *
     *    SYSTEM "Url"
     * or PUBLIC "Name" "Url"
     *
     * @param  InputStartToken  The first token of the external subset
     * @return                  The external ID which has just been parsed
     */
    public xg_ExternalID parseExternalID(xe_Token  InputStartToken) throws IOException
    {
        eh_Debug.add(5, "xe_DtdParser.parseExternalID:");

        // Create a parser for the external ID, and tell it about what it's parsing.
        xe_ExternalIDParser  ExternalIDParser = new xe_ExternalIDParser();
        ExternalIDParser.setParseManager(TheParseManager);
//        NewParser.setEntityType(EntityKeywordDefn.getType() );
        ExternalIDParser.setEntityName(InputStartToken.getStringValue() );
        ExternalIDParser.setEntityClassName("xg.xg_ExternalID");

        xg_ExternalID  NewExternalID = (xg_ExternalID)ExternalIDParser.parse();

        eh_Debug.add(5, "xe_DtdParser.parseExternalID: Return");
        return NewExternalID;
    }
}

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