//*****************************************************************************
/*
** FILE:   xe_AttListDeclParser.java
**
** (c) 1997, 1998 Steve Withall.
**
** HISTORY:
**    09Nov97  stevew  Created.
*/
package xe;

import xm.xm_ParseException;

import xg.xg_AttDecl;
import xg.xg_AttListDecl;
import xg.xg_Dtd;
import xg.xg_Node;

import xa.xa_AttTypeChoiceList;

import eh.eh_Debug;

import java.io.IOException;

//*****************************************************************************
/** Parser for an attribute list declaration within the DTD of an XML document,
 *  assuming the beginning ('<!ATTLIST') has already been parsed. AttListDecls
 *  have the roughly following form:
 *
 *    <!ATTLIST Name [AttDecl]+ >
 */
public class xe_AttListDeclParser extends xe_Parser
{
    static final xa_AttTypeChoiceList  AttTypeChoiceList = new xa_AttTypeChoiceList();

    //*****************************************************************************
    /** Parse the body of an attribute list declaration.
     *
     *  @return     The parsed AttListDecl
     *  @exception  xm_ParseException  XML wellformedness error
     *  @exception  IOException        Error reading from source reader
     */
    public xg_Node parse() throws xm_ParseException, IOException
    {
        eh_Debug.add(5, "xe_AttListDeclParser.parse: Start parsing AttListDecl");

        // Create new attribute list declaration object.
        xg_AttListDecl  NewAttListDecl = (xg_AttListDecl)createNode("xg.xg_AttListDecl");
//        xg_AttListDecl  NewAttListDecl
//                         = (xg_AttListDecl)TheParseManager.createEntity(EntityName,
//                                                                        EntityType,
//                                                                        "xg.xg_AttListDecl",
//                                                                        ParentNode);
        // Parse the name of this attribute list declaration.
        xe_Token  CurrentToken = TheParseManager.parseNameToken(true);
        String  AttListDeclName = CurrentToken.getStringValue();
        NewAttListDecl.setName(AttListDeclName);

        // Parse the declaration of each attribute.
        CurrentToken = TheParseManager.parseNextToken(true);
        boolean  MoreAttributesFlag = true;
        while (MoreAttributesFlag)
        {
            switch (CurrentToken.getType())
            {
                case xe_TokenType.TAG_END_CHAR:
                    // This is the end of the attribute list declaration.
                    MoreAttributesFlag = false;
                    break;

                case xe_TokenType.NAME:
                    // Treat this as the name of the declaration of the next attribute.
                    parseAttDecl(NewAttListDecl, CurrentToken);
                    break;

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

            if (MoreAttributesFlag)
                CurrentToken = TheParseManager.parseNextToken(true);
        }

        eh_Debug.add(7, "xe_AttListDeclParser: Completed parsing ATTLIST declaration '" + EntityName + "'");
        return NewAttListDecl;
    }

    //*****************************************************************************
    /** Parse the declaration of an attribute, assuming that its name has already
     *  been parsed (and is in the InputStartToken). It should have the form:
     *
     *     Name AttType Default
     *  or Name (Value|Value|...) Default
     *     ===>
     *  @param      InputAttListDecl   The AttListDecl currently being parsed
     *  @param      InputStartToken    The first token of the external subset
     *  @exception  xm_ParseException  XML wellformedness error
     *  @exception  IOException        Error reading from source reader
     */
    public xe_Token parseAttDecl(xg_AttListDecl  InputAttListDecl,
                                 xe_Token        InputStartToken)
                                      throws xm_ParseException, IOException
    {
        eh_Debug.add(5, "xe_AttListDeclParser.parseAttDecl:");

        //TBD Check that there isn't an attribute with this name already declared.

        // Create an attribute declaration object.
        xg_AttDecl  NewAttDecl = new xg_AttDecl();
        NewAttDecl.setPrecedingWhitespace(InputStartToken.getPrecedingWhitespace());
        NewAttDecl.setName(InputStartToken.getStringValue());

        xe_Token  CurrentToken = TheParseManager.parseNextToken(true);
        switch (CurrentToken.getType())
        {
            case xe_TokenType.NAME:
                int  AttType = AttTypeChoiceList.getChoice(CurrentToken.getStringValue() );
                if (AttType == -1)
                    TheParseManager.throwParseException("Illegal attribute type '"
                                                           + CurrentToken.getStringValue() + "'");
                NewAttDecl.setType(AttType);

                // String or tokenized attributes require no further work.
                if (CurrentToken.getStringValue().compareTo(xa_AttTypeChoiceList.NOTATION_STRING) == 0)
                {
                    // NOTATION enumerated type.
                    CurrentToken = TheParseManager.parseNextTokenExpected(xe_TokenType.OPEN_PAREN_CHAR, true);

                    //TBD This is not strictly correct, because the names in a NOTATION
                    //TBD list have a different allowed format from a normal enumeration.
                    parseEnumeratedAtt(NewAttDecl, CurrentToken);
                }
                break;

            case xe_TokenType.OPEN_PAREN_CHAR:
                // This is an enumerated attribute.
                NewAttDecl.setType(xg_AttDecl.ENUMERATION_TYPE);
                parseEnumeratedAtt(NewAttDecl, CurrentToken);
                break;

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

        parseAttDefault(NewAttDecl);   // Parse the default value

        eh_Debug.add(5, "xe_AttListDeclParser.parseAttDecl: Return");
        return CurrentToken;
    }

    //*****************************************************************************
    /** Parse an enumerated attribute declaration, which defines a list of values
     *  this attribute may have. We assume its name, NOTATION keyword (if present)
     *  and opening parenthesis have already been parsed. It has the rough form:
     *
     *     Name [NOTATION](Value|Value|...) Default
     *     ===============>
     *  @param      InputAttDecl         The attribute declaration currently being parsed
     *  @param      InputOpenParenToken  The open parenthesis token
     *  @exception  xm_ParseException    XML wellformedness error
     *  @exception  IOException          Error reading from source reader
     */
    public void parseEnumeratedAtt(xg_AttDecl  InputAttDecl,
                                   xe_Token    InputOpenParenToken)
                                        throws xm_ParseException, IOException
    {
        eh_Debug.add(5, "xe_AttListDeclParser.parseEnumeratedAtt:");

        // Parse the first value (which is mandatory).
        xe_Token  CurrentToken   = null;
        boolean   MoreValuesFlag = true;
        while (MoreValuesFlag)
        {
            CurrentToken = TheParseManager.parseNextTokenExpected(xe_TokenType.NAME, true);
            if (InputAttDecl.isValueAllowed(CurrentToken.getStringValue() ) )
                TheParseManager.throwParseException("Value '"
                                                       + CurrentToken.getStringValue()
                                                       + "' appears twice in list of allowed values");

            InputAttDecl.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");
            }
        }

        eh_Debug.add(5, "xe_AttListDeclParser.parseEnumeratedAtt: Return");
        return;
    }

    //*****************************************************************************
    /** Parse the default value for an attribute declaration, assuming all its
     *  preceding parts have already been parsed. It has the rough form:
     *
     *     #REQUIRED
     *  or #IMPLIED
     *  or [#FIXED] "DefaultValue"
     *
     *  @param      InputAttDecl         The attribute declaration currently being
     *                                    parsed
     *  @exception  xm_ParseException    XML wellformedness error
     *  @exception  IOException          Error reading from source reader
     */
    public void parseAttDefault(xg_AttDecl  InputAttDecl)
                                     throws xm_ParseException, IOException
    {
        eh_Debug.add(5, "xe_AttListDeclParser.parseAttDefault:");
        xe_Token  CurrentToken = TheParseManager.parseNextToken(true);
        switch (CurrentToken.getType())
        {
            case xe_TokenType.HASH_CHAR:
                // We've read '#'; expect #REQUIRED, #IMPLIED or #FIXED.
                CurrentToken = TheParseManager.parseNameToken(false);
                String  CurrentTokenValue = CurrentToken.getStringValue();
                if (CurrentTokenValue.compareTo(xg_AttDecl.REQUIRED_STRING) == 0)
                    InputAttDecl.setDefaultType(xg_AttDecl.REQUIRED);
                else if (CurrentTokenValue.compareTo(xg_AttDecl.IMPLIED_STRING) == 0)
                    InputAttDecl.setDefaultType(xg_AttDecl.IMPLIED);
                else if (CurrentTokenValue.compareTo(xg_AttDecl.FIXED_STRING) == 0)
                {
                    // #FIXED is followed by the fixed value itself (in quotes).
                    InputAttDecl.setDefaultType(xg_AttDecl.FIXED);
                    CurrentToken = TheParseManager.parseLiteralToken(true);
                    InputAttDecl.setDefaultValue(CurrentToken.getStringValue() );
                }
                else
                    TheParseManager.throwParseException("Illegal default value in attribute declaration");
                break;

            case xe_TokenType.SINGLE_QUOTE_CHAR:
                // A quote indicates the start of the default value itself.
                InputAttDecl.setDefaultType(xg_AttDecl.SIMPLE);
                CurrentToken = TheParseManager.parseLiteralToken('\'');
                InputAttDecl.setDefaultValue(CurrentToken.getStringValue() );
                break;

            case xe_TokenType.DOUBLE_QUOTE_CHAR:
                // A quote indicates the start of the default value itself.
                InputAttDecl.setDefaultType(xg_AttDecl.SIMPLE);
                CurrentToken = TheParseManager.parseLiteralToken('\"');
                InputAttDecl.setDefaultValue(CurrentToken.getStringValue() );
                break;

            default:
                TheParseManager.throwParseException("Illegal default value in attribute declaration");
                break;
        }
    }
}

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