/* -*-java-extended-*-
 * Copyright (c) 1999 World Wide Web Consortium
 * (Massachusetts Institute of Technology, Institut National de Recherche
 *  en Informatique et en Automatique, Keio University).
 * All Rights Reserved. http://www.w3.org/Consortium/Legal/
 *
 * $Id: Parser.jj,v 1.15 2000/10/27 21:09:37 plehegar Exp $
 */

options {
    IGNORE_CASE  = true;
    STATIC = false;
    USER_CHAR_STREAM = true;
    /* DEBUG_TOKEN_MANAGER = true;
       DEBUG_PARSER = true; */
}

PARSER_BEGIN(Parser)

package com.vaadin.sass.parser;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;

import org.w3c.css.sac.ConditionFactory;
import org.w3c.css.sac.Condition;
import org.w3c.css.sac.SelectorFactory;
import org.w3c.css.sac.SelectorList;
import org.w3c.css.sac.Selector;
import org.w3c.css.sac.SimpleSelector;
import org.w3c.css.sac.DocumentHandler;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.ErrorHandler;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.Locator;
import org.w3c.css.sac.LexicalUnit;

import org.w3c.flute.parser.selectors.SelectorFactoryImpl;
import org.w3c.flute.parser.selectors.ConditionFactoryImpl;

import org.w3c.flute.util.Encoding;

import com.vaadin.sass.handler.*;

import com.vaadin.sass.tree.*;

/**
 * A CSS2 parser
 *
 * @author Philippe Le H�garet
 * @version $Revision: 1.15 $
 */
public class Parser implements org.w3c.css.sac.Parser {

    // replaces all \t, \n, etc with this StringBuffer.
    static final StringBuilder SPACE = new StringBuilder(" ");

    // the document handler for the parser
    protected SCSSDocumentHandlerImpl documentHandler;
    // the error handler for the parser
    protected ErrorHandler errorHandler;
    // the input source for the parser
    protected InputSource source;

    protected ConditionFactory conditionFactory;
    protected SelectorFactory selectorFactory;

    // temporary place holder for pseudo-element ...
    private String pseudoElt;
    
    /**
     * Creates a new Parser
     */
    public Parser() {
	this((CharStream) null);
    }

    /**
     * @@TODO
     * @exception CSSException Not yet implemented
     */    
    public void setLocale(Locale locale) throws CSSException {
	throw new CSSException(CSSException.SAC_NOT_SUPPORTED_ERR);
    }

    /**
     * Set the document handler for this parser
     */    
    public void setDocumentHandler(DocumentHandler handler) {
	this.documentHandler = (SCSSDocumentHandlerImpl) handler;
    }

    public void setSelectorFactory(SelectorFactory selectorFactory) {
	this.selectorFactory = selectorFactory;
    }

    public void setConditionFactory(ConditionFactory conditionFactory) {
	this.conditionFactory = conditionFactory;
    }

    /**
     * Set the error handler for this parser
     */    
    public void setErrorHandler(ErrorHandler error) {
	this.errorHandler = error;
    }
    
    /**
     * Main parse methods
     *
     * @param source the source of the style sheet.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */
    public void parseStyleSheet(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));
	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}

	parserUnit();
    }

    /**
     * Convenient method for URIs.
     *
     * @param systemId the fully resolved URI of the style sheet.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */    
    public void parseStyleSheet(String systemId) 
	    throws CSSException, IOException {
	parseStyleSheet(new InputSource(systemId));
    }

    /**
     * This method parses only one rule (style rule or at-rule, except @charset).
     *
     * @param source the source of the rule.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */    
    public void parseRule(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseRule();
    }

    /**
     * This method parses a style declaration (including the surrounding curly
     * braces).
     *
     * @param source the source of the style declaration.
     * @exception IOException the source can't be parsed.
     * @exception CSSException the source is not CSS valid.
     */
    public void parseStyleDeclaration(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseDeclarationBlock();
    }

    /**
     * This methods returns "http://www.w3.org/TR/REC-CSS2".
     * @return the string "http://www.w3.org/TR/REC-CSS2".
     */    
    public String getParserVersion() {
	return "http://www.w3.org/TR/REC-CSS2";
    }

    /**
     * Parse methods used by DOM Level 2 implementation.
     */    
    public void parseImportRule(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseImportRule();
    }

    public void parseMediaRule(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	_parseMediaRule();
    }

    public SelectorList parseSelectors(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	if (selectorFactory == null) {
	    selectorFactory = new SelectorFactoryImpl();
	}
	if (conditionFactory == null) {
	    conditionFactory = new ConditionFactoryImpl();
	}
	return _parseSelectors();
    }

    public LexicalUnit parsePropertyValue(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	return expr();
    }

    public boolean parsePriority(InputSource source) 
	    throws CSSException, IOException {
	this.source = source;
	ReInit(getCharStreamWithLurk(source));

	return prio();
    }

    /**
     * Convert the source into a Reader. Used only by DOM Level 2 parser methods.
     */    
    private Reader getReader(InputSource source) throws IOException {
	if (source.getCharacterStream() != null) {
	    return source.getCharacterStream();
	} else if (source.getByteStream() != null) {
	    // My DOM level 2 implementation doesn't use this case.
	    if (source.getEncoding() == null) {
		// unknown encoding, use ASCII as default.
		return new InputStreamReader(source.getByteStream(), "ASCII");
	    } else {
		return new InputStreamReader(source.getByteStream(), 
					     source.getEncoding());
	    }
	} else {
	    // systemId
	    // @@TODO
	    throw new CSSException("not yet implemented");
	}
    }

    /**
     * Convert the source into a CharStream with encoding informations.
     * The encoding can be found in the InputSource or in the CSS document.
     * Since this method marks the reader and make a reset after looking for
     * the charset declaration, you'll find the charset declaration into the
     * stream.  
     */
    private CharStream getCharStreamWithLurk(InputSource source)
	    throws CSSException, IOException {
	if (source.getCharacterStream() != null) {
	    // all encoding are supposed to be resolved by the user
	    // return the reader
	    return new Generic_CharStream(source.getCharacterStream(), 1, 1);
	} else if (source.getByteStream() == null) {
	    // @@CONTINUE ME. see also getReader() with systemId
	    try {
		source.setByteStream(new URL(source.getURI()).openStream());
	    } catch (Exception e) {
		try {
		    source.setByteStream(new FileInputStream(source.getURI()));
		} catch (IOException ex) {
		    throw new CSSException("invalid url ?");
		}
	    }
	}
	String encoding = "ASCII";
	InputStream input = source.getByteStream();
	char c = ' ';

	if (!input.markSupported()) {
	    input = new BufferedInputStream(input);
	    source.setByteStream(input);
	}
	input.mark(100);
	c = (char) input.read();

	if (c == '@') {
	    // hum, is it a charset ?
	    int size   = 100;
	    byte[] buf = new byte[size];
	    input.read(buf, 0, 7);
	    String keyword = new String(buf, 0, 7);
	    if (keyword.equals("charset")) {
		// Yes, this is the charset declaration !

		// here I don't use the right declaration : white space are ' '.
		while ((c = (char) input.read()) == ' ') {
		    // find the first quote
		}
		char endChar = c;
		int i = 0;

		if ((endChar != '"') && (endChar != '\'')) {
		    // hum this is not a quote.
		    throw new CSSException("invalid charset declaration");
		}

		while ((c = (char) input.read()) != endChar) {
		    buf[i++] = (byte) c;
		    if (i == size) {
			byte[] old = buf;
			buf = new byte[size + 100];
			System.arraycopy(old, 0, buf, 0, size);
			size += 100;
		    }
		}
		while ((c = (char) input.read()) == ' ') {
		    // find the next relevant character
		}
		if (c != ';') {
		    // no semi colon at the end ?
		    throw new CSSException("invalid charset declaration: "
					   + "missing semi colon");
		}
		encoding = new String(buf, 0, i);
		if (source.getEncoding() != null) {
		    // compare the two encoding informations.
		    // For example, I don't accept to have ASCII and after UTF-8.
		    // Is it really good ? That is the question.
		    if (!encoding.equals(source.getEncoding())) {
			throw new CSSException("invalid encoding information.");
		    }
		}
	    } // else no charset declaration available
	}
	// ok set the real encoding of this source.
	source.setEncoding(encoding);
	// set the real reader of this source.
	source.setCharacterStream(new InputStreamReader(source.getByteStream(),
                                             Encoding.getJavaEncoding(encoding)));
	// reset the stream (leave the charset declaration in the stream).
	input.reset();

	return new Generic_CharStream(source.getCharacterStream(), 1, 1);
    }

    private LocatorImpl currentLocator;
    private Locator getLocator() {
	if (currentLocator == null) {
	    currentLocator = new LocatorImpl(this);
	    return currentLocator;
	}
	return currentLocator.reInit(this);
    }
    private LocatorImpl getLocator(Token save) {
	if (currentLocator == null) {
	    currentLocator = new LocatorImpl(this, save);
	    return currentLocator;
	}
	return currentLocator.reInit(this, save);
    }

    private void reportError(Locator l, Exception e) {
	if (errorHandler != null) {
	    if (e instanceof ParseException) {
		// construct a clean error message.
		ParseException pe = (ParseException) e;
		if (pe.specialConstructor) {
		    StringBuffer errorM = new StringBuffer();
		    if (pe.currentToken != null) {
			errorM.append("encountered \"")
			    .append(pe.currentToken.next);
		    }
		    errorM.append('"');
		    if (pe.expectedTokenSequences.length != 0) {
			errorM.append(". Was expecting one of: ");
			for (int i = 0; i < pe.expectedTokenSequences.length; i++) {
			    for (int j = 0; j < pe.expectedTokenSequences[i].length; j++) {
				int kind = pe.expectedTokenSequences[i][j];
				if (kind != S) {
				    errorM.append(pe.tokenImage[kind]);
				    errorM.append(' ');
				}
			    }
			}
		    }
		    errorHandler.error(new CSSParseException(errorM.toString(), 
							     l, e));
		} else {
		    errorHandler.error(new CSSParseException(e.getMessage(), 
							     l, e));
		}
	    } else if (e == null) {
		errorHandler.error(new CSSParseException("error", l, null));
	    } else {
		errorHandler.error(new CSSParseException(e.getMessage(), l, e));
	    }
	}
    }

    private void reportWarningSkipText(Locator l, String text) {
	if (errorHandler != null && text  != null) {
	    errorHandler.warning(new CSSParseException("Skipping: " + text, l));
	}
    }
}

PARSER_END(Parser)

/*
 * The tokenizer 
 */

<DEFAULT>
TOKEN :
{
    < S : ( [ " ", "\t" , "\n" , "\r", "\f" ] )+ > 
	{ image = Parser.SPACE; }
}
   
<DEFAULT>
MORE :
{
  "//" : IN_SINGLE_LINE_COMMENT
}

<IN_SINGLE_LINE_COMMENT>
MORE :
{
    < ~["\n","\r"] > : IN_SINGLE_LINE_COMMENT
}

<IN_SINGLE_LINE_COMMENT>
SKIP :
{
    < "\n"|"\r"|"\r\n" > : DEFAULT
}

<DEFAULT>
MORE :
{
  <"/**" ~["/"]> { input_stream.backup(1); } : IN_FORMAL_COMMENT
|
  "/*" : IN_MULTI_LINE_COMMENT
}
<IN_FORMAL_COMMENT>
SPECIAL_TOKEN :
{
  <FORMAL_COMMENT: "*/" > : DEFAULT
}

<IN_MULTI_LINE_COMMENT>
SKIP :
{
  <MULTI_LINE_COMMENT: "*/" > : DEFAULT
}

<IN_FORMAL_COMMENT,IN_MULTI_LINE_COMMENT>
MORE :
{
  < ~[] >
}

<DEFAULT>
TOKEN :
{
  < CDO : "<!--" >
  | < CDC : "-->" >
  | < LBRACE : "{" >
  | < RBRACE : "}">
  | < DASHMATCH : "|=" >
  | < INCLUDES  : "~=" >
  | < EQ        : "=" >
  | < PLUS      : "+" >
  | < MINUS     : "-" >
  | < COMMA     : "," >
  | < SEMICOLON : ";" >
  | < PRECEDES  : ">" >
  | < SUCCEEDS : "<" >
  | < DIV       : "/" >
  | < LBRACKET  : "[" >
  | < RBRACKET  : "]" >
  | < ANY       : "*" >
  | < PARENT    : "&" >
  | < DOT       : "." >
  | < LPARAN    : "(" >
  | < RPARAN    : ")">
  | < COMPARE   : "==" >
  | < OR		: "||" >
  | < AND		: "&&" >
  | < NOT_EQ : "!=" >
}

<DEFAULT>
TOKEN :
{
  < COLON     : ":" >
}

< DEFAULT >
TOKEN :
{
	< EACH_VAR : "#{"< VARIABLE > "}">
}

<DEFAULT>
TOKEN : /* basic tokens */
{ 
    < NONASCII    : ["\200"-"\377"] >
  | < #H          : ["0"-"9", "a"-"f"] >
  | < #UNICODE    : "\\" <H> ( <H> )? /* I can't say {1,6} */
                         ( <H> )? ( <H> )?
                         ( <H> )? ( <H> )? 
                    ( [ " ", "\t" , "\n" , "\r", "\f" ] )? >
  | < #ESCAPE     : <UNICODE> | ( "\\" [ " "-"~","\200"-"\377" ] ) >
  | < #NMSTART    : ("-")?[ "a"-"z"] | <NONASCII> | <ESCAPE> >
  | < #NMCHAR	  : ["a"-"z", "0"-"9", "-", "_"] | <NONASCII> | <ESCAPE> >
  | < #STRINGCHAR : [ "\t"," ","!","#","$","%","&","("-"~" ] 
                    | "\\\n" | "\\\r\n" | "\\\r" | "\\\f" 
                    | <NONASCII> | <ESCAPE> >
  | < #D          : ["0"-"9"] >
  | < #NAME       : ( <NMCHAR> )+ >
}

<DEFAULT>
TOKEN :
{
    <TO         : "to">
    |<THROUGH   : "through">
    |<EACH_IN        : "in">
}

/* DERECTIVES */
<DEFAULT>
TOKEN :
{
    <MIXIN_SYM          : "@mixin">
  | <INCLUDE_SYM        : "@include">
  | <FUNCTION_SYM       : "@function">
  | <RETURN_SYM         : "@return">
  | <DEBUG_SYM          : "@debug">
  | <WARN_SYM           : "@warn">
  | <FOR_SYM            : "@for">
  | <EACH_SYM           : "@each">
  | <WHILE_SYM          : "@while">
  | <IF_SYM             : "@if">
  | <ELSE_SYM           : "@else">
  | <EXTEND_SYM         : "@extend">
  | <MOZ_DOCUMENT_SYM   : "@-moz-document">
  | <SUPPORTS_SYM       : "@supports">
}

< DEFAULT >
TOKEN:
{
  < IF : "if" > 
}

<DEFAULT>
TOKEN:
{
        < GUARDED_SYM : "!" ( <S> )? "default">
}

<DEFAULT>
TOKEN :
{
  < STRING      : ( "\"" ( <STRINGCHAR> | "'" )* "\"" ) | 
                  ( "'" ( <STRINGCHAR> | "\"" )* "'" ) >
  | < IDENT         : <NMSTART> ( <NMCHAR> )* >
  | < NUMBER	  : ( <D> )+ | ( <D> )* "." ( <D> )+ > 
  | < #_URL       : [ "!","#","$","%","&","*"-"~" ] | <NONASCII> | <ESCAPE> >
  | < URL         : "url(" ( <S> )*
                    ( <STRING> | ( <_URL> )* ) ( <S> )* ")" >            
}

<DEFAULT>
TOKEN:
{
	< VARIABLE : "$" <IDENT>>
}

<DEFAULT>
TOKEN :
{
   < PERCENTAGE : <NUMBER> "%" >
 | < PT : <NUMBER> "pt" >
 | < MM : <NUMBER> "mm" >
 | < CM : <NUMBER> "cm" >
 | < PC : <NUMBER> "pc" >
 | < IN : <NUMBER> "in" >
 | < PX : <NUMBER> "px" >
 | < EMS : <NUMBER> "em" >
 | < EXS : <NUMBER> "ex" >
 | < DEG : <NUMBER> "deg" >
 | < RAD : <NUMBER> "rad" >
 | < GRAD : <NUMBER> "grad" >
 | < MS : <NUMBER> "ms" >
 | < SECOND  : <NUMBER> "s" >
 | < HZ : <NUMBER> "Hz" >
 | < KHZ : <NUMBER> "kHz" >
 | < DIMEN  : <NUMBER> <IDENT> >
}

<DEFAULT>
TOKEN :
{
  < HASH : "#" <NAME> >
}

/* RESERVED ATRULE WORDS */
<DEFAULT>
TOKEN : 
{
    < IMPORT_SYM   : "@import">
  | < MEDIA_SYM    : "@media" >
  | < CHARSET_SYM  : "@charset" >
  | < PAGE_SYM     : "@page"  >
  | < FONT_FACE_SYM: "@font-face" >
  | < ATKEYWORD    : "@" <IDENT> >
}

<DEFAULT>
TOKEN :
{
 < IMPORTANT_SYM : "!" ( <S> )? "important" >
}

<DEFAULT>
TOKEN :
{
    < #RANGE0 : <H> <H> <H> <H>  <H> >
    | < #RANGE1 : <H> <H> <H> <H> <H> ( "?" )? >
    | < #RANGE2 : <H> <H> <H> <H> ( "?" )? ( "?" )? >
    | < #RANGE3 : <H> <H> <H> ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE4 : <H> <H> ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE5 : <H> ( "?" )? ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE6 : "?" ( "?" )? ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
    | < #RANGE  : <RANGE0> | <RANGE1> | <RANGE2> 
                  | <RANGE3> | <RANGE4> | <RANGE5> | <RANGE6> >
    | < #UNI    : <H> ( <H> )? ( <H> )? ( <H> )? ( <H> )? ( <H> )? >
    | < UNICODERANGE : "U+" <RANGE>
                        | "U+" <UNI> "-" <UNI> >
}

<DEFAULT>
TOKEN :
{
 < FUNCTION : <IDENT>(< S >)* "(" >
}

<DEFAULT, IN_MULTI_LINE_COMMENT>
TOKEN :
{ /* avoid token manager error */
    < UNKNOWN : ~[] >
}

/*
 * The grammar of CSS2
 */

/**
 * The main entry for the parser.
 *
 * @exception ParseException exception during the parse
 */
void parserUnit() :
{}
{
    try {
	{ documentHandler.startDocument(source); }
	( charset() )?
	( <S> comments()
	 | ignoreStatement() )*
	( importDeclaration() ( ignoreStatement() ( <S> )* )* )*
	afterImportDeclaration()
	<EOF>
    } finally {
	documentHandler.endDocument(source);
    }
}

void charset() :
{ Token n; }
{  
    try {
	<CHARSET_SYM> ( <S> )* n=<STRING> ( <S> )* ";"
    } catch (ParseException e) {
        reportError(getLocator(e.currentToken.next), e);
	skipStatement();
	// reportWarningSkipText(getLocator(), skipStatement());
    } catch (Exception e) {
        reportError(getLocator(), e);
	skipStatement();
	// reportWarningSkipText(getLocator(), skipStatement());
    }
}

void afterImportDeclaration() :
{String ret; 
 Locator l;
}
{
  ( 
     ( variable() | mixinDirective()|eachDirective() | includeDirective() | styleRule() | media()| page() | fontFace() 
      | { l = getLocator(); } ret=skipStatement() 
      {
	  if ((ret == null) || (ret.length() == 0)) {
	      return; 
	  }
	  reportWarningSkipText(l, ret);
	  if (ret.charAt(0) == '@') {
	      documentHandler.ignorableAtRule(ret);
	  }
      }
      )
    ( ignoreStatement() ( <S> )* )* )*
}

void ignoreStatement() :
{}
{
   <CDO> | <CDC> | atRuleDeclaration()
}

/**
 * The import statement
 *
 * @exception ParseException exception during the parse
 */
void importDeclaration() :
{Token n;
 String uri;
 MediaListImpl ml = new MediaListImpl();
 boolean isURL = false;
}
{
 try {
  <IMPORT_SYM> 
       ( <S> )* ( n=<STRING> { uri = convertStringIndex(n.image, 1, 
							n.image.length() -1); }
		  | n=<URL> 
	   {
	       isURL=true;	      
	       uri = n.image.substring(4, n.image.length()-1).trim();
	       if ((uri.charAt(0) == '"')
		   || (uri.charAt(0) == '\'')) {
		   uri = uri.substring(1, uri.length()-1);
	       }
	   }
		  )
       ( <S> )* ( mediaStatement(ml) )? ";"
       ( <S> )*
	   {
	       if (ml.getLength() == 0) {
		   // see section 6.3 of the CSS2 recommandation.
		   ml.addItem("all");
	       }
	       documentHandler.importStyle(uri, ml, isURL);
	   }
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 }
}

/**
 * @exception ParseException exception during the parse
 */
void media() :
{ 
    boolean start = false;
    String ret;
    MediaListImpl ml = new MediaListImpl(); 
}
{
 try {
  <MEDIA_SYM> ( <S> )*
   mediaStatement(ml)
   { start = true; documentHandler.startMedia(ml); }
   <LBRACE> ( <S> )* ( styleRule() | skipUnknownRule() )* <RBRACE> ( <S> )*
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 } finally {
     if (start) {
	 documentHandler.endMedia(ml);
     }
 }
}

void mediaStatement(MediaListImpl ml) :
{ 
    String m;
}
{
   m=medium() ( <COMMA> ( <S> )* { ml.addItem(m); } m=medium() )* 
   { ml.addItem(m); }
}

/**
 * @exception ParseException exception during the parse
 */
String medium() : /* tv, projection, screen, ... */
{Token n;}
{
    n=<IDENT> ( <S> )* { return convertIdent(n.image); }
}

/**
 * @exception ParseException exception during the parse
 */
void page() :
{
 boolean start = false;
 Token n = null;
 String page = null;
 String pseudo = null;
}
{
 try {
  <PAGE_SYM> ( <S> )* ( n=<IDENT> ( <S> )* )?
  ( pseudo=pseudo_page() )? 
      {
	  if (n != null) {
	      page = convertIdent(n.image);
	  }
      }
  <LBRACE> (<S>)* 
      {
	  start = true;
	  documentHandler.startPage(page, pseudo);
      }
     ( declaration() )? ( ";" ( <S> )* ( declaration() )? )* 
     <RBRACE> (<S>)*
 } catch (ParseException e) {
     if (errorHandler != null) {
	 LocatorImpl li = new LocatorImpl(this,
					  e.currentToken.next.beginLine,
					  e.currentToken.next.beginColumn-1);
	 reportError(li, e);
	 skipStatement();
	 // reportWarningSkipText(li, skipStatement());
     } else {
	 skipStatement();
     }
 } finally {
     if (start) {
	 documentHandler.endPage(page, pseudo);
     }
 }
}

String pseudo_page() :
{ Token n; }
{
    ":" n=<IDENT> ( <S> )* { return convertIdent(n.image); }
}

void fontFace() :
{
    boolean start = false;
}
{
 try {
  <FONT_FACE_SYM> ( <S> )* 
     <LBRACE> (<S>)* 
      { start = true; documentHandler.startFontFace(); }
     ( declaration() )? ( ";" ( <S> )* ( declaration() )? )* 
     <RBRACE> (<S>)*
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 } finally {
     if (start) {
	 documentHandler.endFontFace();
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
void atRuleDeclaration() :
{Token n;
 String ret; 
}
{
 n=<ATKEYWORD>
    {
	ret=skipStatement();
	reportWarningSkipText(getLocator(), ret);
	if ((ret != null) && (ret.charAt(0) == '@')) {
	    documentHandler.ignorableAtRule(ret);
	}
    }
}

void skipUnknownRule() :
{ Token n;}
{
 ( n=<ATKEYWORD>
| n=<CDO>
| n=<CHARSET_SYM>
| n=<COMMA>
| n=<DASHMATCH>
| n=<FONT_FACE_SYM>
| n=<FUNCTION>
| n=<IMPORTANT_SYM>
| n=<IMPORT_SYM>
| n=<INCLUDES>
| n=<LBRACE>
| n=<MEDIA_SYM>
| n=<NONASCII>
| n=<NUMBER>
| n=<PAGE_SYM>
| n=<PERCENTAGE>
| n=<STRING>
| n=<UNICODERANGE>
| n=<URL>
| n=";"
| n="+"
| n=">"
| n="-"
| n=<UNKNOWN>
   ) {
     String ret;
     Locator loc = getLocator();
     ret=skipStatement();
     reportWarningSkipText(loc, ret);
     if ((ret != null) && (n.image.charAt(0) == '@')) {
	 documentHandler.ignorableAtRule(ret);
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
char combinator() :
{
char connector = ' ';
}
{
    "+" ( <S> )* { return '+'; }
    | ">" ( <S> )* { return '>'; }
| <S>  ( ( "+" { connector = '+'; } 
           | ">" { connector = '>'; } )
 ( <S> )* )? { return connector; }
}

/**
 * @exception ParseException exception during the parse
 */
String property() :
{Token n;
}
{
	    n=<IDENT> ( <S> )* { return convertIdent(n.image); }
}

String variableName() :
{Token n;}
{
    n=<VARIABLE> (<S>)* {return convertIdent(n.image.substring(1));}
}

String functionName() :
{Token n;}
{
    n=<FUNCTION> ( <S> )* {return convertIdent(n.image.substring(0, n.image.length()-1));}
}
/**
 * @exception ParseException exception during the parse
 */
void styleRule() :
{
    boolean start = false;
    SelectorList l = null;
    Token save;
    Locator loc;
}
{
 try {
     l=selectorList() { save = token; } <LBRACE> (<S>)*
     {
	 start = true;
	 documentHandler.startSelector(l);
     }
     ( ifDirective() | includeDirective() | media() | extendDirective()| eachDirective() | variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())*
     <RBRACE> (<S>)*
 } catch (ThrowedParseException e) {
     if (errorHandler != null) {
	 LocatorImpl li = new LocatorImpl(this,
					  e.e.currentToken.next.beginLine,
					  e.e.currentToken.next.beginColumn-1);
	 reportError(li, e.e);
     }
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 } catch (TokenMgrError e) {
     reportWarningSkipText(getLocator(), skipStatement());
 } finally {
     if (start) {
	 documentHandler.endSelector(l);
     }
 }
}

SelectorList selectorList() :
{
    SelectorListImpl selectors = new SelectorListImpl();
    Selector selector;
}
{
 selector=selector() ( <COMMA> (<S>)* { selectors.addSelector(selector); } 
                       selector=selector() )* 
  { selectors.addSelector(selector); 
    return selectors;
  }
}

/**
 * @exception ParseException exception during the parse
 */
Selector selector() :
{   
    Selector selector;
    char    comb;
}
{
 try {
   selector=simple_selector(null, ' ') 
       ( LOOKAHEAD(2) comb=combinator() 
	 selector=simple_selector(selector, comb) )* (<S>)*
   {
       return selector; 
   }
 } catch (ParseException e) {
     /*
     Token t = getToken(1);
     StringBuffer s = new StringBuffer();
     s.append(getToken(0).image);
     while ((t.kind != COMMA) && (t.kind != SEMICOLON) 
	    && (t.kind != LBRACE) && (t.kind != EOF)) {
	 s.append(t.image);
	 getNextToken();
	 t = getToken(1);
     }
     reportWarningSkipText(getLocator(), s.toString());
     */
     Token t = getToken(1);
     while ((t.kind != COMMA) && (t.kind != SEMICOLON) 
	    && (t.kind != LBRACE) && (t.kind != EOF)) {
	 getNextToken();
	 t = getToken(1);
     }

     throw new ThrowedParseException(e);
  }
}

/**
 * @exception ParseException exception during the parse
 */
Selector simple_selector(Selector selector, char comb) :
{
    SimpleSelector simple_current = null;
    Condition cond = null;

    pseudoElt = null;
}
{
    ( simple_current=element_name() 
	( cond=hash(cond) | cond=_class(cond) 
	  | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=hash(cond) ( cond=_class(cond) 
			  | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=_class(cond) ( cond=hash(cond) | cond=_class(cond) 
		       | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=pseudo(cond) ( cond=hash(cond) | cond=_class(cond) 
		       | cond=attrib(cond) | cond=pseudo(cond) )*
      | cond=attrib(cond) ( cond=hash(cond) | cond=_class(cond) 
			    | cond=attrib(cond) | cond=pseudo(cond) )*
      )
	{
	    if (simple_current == null) {
		simple_current = selectorFactory.createElementSelector(null, null);
	    }
	    if (cond != null) {
		simple_current = selectorFactory.createConditionalSelector(simple_current, 
								    cond);
	    }
	    if (selector != null) {
		switch (comb) {
		case ' ':
		    selector = selectorFactory.createDescendantSelector(selector, 
								       simple_current);
		    break;
		case '+':
		    selector = 
			selectorFactory.createDirectAdjacentSelector((short) 1,
								     selector, 
								     simple_current);
		    break;
		case '>':
		    selector = selectorFactory.createChildSelector(selector, 
								  simple_current);
		    break;
		default:
		    throw new ParseException("invalid state. send a bug report");
		}
	    } else {
		selector= simple_current;
	    }
	    if (pseudoElt != null) {
		selector = selectorFactory.createChildSelector(selector, 
                             selectorFactory.createPseudoElementSelector(null, pseudoElt));
	    }
	    return selector;
	}
}

/**
 * @exception ParseException exception during the parse
 */
Condition _class(Condition pred) :
{Token t;
String s = "";
Condition c;
}
{
  "." (t = <IDENT>{s += t.image; }|t = < EACH_VAR >{ s += t.image; })+
      { 
	  c = conditionFactory.createClassCondition(null, s);
	  if (pred == null) {
	      return c;
	  } else {
	      return conditionFactory.createAndCondition(pred, c);
	  }
      }
}

/**
 * @exception ParseException exception during the parse
 */
SimpleSelector element_name() :
{Token t; String s = "";}
{
    (t = <IDENT>{s += t.image; }|t = < EACH_VAR >{ s += t.image; })+
    { 
      return selectorFactory.createElementSelector(null, s); 
    }
      | "*"
    { return selectorFactory.createElementSelector(null, "*"); }
      | "&"
    { return selectorFactory.createElementSelector(null, "&"); }
}

/**
 * @exception ParseException exception during the parse
 */
Condition attrib(Condition pred) :
{
    int cases = 0;
    Token att = null;
    Token val = null;
    String attValue = null;
}
{
  "[" ( <S> )* att=<IDENT> ( <S> )*
      ( ( "="           { cases = 1; }
	  | <INCLUDES>  { cases = 2; }	  
	  | <DASHMATCH> { cases = 3; } ) ( <S> )* 
	( val=<IDENT> { attValue = val.image; }
	  | val=<STRING> { attValue = convertStringIndex(val.image, 1, 
							 val.image.length() -1);}
	)
	( <S> )* )?
  "]"
  {
      String name = convertIdent(att.image);
      Condition c;
      switch (cases) {
	  case 0:
	  c = conditionFactory.createAttributeCondition(name, null, false, null);
	  break;
	  case 1:
	  c = conditionFactory.createAttributeCondition(name, null, false, 
							attValue);
	  break;
	  case 2:
	  c = conditionFactory.createOneOfAttributeCondition(name, null, false, 
							     attValue);
	  break;
	  case 3:
	  c = conditionFactory.createBeginHyphenAttributeCondition(name, null, 
								   false, 
								   attValue);
	  break;
	  default:
	  // never reached.
	  c = null;
      }
      if (pred == null) {
	  return c;
      } else {
	  return conditionFactory.createAndCondition(pred, c);
      }
  }
}

/**
 * @exception ParseException exception during the parse
 */
Condition pseudo(Condition pred) :
{Token n;
Token language;
boolean isPseudoElement = false;
}
{
    ":" (":"{isPseudoElement=true;})?( n=<IDENT>
	{
	    String s = convertIdent(n.image);
	    if (isPseudoElement) {
		if (pseudoElt != null) {
		    throw new CSSParseException("duplicate pseudo element definition " 
						+ s, getLocator());
		} else {
		    pseudoElt = s;
		    return pred;
		}
	    } else {
		Condition c = 
		conditionFactory.createPseudoClassCondition(null, s);
		if (pred == null) {
		    return c;
		} else {
		    return conditionFactory.createAndCondition(pred, c);
		}
	    }
	}
	  | ( n=<FUNCTION> ( <S> )* language=<IDENT> ( <S> )* ")" 
	      {
		  String f = convertIdent(n.image);
		  if (f.equals("lang(")) {
		      Condition d = 
	      conditionFactory.createLangCondition(convertIdent(language.image));
		      if (pred == null) {
			  return d;
		      } else {
			  return conditionFactory.createAndCondition(pred, d);
		      }
		  } else {
		      throw new CSSParseException("invalid pseudo function name " 
						  + f, getLocator());
		  }
	      }
	      )
	  )
}

/**
 * @exception ParseException exception during the parse
 */
Condition hash(Condition pred) :
{Token n; }
{
  n=<HASH>
  {
      Condition d = 
	  conditionFactory.createIdCondition(n.image.substring(1));
      if (pred == null) {
	  return d;
      } else {
	  return conditionFactory.createAndCondition(pred, d);
      }
  }
}

void variable() :
{
	String name;
	LexicalUnit exp = null;
	boolean guarded = false;
	String raw;
}
{
	try{
		name = variableName()
		":" ( <S> )* exp=expr() ( guarded=guarded() )?(";"(<S>)*)+
		//raw=skipStatementUntilSemiColon()
		{
		    documentHandler.variable(name, exp, guarded);
		}
	}catch (JumpException e) {
	    skipAfterExpression();
	} catch (NumberFormatException e) {
	    if (errorHandler != null) {
		errorHandler.error(new CSSParseException("Invalid number " 
						  + e.getMessage(),
						  getLocator(), 
						  e));
	    }
	    reportWarningSkipText(getLocator(), skipAfterExpression());
	 } catch (ParseException e) {
	    if (errorHandler != null) {
	    	if (e.currentToken != null) {
	    		LocatorImpl li = new LocatorImpl(this,
						      e.currentToken.next.beginLine,
						      e.currentToken.next.beginColumn-1);
	    		reportError(li, e);
	    	} else {
	    		reportError(getLocator(), e);
	    }
		skipAfterExpression();
	    } else {
		skipAfterExpression();
	    }
	}
}

void ifDirective() :
{
  Token n = null;
  String evaluator = "";
}
{
  < IF_SYM >
  ( n = booleanExpressionToken() { evaluator += n.image; } )+
  < LBRACE >(< S >)*    
  { documentHandler.startIfElseDirective();
    documentHandler.ifDirective(evaluator);
   }
  ( includeDirective() | media() | extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())*	  
  < RBRACE >(< S >)*
  (elseDirective())*
  { documentHandler.endIfElseDirective(); }
}

void elseDirective() :
{
  String evaluator = "";
  Token n = null;
}
{
  < ELSE_SYM >(< S >)* 
  ( < IF > (n = booleanExpressionToken() { if(n != null) evaluator += n.image; })*)?
  < LBRACE >(< S >)*
  { if(!evaluator.trim().equals("")){ documentHandler.ifDirective(evaluator); }
    else{ documentHandler.elseDirective(); }  
  }
  ( includeDirective() | media() | extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())*  
  < RBRACE >(< S >)*
}

Token booleanExpressionToken() :
{
  Token n = null;
}
{
 (
   n = < VARIABLE >
   |n = < IDENT >
   |n = < NUMBER >
   |n = < LPARAN >
   |n = < RPARAN >
   |n = < PLUS >
   |n = < MINUS >
   |n = < DIV >
   |n = < ANY >
   |n = < COMPARE >
   |n = < EQ >
   |n = < PRECEDES >
   |n = < SUCCEEDS >
   |n = < OR >
   |n = < AND >
   |n = < S >
   |n = < NOT_EQ >
){
   return n;
 }
}
  
void eachDirective() :
{
  Token var;
  ArrayList<String> list;
}
{
  < EACH_SYM >
  (< S >)*
  var = < VARIABLE > (< S >)* < EACH_IN > (< S >)*
  list = stringList()
  < LBRACE >(< S >)*
  { documentHandler.startEachDirective(var.image, list);}
  ( includeDirective() | media() | extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())*	  
  < RBRACE >(< S >)*
  { documentHandler.endEachDirective();}
}

ArrayList<String > stringList():
{
	ArrayList<String > strings = new ArrayList<String >();
	Token input;	
}
{
	(input = < IDENT > (< S >)*) 
	{ strings.add(input.image); }
	
	(< COMMA >(< S >)* input = < IDENT > { strings.add(input.image); } (< S >)*)*
	{ return strings; }
	  
}

void mixinDirective() :
{
    String name;
    ArrayList<VariableNode> args = null;
    String body;
}
{
    <MIXIN_SYM>
    (<S>)*
    (name = property()
    |(name = functionName() 
      args = arglist()) <RPARAN> (<S>)*) <LBRACE> (<S>)*
    {documentHandler.startMixinDirective(name, args);}
    ( includeDirective() | media()| extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())*
    //(includeDirective() | media() | LOOKAHEAD(declaration()) declaration()";"(<S>)* | styleRule())*
    <RBRACE>(<S>)*
    {documentHandler.endMixinDirective(name, args);}
}

ArrayList<VariableNode> arglist() :
{
    ArrayList<VariableNode> args = new ArrayList<VariableNode>();
    VariableNode arg;
}
{
    arg=mixinArg() ( <COMMA> (<S>)* { args.add(arg); } 
        arg=mixinArg() )* 
    { args.add(arg); 
        return args;
    }
}

VariableNode mixinArg() :
{
    String name;
    LexicalUnit value = null;
}
{
    name=variableName() (":" (<S>)* value=term(null) )?
    {
        VariableNode arg = new VariableNode(name, value, false);
        return arg;
    }
}

ArrayList<LexicalUnit> argValuelist() :
{
    ArrayList<LexicalUnit> args = new ArrayList<LexicalUnit>();
    LexicalUnit argValue;
}
{
    argValue= term(null) { args.add(argValue); }
    ( <COMMA> (<S>)* argValue = term(null)
      {args.add(argValue);}
    )* 
    {return args;}
}

void includeDirective() :
{
    String name;
    ArrayList<LexicalUnit> args=null;
}
{
    <INCLUDE_SYM>
    (<S>)*
    (name = property()
         |(name = functionName() 
           args = argValuelist()) <RPARAN>)(";"(<S>)*)+
           {documentHandler.includeDirective(name, args);}
}

Node functionDirective() :
{
    String name;
    String args = null;
    String body;
    int[] stops = {RPARAN};
}
{
    (name=functionName() args = skipStatementUntilRightParan() <RPARAN>)
    (<S>)*
    body = skipStatement()   
    {
        return null;
    }
}

Node returnDirective() :
{
    String raw;
}
{
	raw = skipStatement()
	{return null;}
}

JAVACODE
void debugDirective(){
}

JAVACODE
void warnDirective(){
}

Node forDirective() :
{
    String var;
    String from;
    String to;
    boolean exclusive;
    String body;
    Token tok;
}
{
    var = variableName()
    {
        int[] toThrough = {TO, THROUGH};
        from = skipStatementUntil(toThrough);
    }
    (tok = <TO> {exclusive = true;}
    | tok = <THROUGH> {exclusive = false;})
    to = skipStatementUntilLeftBrace()
    (<S>)*
    body = skipStatement()
    {return documentHandler.forDirective(var, from, to, exclusive, body);}
}

Node whileDirective() :
{
    String condition;
    String body;
}
{
    condition = skipStatementUntilLeftBrace()
    body = skipStatement()
    {   return documentHandler.whileDirective(condition, body);}
}

void extendDirective() : 
{SelectorList list;}
{
    <EXTEND_SYM>
    (<S>)*
    list = selectorList()
    (";"(<S>)*)+
    {documentHandler.extendDirective(list);}
}

JAVACODE
Node importDirective(){
    return null;
}

JAVACODE
Node charsetDirective(){
    return null;
}

JAVACODE
Node mozDocumentDirective(){
    return null;
}

JAVACODE
Node supportsDirective(){
    return null;
}


void nestedProperties():
{String name;
LexicalUnit exp;}
{
    name=property()
    ":" ( <S> )* 
    <LBRACE> (<S>)*
    {
      documentHandler.startNestedProperties(name);
    }
    ( declaration() )? ( ";" ( <S> )* ( declaration() )? )*
    <RBRACE>
    {
      documentHandler.endNestedProperties(name);
    }
    (<S>)*
}
/**
 * @exception ParseException exception during the parse
 */
void declarationOrNestedProperties() :
{ boolean important = false;
  String name;
  LexicalUnit exp;
  Token save;
  String comment = null;
}
{
 try {
     name=property()
     { save = token; }
     ":" ( <S> )* 
     (exp=expr() ( important=prio() )?
     {
         Token next = getToken(1);
         if(next.kind == SEMICOLON || next.kind == RBRACE){
             while(next.kind == SEMICOLON){
                 skipStatement();
                 next = getToken(1);
             }
             if(token.specialToken!=null){
                 documentHandler.property(name, exp, important, token.specialToken.image);
             }else{
                 documentHandler.property(name, exp, important, null);
             }
         }
     }
     |<LBRACE> (<S>)*
     {
         documentHandler.startNestedProperties(name);
     }
     ( declaration() )? ( ";" ( <S> )* ( declaration() )? )*
     <RBRACE>(<S>)*
     {
         documentHandler.endNestedProperties(name);
     }
     )
     
 } catch (JumpException e) {
     skipAfterExpression();
     // reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (NumberFormatException e) {
     if (errorHandler != null) {
         errorHandler.error(new CSSParseException("Invalid number " 
                                                  + e.getMessage(),
                                                  getLocator(), 
                                                  e));
     }
     reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (ParseException e) {
     if (errorHandler != null) {
         if (e.currentToken != null) {
             LocatorImpl li = new LocatorImpl(this,
                                              e.currentToken.next.beginLine,
                                              e.currentToken.next.beginColumn-1);
             reportError(li, e);
         } else {
             reportError(getLocator(), e);
         }
         skipAfterExpression();
         /*
         LocatorImpl loc = (LocatorImpl) getLocator();
         loc.column--;
         reportWarningSkipText(loc, skipAfterExpression());
         */
     } else {
         skipAfterExpression();
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
void declaration() :
{ boolean important = false;
  String name;
  LexicalUnit exp;
  Token save;
}
{
 try {
     name=property()
     { save = token; }
     ":" ( <S> )* exp=expr() ( important=prio() )?
     {
	 documentHandler.property(name, exp, important);
     }
 } catch (JumpException e) {
     skipAfterExpression();
     // reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (NumberFormatException e) {
     if (errorHandler != null) {
	 errorHandler.error(new CSSParseException("Invalid number " 
						  + e.getMessage(),
						  getLocator(), 
						  e));
     }
     reportWarningSkipText(getLocator(), skipAfterExpression());
 } catch (ParseException e) {
     if (errorHandler != null) {
	 if (e.currentToken != null) {
	     LocatorImpl li = new LocatorImpl(this,
					      e.currentToken.next.beginLine,
					      e.currentToken.next.beginColumn-1);
	     reportError(li, e);
	 } else {
	     reportError(getLocator(), e);
	 }
	 skipAfterExpression();
	 /*
	 LocatorImpl loc = (LocatorImpl) getLocator();
	 loc.column--;
	 reportWarningSkipText(loc, skipAfterExpression());
	 */
     } else {
	 skipAfterExpression();
     }
 }
}

/**
 * @exception ParseException exception during the parse
 */
boolean prio() :
{}
{
  <IMPORTANT_SYM> ( <S> )* { return true; }
}

boolean guarded() :
{}
{
    <GUARDED_SYM> (<S>)* {return true;}
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl operator(LexicalUnitImpl prev) :
{Token n;}
{
n="/" ( <S> )* { return LexicalUnitImpl.createSlash(n.beginLine, 
						       n.beginColumn,
						       prev); }
| n="," ( <S> )* { return LexicalUnitImpl.createComma(n.beginLine, 
							 n.beginColumn,
							 prev); }
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnit expr() :
{
    LexicalUnitImpl first, res;
    char op;
}
{
    first=term(null){ res = first; }
    ( LOOKAHEAD(2) ( res=operator(res) )? res=term(res))*
  { return first; }
}

/**
 * @exception ParseException exception during the parse
 */
char unaryOperator() :
{}
{
  "-" { return '-'; }
| "+" { return '+'; }
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl term(LexicalUnitImpl prev) :
{ LexicalUnitImpl result = null;
  Token n = null;
  char op = ' ';
  String varName;
}
{
  (( ( ( op=unaryOperator() )?
    (
      n=<NUMBER>
	{ result = LexicalUnitImpl.createNumber(n.beginLine, n.beginColumn,
						prev, number(op, n, 0)); }
    | n=<PERCENTAGE>
	{ result = LexicalUnitImpl.createPercentage(n.beginLine, n.beginColumn,
						    prev, number(op, n, 1)); }
    | n=<PT>
	{ result = LexicalUnitImpl.createPT(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<CM>
	{ result = LexicalUnitImpl.createCM(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<MM>
	{ result = LexicalUnitImpl.createMM(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<PC>
	{ result = LexicalUnitImpl.createPC(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<IN>
	{ result = LexicalUnitImpl.createIN(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<PX>
	{ result = LexicalUnitImpl.createPX(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<EMS>
	{ result = LexicalUnitImpl.createEMS(n.beginLine, n.beginColumn,
					     prev, number(op, n, 2)); }
    | n=<EXS>
	{ result = LexicalUnitImpl.createEXS(n.beginLine, n.beginColumn,
					     prev, number(op, n, 2)); }
    | n=<DEG>
	{ result = LexicalUnitImpl.createDEG(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=<RAD>
	{ result = LexicalUnitImpl.createRAD(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=<GRAD>
	{ result = LexicalUnitImpl.createGRAD(n.beginLine, n.beginColumn,
					      prev, number(op, n, 3)); }
    | n=<SECOND>
	{ result = LexicalUnitImpl.createS(n.beginLine, n.beginColumn,
					   prev, number(op, n, 1)); }
    | n=<MS>
	{ result = LexicalUnitImpl.createMS(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<HZ>
	{ result = LexicalUnitImpl.createHZ(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=<KHZ>
	{ result = LexicalUnitImpl.createKHZ(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=<DIMEN>
	{
	    String s = n.image;
	    int i = 0;
	    while (i < s.length() 
		   && (Character.isDigit(s.charAt(i)) || (s.charAt(i) == '.'))) {
		i++;
	    }
	    result = LexicalUnitImpl.createDimen(n.beginLine, n.beginColumn, prev, 
						 Float.valueOf(s.substring(0, i)).floatValue(),
						 s.substring(i));
	}
    | result=function(op, prev) ) )
  | ( n=<STRING>
	{ result =
	  LexicalUnitImpl.createString(n.beginLine, n.beginColumn, prev,
				       convertStringIndex(n.image, 1, 
							  n.image.length() -1));}
       | n=<IDENT> 
      { String s = convertIdent(n.image);
	if ("inherit".equals(s)) {
	    result = LexicalUnitImpl.createInherit(n.beginLine, n.beginColumn,
					       prev);
	} else {
	    result = LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn,
					       prev, convertIdent(n.image));
	}

	  /* /
         Auto correction code used in the CSS Validator but must not
          be used by a conformant CSS2 parser.
	 * Common error :
	 * H1 {
	 *   color : black
	 *   background : white
	 * }
	 *
	Token t = getToken(1);
	Token semicolon = new Token();
	semicolon.kind = SEMICOLON;
	semicolon.image = ";";
	if (t.kind == COLON) {
	    // @@SEEME. (generate a warning?)
	    // @@SEEME if expression is a single ident, 
	       generate an error ?
	    rejectToken(semicolon);
	    
	    result = prev;
	}
	/ */
    }
	| result=hexcolor(prev)	
        | result=url(prev)
	| result=unicode(prev)
         ) ) ( <S> )*
        | varName = variableName()
         {result = LexicalUnitImpl.createVariable(token.beginLine, token.beginColumn,
                 prev, varName);})
      {
	  return result;
      }
}

/**
 * Handle all CSS2 functions.
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl function(char operator, LexicalUnitImpl prev) :
{Token n;
 LexicalUnit params = null;
}
{
    n=<FUNCTION> ( <S> )*  
    {
        String fname = convertIdent(n.image);
        if("alpha(".equals(fname)){
            String body = skipStatementUntilSemiColon();
            return LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn,
                 null, "alpha("+body);
        }
    }
    ( params=expr() )? ")"
    {
        if (operator != ' ') {
	    throw new CSSParseException("invalid operator before a function.", 
					getLocator());
	}
	String f = convertIdent(n.image);
        LexicalUnitImpl l = (LexicalUnitImpl) params;
	boolean loop = true;
	if ("rgb(".equals(f)) {
	    // this is a RGB declaration (e.g. rgb(255, 50%, 0) )
	    int i = 0;
	    while (loop && l != null && i < 5) {
		switch (i) {
		    case 0:
		    case 2:
		    case 4:
			if ((l.getLexicalUnitType() != LexicalUnit.SAC_INTEGER)
			    && (l.getLexicalUnitType() != LexicalUnit.SAC_PERCENTAGE)) {
			    loop = false;
			}
			break;
		    case 1:
		    case 3:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		if (loop) {
		    l = (LexicalUnitImpl) l.getNextLexicalUnit();
		    i ++;
		}
	    }
	    if ((i == 5) && loop && (l == null)) {
		return LexicalUnitImpl.createRGBColor(n.beginLine,
						      n.beginColumn,
						      prev, params);
	    } else {
		if (errorHandler != null) {
		    String errorText;
		    Locator loc;
		    if (i < 5) {
			if (params == null) {
			    loc = new LocatorImpl(this, n.beginLine,
						  n.beginColumn-1);
			    errorText = "not enough parameters.";
			} else if (l == null) {
			    loc = new LocatorImpl(this, n.beginLine,
						  n.beginColumn-1);
			    errorText = "not enough parameters: " 
				+ params.toString();
			} else {
			    loc = new LocatorImpl(this, l.getLineNumber(),
						  l.getColumnNumber());
			    errorText = "invalid parameter: "
				+ l.toString();
			}
		    } else {
			loc = new LocatorImpl(this, l.getLineNumber(),
					      l.getColumnNumber());
			errorText = "too many parameters: "
			    + l.toString();
		    }
		    errorHandler.error(new CSSParseException(errorText, loc));
		}

		throw new JumpException();
	    }
	} else if ("counter".equals(f)) {
	    int i = 0;
	    while (loop && l != null && i < 3) {
		switch (i) {
		    case 0:
		    case 2:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_IDENT) {
			    loop = false;
			}
			break;
		    case 1:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		l = (LexicalUnitImpl) l.getNextLexicalUnit();
		i ++;
	    }
	    if (((i == 1) || (i == 3)) && loop && (l == null)) {
		return LexicalUnitImpl.createCounter(n.beginLine, n.beginColumn,
						     prev, params);
	    }

	} else if ("counters(".equals(f)) {
	    
	    int i = 0;
	    while (loop && l != null && i < 5) {
		switch (i) {
		    case 0:
		    case 4:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_IDENT) {
			    loop = false;
			}
			break;
		    case 2:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_STRING_VALUE) {
			    loop = false;
			}
			break;
		    case 1:
		    case 3:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		l = (LexicalUnitImpl) l.getNextLexicalUnit();
		i ++;
	    }
	    if (((i == 3) || (i == 5)) && loop && (l == null)) {
		return LexicalUnitImpl.createCounters(n.beginLine, n.beginColumn,
						      prev, params);
	    }
	} else if ("attr(".equals(f)) {
	    if ((l != null)
		&& (l.getNextLexicalUnit() == null)
		&& (l.getLexicalUnitType() == LexicalUnit.SAC_IDENT)) {
                return LexicalUnitImpl.createAttr(l.getLineNumber(), 
						  l.getColumnNumber(),
						  prev, l.getStringValue());
	    }
	} else if ("rect(".equals(f)) {
	    int i = 0;
	    while (loop && l != null && i < 7) {
		switch (i) {
		    case 0:
		    case 2:
		    case 4:
		    case 6:
			switch (l.getLexicalUnitType()) {
			case LexicalUnit.SAC_INTEGER:
			    if (l.getIntegerValue() != 0) {
				loop = false;
			    }
			    break;
			case LexicalUnit.SAC_IDENT:
			    if (!l.getStringValue().equals("auto")) {
				loop = false;
			    }
			    break;
			case LexicalUnit.SAC_EM:
			case LexicalUnit.SAC_EX:
			case LexicalUnit.SAC_PIXEL:
			case LexicalUnit.SAC_CENTIMETER:
			case LexicalUnit.SAC_MILLIMETER:
			case LexicalUnit.SAC_INCH:
			case LexicalUnit.SAC_POINT:
			case LexicalUnit.SAC_PICA:
			    // nothing
			    break;
			default:
			    loop = false;
			}
			break;
		    case 1:
		    case 3:
		    case 5:
			if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) {
			    loop = false;
			}
			break;
		    default:
                        throw new ParseException("implementation error");
		}
		l = (LexicalUnitImpl) l.getNextLexicalUnit();
		i ++;
	    }
	    if ((i == 7) && loop && (l == null)) {
		return LexicalUnitImpl.createRect(n.beginLine, n.beginColumn,
						  prev, params);
	    }
	} 
	return LexicalUnitImpl.createFunction(n.beginLine, n.beginColumn, prev, 
					      f.substring(0, 
							f.length() -1),
					      params);
    }
}

LexicalUnitImpl unicode(LexicalUnitImpl prev) :
{ Token n;
}
{
  n=<UNICODERANGE>
 {
     LexicalUnitImpl params = null;
     String s = n.image.substring(2);
     int index = s.indexOf('-');
     if (index == -1) {
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, Integer.parseInt(s, 16));
     } else {
	 String s1 = s.substring(0, index);
	 String s2 = s.substring(index);

	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, Integer.parseInt(s1, 16));
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, Integer.parseInt(s2, 16));
     }

     return LexicalUnitImpl.createUnicodeRange(n.beginLine, n.beginColumn,
					       prev, params);
 }
}

LexicalUnitImpl url(LexicalUnitImpl prev) :
{ Token n;
}
{
  n=<URL>
 {
   String urlname = n.image.substring(4, n.image.length()-1).trim();
   if (urlname.charAt(0) == '"'
       || urlname.charAt(0) == '\'') {
       urlname = urlname.substring(1, urlname.length()-1);
   }
   return LexicalUnitImpl.createURL(n.beginLine, n.beginColumn, prev, urlname); 
 }
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl hexcolor(LexicalUnitImpl prev) :
{Token n; 
}
{
 n=<HASH>
 { 
     int r;
     LexicalUnitImpl first, params = null;
     String s = n.image.substring(1);
     
     if(s.length()!=3 && s.length()!=6) {
	 first = null;
	 throw new CSSParseException("invalid hexadecimal notation for RGB: " + s, 
				     getLocator());
     }
     return LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn,
             prev, n.image);
 }
}

JAVACODE
float number(char operator, Token n, int lengthUnit) {
    String image  = n.image;
    float f = 0;

    if (lengthUnit != 0) {
	image = image.substring(0, image.length() - lengthUnit);
    }
    f = Float.valueOf(image).floatValue();
    return (operator == '-')? -f: f;
}

JAVACODE
String skipStatementUntilSemiColon(){
    int[] semicolon = {SEMICOLON};
    return skipStatementUntil(semicolon);
}

JAVACODE
String skipStatementUntilLeftBrace(){
    int[] lBrace = {LBRACE};
    return skipStatementUntil(lBrace);
}

JAVACODE
String skipStatementUntilRightParan(){
    int[] rParan = {RPARAN};
    return skipStatementUntil(rParan);
}

JAVACODE
String skipStatementUntil(int[] symbols){
    StringBuffer s = new StringBuffer();
    boolean stop = false;
    Token tok;
    while(!stop){
        tok = getToken(1);
        if(tok.kind == EOF) {
            return null;
        }
        for(int sym : symbols){
            if(tok.kind == sym){
                stop = true;
                break;
            }
        }
        if(!stop){
            if (tok.image != null) {
                s.append(tok.image);
            }
            getNextToken();
        }
    }
    return s.toString().trim();
}


JAVACODE
String skipStatement() {
    StringBuffer s = new StringBuffer();
    Token tok = getToken(0);
    if (tok.image != null) {
        s.append(tok.image);    
    }
    while (true) {
        tok = getToken(1);
        if (tok.kind == EOF) {
            return null;
        } 
        s.append(tok.image);
        if (tok.kind == LBRACE) {
            getNextToken();
            s.append(skip_to_matching_brace());
            getNextToken();
            tok = getToken(1);
            break;
        } else if (tok.kind == RBRACE) {
            getNextToken();
            tok = getToken(1);
            break;
        } else if (tok.kind == SEMICOLON) {
            getNextToken();
            tok = getToken(1);
            break;
        }
        getNextToken();
    }
    
    // skip white space
    while (true) {
        if (tok.kind != S) {
            break;
        }
        tok = getNextToken();
        tok = getToken(1);
    }

    return s.toString().trim();
}

JAVACODE
String skip_to_matching_brace() {
    StringBuffer s = new StringBuffer();
    Token tok;
    int nesting = 1;
    while (true) {
        tok = getToken(1);
        if (tok.kind == EOF) {
            break;
        }
        s.append(tok.image);
        if (tok.kind == LBRACE) {
            nesting++;
        } else if (tok.kind == RBRACE) {
            nesting--;
            if (nesting == 0) {
                break;
            }
        }
        getNextToken();
    }
    return s.toString();
}

/*
 * Here I handle all CSS2 unicode character stuffs.
 * I convert all \XXXXXX character into a single character.
 * Don't forget that the parser has recognize the token before.
 * (So IDENT won't contain newline and stuffs like this).
 */
JAVACODE
String convertStringIndex(String s, int start, int len) {
    StringBuffer buf = new StringBuffer(len);
    int index = start;

    while (index < len) {
	char c = s.charAt(index);
	if (c == '\\') {
	    if (++index < len) {
		c = s.charAt(index);
		switch (c) {
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
		    int numValue = Character.digit(c, 16);
		    int count = 0;
		    int p = 16;

		    while (index + 1 < len && count < 6) {
			c = s.charAt(index+1);

			if (Character.digit(c, 16) != -1) {
			    numValue = (numValue * 16) + Character.digit(c, 16);
			    p *= 16;
			    index++;
			} else {
			    if (c == ' ') {
				// skip the latest white space
				index++;
			    }
			    break;
			}
		    }
		    buf.append((char) numValue);
		    break;
		case '\n':
		case '\f':
		    break;
		case '\r':
		    if (index + 1 < len) {
			if (s.charAt(index + 1) == '\n') {
			    index ++;
			}
		    }
		    break;
		default:
		    buf.append(c);
		}
	    } else {
		throw new CSSParseException("invalid string " + s, getLocator());
	    }
	} else {
	    buf.append(c);
	}
	index++;
    }

    return buf.toString();
}

JAVACODE
String convertIdent(String s) {
    return convertStringIndex(s, 0, s.length());
}

JAVACODE
String convertString(String s) {
    return convertStringIndex(s, 0, s.length());
}

JAVACODE
void comments(){
    if (token.specialToken != null){
        Token tmp_t = token.specialToken;
        while (tmp_t.specialToken != null) tmp_t = tmp_t.specialToken;
        while (tmp_t != null) {
            documentHandler.comment(tmp_t.image);
            tmp_t = tmp_t.next;
        }
    }
} 

/*
 * @@HACK
 * I can't insert a token into the tokens flow.
 * It's jj_consume_token implementation dependant! :-(
 */
JAVACODE
void rejectToken(Token t) {
    Token fakeToken = new Token();
    t.next = token;
    fakeToken.next = t;
    token = fakeToken;
}

/**
 * skip after an expression
 */
JAVACODE
String skipAfterExpression() {
    Token t = getToken(1);
    StringBuffer s = new StringBuffer();
    s.append(getToken(0).image);
    
    while ((t.kind != RBRACE) && (t.kind != SEMICOLON) && (t.kind != EOF)) {
	s.append(t.image);
	getNextToken();
	t = getToken(1);
    }
    
    return s.toString();
}

/**
 * The following functions are useful for a DOM CSS implementation only and are
 * not part of the general CSS2 parser.
 */

void _parseRule() :
{String ret = null;
}
{
	( <S> )*
	   ( importDeclaration() | styleRule() | media() | page() 
	     | fontFace() | ret=skipStatement() 
		{
		    if ((ret == null) || (ret.length() == 0)) {
			return; 
		    }
		    if (ret.charAt(0) == '@') {
			documentHandler.ignorableAtRule(ret);
		    } else {
			throw new CSSParseException("unrecognize rule: " + ret,
						    getLocator());
		    }
		}
	  )
}

void _parseImportRule() :
{
}
{
    ( <S> )* importDeclaration()
}

void _parseMediaRule() :
{
}
{
    ( <S> )* media()
}

void _parseDeclarationBlock() :
{
}
{
    ( <S> )* 
	( declaration() )? ( ";" ( <S> )* ( declaration() )? )* 
 }

SelectorList _parseSelectors() :
{ SelectorList p = null; 
}
{
    try {
	( <S> )* p = selectorList()
	{ return p; }
    } catch (ThrowedParseException e) {
	throw (ParseException) e.e.fillInStackTrace();
    }
}

/*
 * Local Variables:
 * compile-command: javacc Parser.jj & javac Parser.java
 * End:
 */
