//*****************************************************************************
/*
** FILE:   xe_elementParser.java
**
** (c) 1997, 1998 Steve Withall.
**
** HISTORY:
**    13Oct97  stevew  Created.
*/
package xe;

import xg.xg_Attribute;
import xg.xg_Element;
import xg.xg_Node;
import xg.xg_Reference;
import xg.xg_Text;

import eh.eh_Debug;

import java.io.IOException;

//*****************************************************************************
/** Standard parser for an XML element, assuming the beginning ('<Name') has
 *  already been parsed. Elements can have one of the following forms:
 *
 *    <Name [Att1 = "Value1" Att2 = "Value2" ...]/>
 *    <Name [Att1 = "Value1" Att2 = "Value2" ...]>[Content]</Name>
 */
public class xe_ElementParser extends xe_Parser
{
    //*****************************************************************************
    /** Parse the body of an XML element, putting the result in a newly-created
     *  element object.
     *
     *  @return  The parsed element
     */
    public xg_Node parse() throws IOException
    {
        eh_Debug.add(5, "xe_ElementParser.parse: Start parsing element '" + EntityName + "'");

        // Create new element object.
        xg_Element  NewElement = (xg_Element)createNode("xg.xg_Element");
//        xg_Element  NewElement = (xg_Element)TheParseManager.createEntity(EntityName,
//                                                                          EntityType,
//                                                                          NodePrecedingWhitespace,
//                                                                          "xg.xg_Element",
//                                                                          ParentNode);

        // Retrieve the attribute name/value pairs (if any).
        boolean  EmptyElementFlag = parseAttributes(NewElement);

        // Retrieve the content (if any), plus the end tag.
        if (!EmptyElementFlag)
            parseContent(NewElement);

        TheParseManager.fireEndNodeEvent(NewElement);
//        eh_Debug.add(7, "xe_ElementParser: Completed parsing element '" + EntityName + "'");
        return NewElement;
    }

    //*****************************************************************************
    /** Parse the attributes of an XML element, if it has any.
     *
     *  Keep looking for 'Name = "Value"' attributes until we encounter '>' or '/>'.
     *
     *  @param  InputElement  The element currently being parsed
     *  @return               Whether the element is empty: true means it is
     */
    public boolean parseAttributes(xg_Element  InputElement) throws IOException
    {
//        eh_Debug.add(7, "xe_ElementParser.parseAttributes: Try to parse the element's attributes");

        xe_Token      CurrentToken       = null;
        xg_Attribute  NewAttribute       = null;
        boolean       EmptyElementFlag   = false;
        boolean       MoreAttributesFlag = true;
        while (MoreAttributesFlag)
        {
            CurrentToken = TheParseManager.parseNextToken(true);
            if (CurrentToken.getType() == xe_TokenType.TAG_END_CHAR)
                // '>' means end of start tag for non-empty element.
                MoreAttributesFlag = false;
            else if (CurrentToken.getType() == xe_TokenType.EMPTY_TAG_END)
            {
                // '/>' means end of empty element.
                EmptyElementFlag   = true;
                MoreAttributesFlag = false;
            }
            else
            {
                // We have the beginning of a new attribute. Parse the rest of it.
                NewAttribute = parseAttribute(CurrentToken);
                InputElement.addAttribute(NewAttribute);
            }
        }

        return EmptyElementFlag;
    }

    //*****************************************************************************
    /** Parse the content of an XML element, adding it to the InputElement.
     *
     *  Treat everything up to the next '</' as content (the closing tags of child
     *  elements excepted). This method then parses the remainder of the end tag.
     *
     *  Content can comprise any combination of the following types of entity:
     *
     *    Reference:               &Name; or &#nn; or &#xnn;
     *    CDATA section:           <![CDATA[...]]>
     *    Comment:                 <!--...-->
     *    Processing instruction:  <?Name ...?>
     *    Element:                 <Name>...</Name>
     *    PCData:                  Text
     *
     *  ... and:
     *
     *    End tag                  </Name>
     *
     *  @param  InputElement  The element currently being parsed
     */
    public void parseContent(xg_Element  InputElement) throws IOException
    {
        eh_Debug.add(7, "xe_ElementParser.parseContent: Parse the (mark-up) content of element");

        xe_Token  CurrentToken        = null;
        String    PrecedingWhitespace = null;
        xg_Node   NewNode             = null;
        boolean   MoreContentFlag     = true;
        while (MoreContentFlag)
        {
            PrecedingWhitespace = TheParseManager.parseWhitespace(true);
            CurrentToken        = TheParseManager.parseContentToken();
            NewNode             = null;
            switch (CurrentToken.getType())
            {
                case xe_TokenType.END_TAG_START:
                    // This is the start of this element's end tag.
                    parseEndTag(InputElement, CurrentToken);
                    InputElement.setTrailingWhitespace(PrecedingWhitespace);
                    MoreContentFlag = false;
                    break;

                case xe_TokenType.ELEMENT_START:
                case xe_TokenType.COMMENT_START:
                case xe_TokenType.CDATA_START:
                case xe_TokenType.PI_START:
                    // All these things can be handled generically here.
                    CurrentToken.setPrecedingWhitespace(PrecedingWhitespace);
                    NewNode = parseNextEntity(CurrentToken, InputElement);
                    break;

                case xe_TokenType.ENTITY_REFERENCE:
                case xe_TokenType.CHAR_REFERENCE_DEC:
                case xe_TokenType.CHAR_REFERENCE_HEX:
                    // Have parsed a reference.
//                    eh_Debug.add(7, "xe_ElementParser.parseContent: Create new xg_Reference '"
//                                           + CurrentToken.getStringValue() + "'");
                    //TBD
                    eh_Debug.add(4, "***References not yet properly supported*** ('"
                                             + EntityName + "')");
                    xg_Reference  NewReference = new xg_Reference(CurrentToken.getStringValue() );
                    NewNode.setPrecedingWhitespace(PrecedingWhitespace);
//                    NewReference.setType(CurrentToken.getType());  //TBD
                    //TBD To add to tree
                    break;

                case xe_TokenType.PCDATA:
                    // Have parsed a piece of plain text (PCData).
//                    eh_Debug.add(7, "xe_ElementParser.parseContent: Create new xg_Text '"
//                                          + CurrentToken.getStringValue() + "'");
                    NewNode = new xg_Text(CurrentToken.getStringValue() );
                    NewNode.setPrecedingWhitespace(PrecedingWhitespace);
                    TheParseManager.certifyNewNode(NewNode, InputElement);
                    TheParseManager.fireEndNodeEvent(NewNode);
                    break;

                case xe_TokenType.END_OF_SOURCE:
                    // We shouldn't have reached the end yet!
                    TheParseManager.throwParseException("Unexpected end of source within element '"
                                                          + EntityName + "'");

                default:
                    // Whatever it is, it isn't valid here.
                    TheParseManager.throwParseException("Illegal value (" + CurrentToken
                                                          + ") within content of element '"
                                                          + EntityName + "'");
            }
        }

        return;
    }

    //*****************************************************************************
    /** Parse the end tag, which we expect to be of the form </Name>, where Name
     *  is the name of the InputElement. If the end tag is preceded by whitespace,
     *  it is assumed the whitespace has already been parsed.
     *
     *  @param  InputElement      The element currently being parsed
     *  @param  InputEndTagToken  The body of the end tag - the </Name part
     */
    public void parseEndTag(xg_Element  InputElement,
                            xe_Token    InputEndTagToken) throws IOException
    {
//        eh_Debug.add(7, "xe_ElementParser.parseEndTag: Parse the end tag of element");

        String  StartElementName = InputElement.getName();
        String  EndElementName   = InputEndTagToken.getStringValue();

        if (StartElementName.compareTo(EndElementName) != 0)
        {
            // The end name is different to the start name.
            String  ErrorMessage = null;
            if (StartElementName.equalsIgnoreCase(EndElementName))
                ErrorMessage = new String("Element '" + StartElementName
                                             + "' has end tag different in case ('"
                                             + EndElementName + "')");
            else
                ErrorMessage = new String("Element '" + StartElementName
                                             + "' has incorrect name ('"
                                             + EndElementName + "') in end tag");
            TheParseManager.throwParseException(ErrorMessage);
        }

        // Get the end of the end tag (and ignore it).
        //TBD Note that it may have preceding whitespace - which we don't store.
        xe_Token  EndToken = TheParseManager.parseNextToken(true);
        if (EndToken.getType() != xe_TokenType.TAG_END_CHAR)
            TheParseManager.throwParseException("End tag does not end in '>' ("
                                                  + EndToken + ")");
        return;
    }
}

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