/*
* Jsp Parser
*
*
* @author Ricardo Rocha
* @version 0.2, 1/31/1990
*/

options 
{
   STATIC = false;                             
   IGNORE_CASE  =  true;
}

PARSER_BEGIN(Parser)

package org.plenix.jsp.parser;

import com.ibm.xml.parser.*;
import java.io.*;
import java.util.*;
import org.w3c.dom.*;

import org.plenix.jsp.Util;

public class Parser 
{
  private TXDocument document = null;
  private String language = null;
  private String version = null;

  public static final String JSP_NODE_NAME = "jsp-script";

  public String getLanguage()
  {
    return language;
  }

  public String getVersion()
  {
    return version;
  }

  public Document getDocument()
  {
    return document;
  }

  public static void main(String args[]) throws ParseException 
  {
    // Parse arguments
    Reader input = null;
    PrintWriter output = null;

    String usage =
      "org.plenix.jsp.Parser " +
        "[ -i <input file> ] " +
        "[ -o <output file> ]";

    String Parser = "stdin";

    try 
    {
      int argc = 0;

      // Input file
      if (argc < args.length && args[argc].equals("-i")) 
      {
        if (++argc < args.length) 
        {
          try 
          {
	    Parser = args[argc++];
            input = new FileReader(Parser);
          }
          catch (Exception e) 
          {
            System.err.println("Error opening file " + args[argc - 1] + ": " + e.getMessage());
            System.exit(1);
          }
        }
        else
	{
          throw new ParseException(usage);
        }
      }
      else
      {
        input = new InputStreamReader(System.in);
      }

      // Output file
      if (argc < args.length && args[argc].equals("-o")) 
      {
        if (++argc < args.length) 
        {
          try 
          {
	    FileOutputStream fos = new FileOutputStream(args[argc++]);
	    OutputStreamWriter osw = new OutputStreamWriter(fos);
            output = new PrintWriter(osw);
          }
          catch (Exception e) 
          {
            System.err.println("Error opening file " + args[argc - 1] + ": " + e.getMessage());
            System.exit(1);
          }
        }
        else
	{
          throw new ParseException(usage);
        }
      }
      else
      {
        output = new PrintWriter(new OutputStreamWriter(System.out), true);
      }
    }
    catch (Exception e) 
    {
      System.err.println(e.getMessage());
      System.exit(1);
    }

    try 
    {
      Parser parser = new Parser(input);

      parser.parse(Parser);

      Document document = parser.getDocument();

      ((TXDocument) document).printWithFormat(output);
      output.flush();
    }
    catch (Exception e) 
    {
      System.err.println(e.getClass().getName());
      e.printStackTrace(System.err);
      System.exit(1);
    }
  }

  private Element createTextElement(String name, String data) 
  {
    Element element = document.createElement(name);
    Text text = document.createTextNode(data);
    element.appendChild(text);

    return element;
  }
}

PARSER_END(Parser)

/* *** PARSER *** */

void parse(String name): 
{
  language = System.getProperty("polyjsp.default.language");
  version = System.getProperty("polyjsp.default.version");

  if (language == null)
  {
    language = "java";
  }

  if (version == null)
  {
    version = "0.92"; 
  }

  document = new TXDocument();

  Element root = document.createElement(JSP_NODE_NAME);
  root.setAttribute("name", name);
  document.appendChild(root);

  Element body = document.createElement("body");

  Node node = null;
}
{
  (
      ( node = directive() { root.appendChild(node); } )
    | ( node = declaration() { root.appendChild(node); } )
    | ( node = bodyRegion() { body.appendChild(node); } )
  ) +
  <EOF> 
  {
    root.appendChild(body);
  }
}

Node bodyRegion(): 
{
  Node  node;
}
{
  (
      node = emptyTag()
    | node = nonEmptyTag()
    | node = scriptlet()
    | node = inclusion()
    | node = boilerplate()
  )
  {
    return node;
  }
}

Node boilerplate():
{
  String boilerplate;
  Vector vector = new Vector();
}
{
  (
    LOOKAHEAD(2) boilerplate = pcData() 
    {
      vector.addElement(boilerplate);
    }
  )+
  {
    int size = vector.size();
    DocumentFragment node = document.createDocumentFragment();

    for (int i= 0; i < size; i++)
    {
      String line = (String) vector.elementAt(i);
      node.appendChild(createTextElement("boilerplate", Util.stringEncode(line)));
    }

    return node;
  }
}

Node directive(): 
{
  Attr attr;
  Node node = null;
}
{
  <BEGIN_DIRECTIVE>
  attr = attribute() 
  {
    String name = attr.getName();
    String value = attr.getValue();

    if (name.equalsIgnoreCase("language")) 
    {
      language = value.toLowerCase();
    }
    else if (name.equalsIgnoreCase("version"))
    {
      version = value.toLowerCase();
      TagDefinition.setVersion(version);
    }
    else
    {
      node = createTextElement(name, value);
    }
  }
  <END_TAG> 
  {
    return node;
  }
}

Element declaration(): 
{
  Token t = null;
  String codeText = null;
}
{
    <BEGIN_DECLARATION> codeText = code() <END_DECLARATION> 
    {
     return createTextElement("declaration", codeText.trim());
    }
}

Element emptyTag(): 
{
  Token token = null;
  Element element = null;
  String name = null;
  Attr attr = null;
}
{
  token = <BEGIN_EMPTY_TAG> 
  {
    name = token.image.substring(1).toLowerCase();
    element = document.createElement(name);
  }
  ( attr = attribute() { element.setAttributeNode(attr); } ) *
  <END_TAG> 
  {
    return element;
  }
}

Element nonEmptyTag(): 
{
  Node node = null;
  Attr attr = null;
  Token token = null;
  Element element = null;
  Element body = document.createElement("body");
}
{
  token = <OPEN_TAG> 
  {
    String name = token.image.substring(1).toLowerCase();
    element = document.createElement(name);
  }
  ( attr = attribute() { element.setAttributeNode(attr); } ) *
  <END_TAG>
  ( node = bodyRegion(){ body.appendChild(node); } ) *
  {
    element.appendChild(body);
  }
  <CLOSE_TAG> 
  {
    return element;
  }
}

Element scriptlet(): 
{
  Token token = null;
  Element element = null;
  String name = null;
  String code = null;
}
{
    token = <BEGIN_SCRIPTLET> 
    {
      if (token.image.substring(2).equals("="))
      {
        name = "expression";
      }
      else
      {
        name = "scriptlet";
      }
    }
    code = code()
    <END_SCRIPTLET> 
    {
      if (name.equals("expression"))
      {
        code = code.trim();
      }

      element = document.createElement(name);
      Text text = document.createTextNode(code);
      element.appendChild(text);

      return element;
    }
}

Element inclusion(): 
{
  Attr attr = null;
  Element element = null;
}
{
  <BEGIN_INCLUSION> { element = document.createElement("include"); }
  ( attr = attribute() { element.setAttributeNode(attr); } ) +
  <END_TAG> 
  {
    return element;
  }
}

Attr attribute(): 
{
  Attr attr = null;
  Token token = null;
  String name = null;
  String value = null;
}
{
  token = <ATTRIBUTE_NAME> { name = token.image.toLowerCase(); }
  (
    <EQUALS>

    (
        token = <ATTRIBUTE_VALUE> 
        {
	  value = Util.unquote(token.image);
	}
      | token = <ATTRIBUTE_NAME> 
      {
	  value = token.image;
	}
    )
  ) ?
  {
    attr = document.createAttribute(name);
    attr.setValue(value);

    return attr;
  }
}


String pcData(): 
{
  Token token = null;
}
{
  (
      token = <PCDATA>
    | token = <COMMENT>
  )
  {
    return token.image;
  }
}

String code(): 
{
  Token token = null;
  StringBuffer buffer = new StringBuffer();
}
{
    (
        token = <UNQUOTED_CODE> 
        {
          buffer.append(token.image);
	}
      | token = <STRING> 
      {
          buffer.append(token.image);
	}
    ) +
    {
      return buffer.toString();
    }
}

/* *** LEXER *** */
/* DEFAULT */
SKIP:
{
    <			  <WHITESPACE> >
}

TOKEN: 
{
    < TAG:		  <LT> (<SLASH>)? <TAG_IDENTIFIER> (<GT>)? > 
    {
      SwitchTo(setToken(matchedToken));
    }
 |  < BEGIN_DIRECTIVE:	  "<%@" >			: WithinTag
 |  < BEGIN_SCRIPTLET:	  "<%" ( "=" ) ? >		: WithinCode
 |  < BEGIN_DECLARATION:  <BEGIN_SCRIPT> >		: WithinCode
 |  < BEGIN_INCLUSION:     "<!--#" >			: WithinTag
 |  < COMMENT:		  <SGML_COMMENT> >
 |  < PCDATA: 		  ( ~["<"] ) + >
}

<WithinTag>TOKEN: 
{
    < ATTRIBUTE_NAME:	  <IDENTIFIER> >
 |  < EQUALS:		  <EQ> >
 |  < ATTRIBUTE_VALUE:	  <CDATA> >
 |  < END_TAG:		  ( ( "%" ) ? | "--") <GT> >	: DEFAULT
}
<WithinTag>SKIP: { <TAG_WHITESPACE: <WHITESPACE> > }

<WithinCode>TOKEN: 
{
    < END_SCRIPTLET:  	  <END_CODE> >			: DEFAULT
  | < END_DECLARATION:	  <END_SCRIPT> >		: DEFAULT
  | < STRING:		  <QUOTED_STRING> >
  | < UNQUOTED_CODE:	  ( ~[ "/", "'", "\"", "%", "<" ] ) + >
}

<WithinCode>MORE: { < [ "/", "'", "\"", "%", "<" ] > }

<WithinCode>SPECIAL_TOKEN: 
{
    <SINGLE_LINE_COMMENT: "//" ( ~[ "\n" ] ) * "\n" >			// *** Language dependent!
  | <MULTI_LINE_COMMENT: "/*" ( ~[ "*" ] | "*" ~[ "/" ] ) * "*/" >	// *** Language dependent!
}

/* Reference Tokens */
<LexicalReference> TOKEN: 
{
    < LT:		  "<" >
  | < GT:		  ">" >
  | < EQ:		  "=" >
  | < SLASH:		  "/" >
  | < WHITESPACE:	  [" ", "\t", "\r", "\n"] >
  | < QUOTED_STRING:							// *** Language dependent!
			  ( "'" (~["'"] | "\\'" | "''")* "'" )
			| ( "\"" (~["\""] | "\\\"" | "\"\"")* "\"" )
    >
  | < CDATA:		  <QUOTED_STRING>
			| ( ~[" ", "\t", "\n", "\r", "=", "%", "<", ">"] )+
    >
  | < IDENTIFIER:	  [ "a" - "z", "A" - "Z", "_" ]			// *** Language dependent!
			  ( [ "a" - "z", "A" - "Z", "0" - "9", "_" ] ) *
    >
  | < TAG_IDENTIFIER: <IDENTIFIER> > // Could change for other languages
  | < END_CODE:		  "%>" >
  | < SGML_COMMENT:	  "<!--"
			  (
			    ("-->") |
			    ~["#"]
 			    ( ~["-"] | "-" ~["-"] | "--" ~[">"] )*
			    "-->"
			  )
    >
}

<LexicalReference> TOKEN: 
{
    < EMPTY_TAG:	  <IDENTIFIER> >
  | < NONEMPTY_TAG:	  <IDENTIFIER> >
  | < BEGIN_SCRIPT:	  <LT> "SCRIPT"
			  ( <WHITESPACE> ) +
			  "RUNAT"
			  ( <WHITESPACE> ) *
			  "="
			  ( <WHITESPACE> ) *
			  ("'" | "\"") * "SERVER" ("'" | "\"") *
			  ( <WHITESPACE> ) *
			  ">"
    >
  | < END_SCRIPT:	  "</SCRIPT>" >

  | < BEGIN_EMPTY_TAG:	  <LT> <EMPTY_TAG> >		: WithinTag
  | < OPEN_TAG:		  <LT> <NONEMPTY_TAG> >		: WithinTag
  | < CLOSE_TAG:	  <LT> <SLASH> <NONEMPTY_TAG> >	: DEFAULT
}

TOKEN_MGR_DECLS: 
{
  int setToken(Token token) 
  {
      // Determine tag name and context (opening/closing)
      String image = token.image;
      boolean closing = image.startsWith("</");

      int startPos = (closing ? 2 : 1);
      int endPos = image.length() - (closing ? 1 : 0);
      String tagName = image.substring(startPos, endPos).toLowerCase();

      // Classify tag name
      TagDefinition definition = TagDefinition.get(tagName);

      // Non-JSP tag?
      if (definition == null) 
      {
        token.kind = PCDATA;
	return DEFAULT;
      }

      // Close tag?
      if (closing) 
      {
        token.kind = CLOSE_TAG;
	return DEFAULT;
      }

      // Open tag
      token.kind = (definition.isEmpty() ? BEGIN_EMPTY_TAG : OPEN_TAG);
      return WithinTag;
  }
}
