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

import xg.xg_AttDecl;  //TEMP
import xg.xg_ElementDecl;
import xg.xg_Dtd;
import xg.xg_Node;

import eh.eh_Debug;

import java.io.IOException;

//*****************************************************************************
/**
*  Class xe_ElementDeclParser - parser for an element declaration within the
*  DTD of an XML document, assuming the beginning ('<!ELEMENT') has already
*  been parsed. ElementDecls have the roughly following form:
*
*     <!ELEMENT Name EMPTY >
*  or <!ELEMENT Name ANY >
*  or <!ELEMENT Name (#PCDATA | Name; | Name; | ...)* >  - mixed
*  or <!ELEMENT Name ( Entry | Entry | ...  )?|*|+  >    - elements
*/
public class xe_ElementDeclParser extends xe_Parser
{
    //*****************************************************************************
    /**
     * Parse the body of an element declaration.
     *
     * @return  The parsed ElementDecl
     */
    public xg_Node parse() throws IOException
    {
        eh_Debug.add(5, "xe_ElementDeclParser.Parse: Start parsing ElementDecl");

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

        xe_Token  FirstContentToken = TheParseManager.parseNextToken(true);
        switch (FirstContentToken.getType())
        {
            case xe_TokenType.OPEN_PAREN_CHAR:
                // This is either 'mixed' or 'elements'.
                CurrentToken = TheParseManager.parseNextToken(true);
                if (CurrentToken.getType() == xe_TokenType.HASH_CHAR)
                {
                    // Have '#': expect #PCDATA - indicating a 'mixed' declaration.
                    parseMixedContent(NewElementDecl, FirstContentToken, CurrentToken);
                    CurrentToken = TheParseManager.parseNextToken(true);
                }
                else
                    // Treat it as an 'elements' declaration.
                    CurrentToken = parseElementContent(NewElementDecl, FirstContentToken, CurrentToken);
                break;

            case xe_TokenType.NAME:
                // This must be "EMPTY" or "ANY".
                String TokenName = FirstContentToken.getStringValue();
                if (TokenName.equals(xg_ElementDecl.EMPTY_STRING) )
                    NewElementDecl.setType(xg_ElementDecl.EMPTY_TYPE);
                else if (TokenName.equals(xg_ElementDecl.ANY_STRING) )
                    NewElementDecl.setType(xg_ElementDecl.ANY_TYPE);
                else
                    TheParseManager.throwParseException("Only keywords "
                                                           + xg_ElementDecl.EMPTY_STRING
                                                           + " or "
                                                           + xg_ElementDecl.ANY_STRING
                                                           + " are allowed here");
                CurrentToken = TheParseManager.parseNextToken(true);
                break;

            default:
                TheParseManager.throwParseException("Illegal value within element declaration");
                break;
        }

        // The next token has already been parsed; make sure it is the tag end ('>').
        if (CurrentToken.getType() != xe_TokenType.TAG_END_CHAR)
            TheParseManager.throwParseException("Expected tag end ('"
                                                    + xe_TokenType.TAG_END_CHAR
                                                    + "') here");

        eh_Debug.add(7, "xe_ElementDeclParser: End of element declaration '" + EntityName + "'");
        return NewElementDecl;
    }

    //*****************************************************************************
    /**
     * Parse the declaration of a mixed element, assuming that its name, opening
     * parenthesis and the '#' of #PCDATA have already been parsed (and the '#' is
     * in the InputStartToken). It should have the rough form:
     *
     *    Name (#PCDATA )
     *    Name (#PCDATA | Name; | Name; | ...)*
     *    ======>
     *
     * @param  InputElementDecl     The element declaration currently being parsed
     * @param  InputOpenParenToken  The opening parenthesis token
     * @param  InputFirstToken      The first token after the open parenthesis
     */
    public xe_Token parseMixedContent(xg_ElementDecl  InputElementDecl,
                                      xe_Token        InputOpenParenToken,
                                      xe_Token        InputFirstToken) throws IOException
    {
        eh_Debug.add(5, "xe_ElementDeclParser.parseMixedContent:");
        InputElementDecl.setType(xg_ElementDecl.MIXED_TYPE);

        // Ensure there is no space between the '(' and the '#'.
        if (InputFirstToken.getPrecedingWhitespace() != null)
            TheParseManager.throwParseException("Whitespace is not allowed before '"
                                                   + xe_TokenType.HASH_CHAR + "' here");
        // Parse 'PCDATA'.
        xe_Token  CurrentToken = TheParseManager.parseNameTokenExpected(xe_TokenType.PCDATA_STRING,
                                                                        true);
        // Parse the list of allowed names (if there are any).
        CurrentToken = TheParseManager.parseNextToken(true);
        if (CurrentToken.getType() != xe_TokenType.CLOSE_PAREN_CHAR)
            parseMixedNameList(InputElementDecl, CurrentToken);
        // Note that we are taking no action if there are no names.

        eh_Debug.add(5, "xe_ElementDeclParser.parseMixedContent: Return");
        return CurrentToken;
    }

    //*****************************************************************************
    /**
     * Parse the declaration of a mixed element which has a list of names, assuming
     * that it has been parsed up to the '#PCDATA |'. This initial '|' is assumed
     * to have already been parsed (and be in the InputStartToken), although we
     * still need to check it actually is '|'. It should have the rough form:
     *
     *    Name (#PCDATA | Name; | Name; | ...)*
     *    ==============>
     *
     * @param  InputElementDecl     The ElementDecl currently being parsed
     * @param  InputOpeningOrToken  The opening or token
     */
    public void parseMixedNameList(xg_ElementDecl  InputElementDecl,
                                   xe_Token        InputOpeningOrToken) throws IOException
    {
        eh_Debug.add(5, "xe_ElementDeclParser.parseMixedNameList:");

        // Make sure the InputOpeningOrToken is actually '|'.
        if (InputOpeningOrToken.getType() != xe_TokenType.OR_CHAR)
            TheParseManager.throwParseException("Expect #PCDATA to be followed by '"
                                                   + xe_TokenType.OR_CHAR + "'");

        xe_Token  CurrentToken   = null;
        boolean   MoreValuesFlag = true;
        while (MoreValuesFlag)
        {
            CurrentToken = TheParseManager.parseNextTokenExpected(xe_TokenType.NAME, true);
//            if (InputElementDecl.isValueAllowed(CurrentToken.getStringValue() ) )
//                TheParseManager.throwParseException("Value '"
//                                                       + CurrentToken.getStringValue()
//                                                       + "' appears twice in list of names");

//            InputElementDecl.addAllowedValue(CurrentToken.GetStringValue() );

            CurrentToken = TheParseManager.parseNextToken(true);
            switch (CurrentToken.getType())
            {
                case xe_TokenType.OR_CHAR:
                    // Indicates that there is another value ('|').
                    break;

                case xe_TokenType.CLOSE_PAREN_CHAR:
                    // The end of the list (')').
                    MoreValuesFlag = false;
                    break;

                default:
                    // Whatever it is, it isn't valid here.
                    TheParseManager.throwParseException("Illegal token ("
                                                          + CurrentToken
                                                          + ") in list of allowed values");
            }
        }

        // Parse the '*' after the closing parenthesis.
        CurrentToken = TheParseManager.parseNextTokenExpected(xe_TokenType.STAR_CHAR, false);
        //TBD Store the whitespace.

        eh_Debug.add(5, "xe_ElementDeclParser.parseMixedNameList: Return");
        return;
    }

    //*****************************************************************************
    /**
     * Parse the declaration of an 'elements' element, assuming that its name,
     * opening parenthesis and the first token of the first Entry have already been
     * parsed. It should have the rough form:
     *
     *    Name ( Entry | Entry | ...  )?|*|+  - choice
     *    Name ( Entry , Entry , ...  )?|*|+  - seq
     *    ========>
     *
     * where Entry is either a 'choice' or 'seq'.
     *
     * @param  InputElementDecl     The ElementDecl currently being parsed
     * @param  InputOpenParenToken  The opening parenthesis token
     * @param  InputFirstToken      The first token after the open parenthesis
     */
    public xe_Token parseElementContent(xg_ElementDecl  InputElementDecl,
                                        xe_Token        InputOpenParenToken,
                                        xe_Token        InputFirstToken) throws IOException
    {
        eh_Debug.add(5, "xe_ElementDeclParser.parseElementContent:");
        InputElementDecl.setType(xg_ElementDecl.ELEMENTS_TYPE);

        // Parse the main list (ie. up to the top level closing parenthesis).
        parseElementsList(InputElementDecl, InputOpenParenToken, InputFirstToken);

        // Parse the occurrences character ('?', '*' or '+'), if there is one.
        xe_Token  CurrentToken = parseOccurrencesChar(InputElementDecl);

        eh_Debug.add(5, "xe_ElementDeclParser.parseElementContent: Return");
        return CurrentToken;
    }

    //*****************************************************************************
    /**
     * Parse an entry in the list of an 'elements' element, assuming that its
     * opening parenthesis and the token after it have already been parsed. It
     * should have the rough form:
     *
     *    ( Entry | Entry | ...  )  - choice
     * or ( Entry , Entry , ...  )  - seq
     *    ===>
     *
     * where Entry is either a Name, 'choice' or 'seq'.
     *
     * @param  InputElementDecl     The element declaration currently being parsed
     * @param  InputOpenParenToken  The opening parenthesis token
     * @param  InputFirstToken      The first token after the opening parenthesis
     */
    public xe_Token parseElementsList(xg_ElementDecl  InputElementDecl,
                                      xe_Token        InputOpenParenToken,
                                      xe_Token        InputFirstToken) throws IOException
    {
        eh_Debug.add(5, "xe_ElementDeclParser.parseElementsList:");
        boolean   MoreValuesFlag = true;
        int       ListType       = xe_TokenType.COMMA_CHAR;

        // Process the first entry, and return the token which follows it.
        xe_Token  CurrentToken = parseElementsEntry(InputElementDecl, InputFirstToken);

        // Check the value after the first entry - which must be either a separator
        // or a closing parenthesis.
        switch (CurrentToken.getType())
        {
            case xe_TokenType.OR_CHAR:
                // We have a choice ('|') list.
                ListType = xe_TokenType.OR_CHAR;
                break;

            case xe_TokenType.COMMA_CHAR:
                // We have a sequence (',') list.
                break;

            case xe_TokenType.CLOSE_PAREN_CHAR:
                // We have a sequence list with only one item.
                MoreValuesFlag = false;
                break;

            default:
                TheParseManager.throwParseException("Expect separator here in element content declaration");
                break;
        }

        // Loop through the rest of the list.
        while (MoreValuesFlag)
        {
            // Parse the first token of the next entry.
            CurrentToken = TheParseManager.parseNextToken(true);

            // Process this entry, and get the token which follows it.
            CurrentToken = parseElementsEntry(InputElementDecl, CurrentToken);

            switch (CurrentToken.getType())
            {
                case xe_TokenType.OR_CHAR:
                case xe_TokenType.COMMA_CHAR:
                    // Indicates that there is another value ('|' or ',').
                    // First check we have the same type of separator as before.
                    if (CurrentToken.getType() != ListType)
                        TheParseManager.throwParseException("Separator must be the same as that used before");
                    break;

                case xe_TokenType.CLOSE_PAREN_CHAR:
                    // The end of the list (')').
                    MoreValuesFlag = false;
                    break;

                default:
                    // Whatever it is, it isn't valid here.
                    TheParseManager.throwParseException("Expect separator here in element content declaration");
            }
        }

        eh_Debug.add(5, "xe_ElementDeclParser.parseElementsList: Return");
        return CurrentToken;
    }

    //*****************************************************************************
    /**
     * Parse an entry in the list of an 'elements' element, assuming that its first
     * token has already been parsed. It should have the rough form:
     *
     *    Name
     * or ( Entry | Entry | ...  )  - choice
     * or ( Entry , Entry , ...  )  - seq
     *
     * where Entry is either a Name, 'choice' or 'seq'.
     *
     * @param   InputElementDecl  The ElementDecl currently being parsed
     * @param   InputFirstToken   The first token
     * @return  The token which follows the entry
     */
    public xe_Token parseElementsEntry(xg_ElementDecl  InputElementDecl,
                                       xe_Token        InputFirstToken) throws IOException
    {
        eh_Debug.add(5, "xe_ElementDeclParser.parseElementsEntry:");

        // Determine whether this is a simple (Name) entry or a complex one.
        xe_Token  CurrentToken = null;
        switch (InputFirstToken.getType())
        {
            case xe_TokenType.NAME:
                // This entry is simply a Name.
              break;

            case xe_TokenType.OPEN_PAREN_CHAR:
                // This entry is either a choice or sequence: parse its list.
                CurrentToken = TheParseManager.parseNextToken(true);
                parseElementsList(InputElementDecl, InputFirstToken, CurrentToken);
              break;

            default:
                TheParseManager.throwParseException("Illegal value at start of element content declaration");
                break;
        }

        // Parse occurrences character ('?', '*' or '+'), if there is one.
        CurrentToken = parseOccurrencesChar(InputElementDecl);

        eh_Debug.add(5, "xe_ElementDeclParser.parseElementsEntry: Return");
        return CurrentToken;
    }

    //*****************************************************************************
    /**
     * Parse the occurrences character ('?', '*', '+' - or it may be omitted
     * altogether) which occurs at the end of an elements list.
     *
     * @param   InputElementDecl  The ElementDecl currently being parsed
     * @return  The token which follows the occurrences character
     */
    public xe_Token parseOccurrencesChar(xg_ElementDecl  InputElementDecl) throws IOException
    {
        eh_Debug.add(5, "xe_ElementDeclParser.parseOccurrencesChar:");

        // Parse the occurrences character ('?', '*' or '+'), if there is one.
        boolean   OccurrencesCharFlag = true;  // Assume there will be one
        xe_Token  CurrentToken = TheParseManager.parseNextToken(true);
        switch (CurrentToken.getType())
        {
            case xe_TokenType.QUESTION_MARK_CHAR:
                // List must occur zero or one times.
                break;

            case xe_TokenType.STAR_CHAR:
                // List must occur zero or more times.
                break;

            case xe_TokenType.PLUS_CHAR:
                // List must occur one or more times.
                break;

            default:
                OccurrencesCharFlag = false;
        }

        // If there is an occurrences character, check it is not preceded by
        // whitespace, and then parse the next token.
        if (OccurrencesCharFlag)
        {
            if (CurrentToken.getPrecedingWhitespace() != null)
                TheParseManager.throwParseException("Whitespace is not allowed before '"
                                                       + CurrentToken.getStringValue() + "'");

            CurrentToken = TheParseManager.parseNextToken(true);
        }

        eh_Debug.add(5, "xe_ElementDeclParser.parseOccurrencesChar: Return");
        return CurrentToken;
    }
}

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