/*
 * $Id: SACParserCSS21.jj,v 1.3 2008/03/25 16:43:09 sdanig Exp $
 *
 * CSS Parser Project
 *
 * Copyright (C) 1999-2008 David Schweinsberg.  All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * To contact the authors of the library:
 *
 * http://cssparser.sourceforge.net/
 * mailto:davidsch@users.sourceforge.net
 */

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

PARSER_BEGIN(SACParserCSS21)

package com.steadystate.css.parser;

import java.io.*;
import java.net.*;
import java.text.MessageFormat;
import java.util.*;
import org.w3c.css.sac.*;
import com.steadystate.css.parser.selectors.*;
import com.steadystate.css.sac.TestCSSParseException;

/**
 * @author <a href="mailto:davidsch@users.sourceforge.net">David Schweinsberg</a>
 * @version $Id: SACParserCSS21.jj,v 1.3 2008/03/25 16:43:09 sdanig Exp $
 */
public class SACParserCSS21 extends AbstractSACParser implements Parser {

    private boolean _quiet = true;

    public SACParserCSS21() {
        this((CharStream) null);
    }

    public String getParserVersion() {
        return "http://www.w3.org/TR/CSS21/";
    }
    
    protected String getGrammarUri()
    {
        return "http://www.w3.org/TR/CSS21/grammar.html";
    }

    protected Token getToken()
    {
        return this.token;
    }

}

PARSER_END(SACParserCSS21)

TOKEN_MGR_DECLS :
{
    private boolean _quiet = true;
    private ErrorHandler errorHandler;

	public String splitNumber(StringBuffer pattern) {
		String regexp = "[0-9]+|[0-9]*.[0-9]+";
		int j=1;
		while(pattern.substring(0, j).matches(regexp)) 
		{
			j++;
		}
		if(pattern.substring(j-1, j).equals(".")) 
		{
			j++;
			while(pattern.substring(0, j).matches(regexp)) 
			{
				j++;
			}
		}
		return pattern.substring(0, j-1);
	}
	
    private String trimBy(StringBuffer s, int left, int right) {
        int end = s.length();
        return s.toString().substring(left, end-right);
    }

    private String trimUrl(StringBuffer s) {
        StringBuffer s1 = new StringBuffer( trimBy(s, 4, 1).trim() );
        if (s1.length() == 0)
        {
            return s1.toString();
        }
        int end = s1.length() - 1;
        if ((s1.charAt(0) == '"' && s1.charAt(end) == '"')
            || (s1.charAt(0) == '\'' && s1.charAt(end) == '\''))
            return trimBy(s1, 1, 1);
        else
           return s1.toString();
    }
}

<DEFAULT> TOKEN :
{
    < S: ( " "|"\t"|"\r"|"\n"|"\f" )+ >
    | < W: ( <S> )? >
}

<DEFAULT> MORE :
{
  < "/*" > : COMMENT
}

<COMMENT> SKIP :
{
  < "*/" > : DEFAULT
}

<COMMENT> MORE :
{
  < ~[] > : COMMENT
}

<DEFAULT> TOKEN :
{
  < LBRACE: <W> "{" >
| < RBRACE: "}" >
| < COMMA: <W> "," >
| < DOT: "." >
| < SEMICOLON: ";" >
| < COLON: ":" >
| < ASTERISK: "*" >
| < SLASH: "/" >
| < PLUS: <W> "+" >
| < MINUS: "-" >
| < EQUALS: "=" >
| < LSQUARE: "[" >
| < RSQUARE: "]" >
| < GREATER: <W> ">" >
| < HASH: "#" <NAME> >
| < STRING: <STRING1> | <STRING2> > { matchedToken.image = trimBy(image, 1, 1); }
| < RROUND: ")" >
//| < INVALID: <INVALID1> | <INVALID2> >
| < #URL: ["!","#","$","%","&","*"-"[","]"-"~"] | <NONASCII> | <ESCAPE> >
| < URI: "url(" ( <S> )* ( <STRING> | ( <URL> )* ) ( <S> )* <RROUND> > { matchedToken.image = trimUrl(image); }
| < CDO: "<!--" >
| < CDC: "-->" >
| < INCLUDES: "~=" >
| < DASHMATCH: "|=" >
| < IMPORT_SYM: "@import" >
| < PAGE_SYM: "@page" >
| < MEDIA_SYM: "@media" >
| < CHARSET_SYM: "@charset " >
| < ATKEYWORD: "@" <IDENT> >
| < IMPORTANT_KEYWORD: "important" >
| < IMPORTANT_SYM: "!" >
| < INHERIT: "inherit" >
| < EMS: <NUM> <E_LETTER> <M_LETTER> > { matchedToken.image = splitNumber(image); }
| < EXS: <NUM> <E_LETTER> <X_LETTER> > { matchedToken.image = splitNumber(image); }
| < LENGTH_PX: <NUM> <P_LETTER> <X_LETTER> > { matchedToken.image = splitNumber(image); }
| < LENGTH_CM: <NUM> <C_LETTER> <M_LETTER> > { matchedToken.image = splitNumber(image); }
| < LENGTH_MM: <NUM> <M_LETTER> <M_LETTER> > { matchedToken.image = splitNumber(image); }
| < LENGTH_IN: <NUM> <I_LETTER> <N_LETTER> > { matchedToken.image = splitNumber(image); }
| < LENGTH_PT: <NUM> <P_LETTER> <T_LETTER> > { matchedToken.image = splitNumber(image); }
| < LENGTH_PC: <NUM> <P_LETTER> <C_LETTER> > { matchedToken.image = splitNumber(image); }
| < ANGLE_DEG: <NUM> <D_LETTER> <E_LETTER> <G_LETTER> > { matchedToken.image = splitNumber(image); }
| < ANGLE_RAD: <NUM> <R_LETTER> <A_LETTER> <D_LETTER> > { matchedToken.image = splitNumber(image); }
| < ANGLE_GRAD: <NUM> <G_LETTER> <R_LETTER> <A_LETTER> <D_LETTER> > { matchedToken.image = splitNumber(image); }
| < TIME_MS: <NUM> <M_LETTER> <S_LETTER> > { matchedToken.image = splitNumber(image); }
| < TIME_S: <NUM> <S_LETTER> > { matchedToken.image = splitNumber(image); }
| < FREQ_HZ: <NUM> <H_LETTER> <Z_LETTER> > { matchedToken.image = splitNumber(image); }
| < FREQ_KHZ: <NUM> <K_LETTER> <H_LETTER> <Z_LETTER> > { matchedToken.image = splitNumber(image); }
| < DIMENSION: <NUM> <IDENT> >
| < PERCENTAGE: <NUM> "%" > { matchedToken.image = splitNumber(image); }
| < NUMBER: <NUM> >
// handled by FUNCTION in CSS 2.1 | < RGB: "rgb(" >
| < FUNCTION: <IDENT> "(" >
| < IDENT: (<MINUS>)? <NMSTART> ( <NMCHAR> )* >
| < #NAME: ( <NMCHAR> )+ >
| < NUM: ( ["0"-"9"] )+ | ( ["0"-"9"] )* "." ( ["0"-"9"] )+ >
| < #Q16: "?" | "??" | "???" | "????" | "?????" | "??????" >
| < #Q15: "?" | "??" | "???" | "????" | "?????" >
| < #Q14: "?" | "??" | "???" | "????" >
| < #Q13: "?" | "??" | "???" >
| < #Q12: "?" | "??" >
| < #Q11: "?" >
| < #NMSTART: ["_","a"-"z"] | <NONASCII> | <ESCAPE> >
| < #NMCHAR: ["_","a"-"z","0"-"9","-"] | <NONASCII> | <ESCAPE> >
| < #STRING1: "\"" ( ~["\n","\r","\f","\""] | "\\" <NL> | <ESCAPE> )* "\"" >
| < #STRING2: "\'" ( ~["\n","\r","\f"] | "\\" <NL> | <ESCAPE> )* "\'" >
// TODO: check
//| < #INVALID1: "\"" ( ~["\n","\r","\f"] | "\\" <NL> | <ESCAPE> )* >
//| < #INVALID2: "\'" ( ~["\n","\r","\f"] | "\\" <NL> | <ESCAPE> )* >
| < #NONASCII: ["\u0080"-"\uFFFF"] >
| < #ESCAPE: <UNICODE> | ( "\\" ~["\r","\n","\f","0"-"9","a"-"f"] ) >
| < #NL: "\n" | "\r\n" | "\r" | "\f" >
| < #A_LETTER: "a" | "\\" ( "0" | "00" | "000" | "0000" ) ( "41" | "61" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? >
| < #C_LETTER: "c" | "\\" ( "0" | "00" | "000" | "0000" ) ( "43" | "63" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? >
| < #D_LETTER: "d" | "\\" ( "0" | "00" | "000" | "0000" ) ( "44" | "64" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? >
| < #E_LETTER: "e" | "\\" ( "0" | "00" | "000" | "0000" ) ( "45" | "65" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? >
| < #G_LETTER: "g" | "\\" ( "0" | "00" | "000" | "0000" ) ( "47" | "67" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "g" >
| < #H_LETTER: "h" | "\\" ( "0" | "00" | "000" | "0000" ) ( "48" | "68" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "h" >
| < #I_LETTER: "i" | "\\" ( "0" | "00" | "000" | "0000" ) ( "49" | "69" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "i" >
| < #K_LETTER: "k" | "\\" ( "0" | "00" | "000" | "0000" ) ( "4b" | "6b" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "k" >
| < #M_LETTER: "m" | "\\" ( "0" | "00" | "000" | "0000" ) ( "4d" | "6d" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "m" >
| < #N_LETTER: "n" | "\\" ( "0" | "00" | "000" | "0000" ) ( "4e" | "6e" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "n" >
| < #P_LETTER: "p" | "\\" ( "0" | "00" | "000" | "0000" ) ( "50" | "70" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "p" >
| < #R_LETTER: "r" | "\\" ( "0" | "00" | "000" | "0000" ) ( "52" | "72" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "r" >
| < #S_LETTER: "s" | "\\" ( "0" | "00" | "000" | "0000" ) ( "53" | "73" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "s" >
| < #T_LETTER: "t" | "\\" ( "0" | "00" | "000" | "0000" ) ( "54" | "74" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "t" >
| < #X_LETTER: "x" | "\\" ( "0" | "00" | "000" | "0000" ) ( "58" | "78" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "x" >
| < #Z_LETTER: "z" | "\\" ( "0" | "00" | "000" | "0000" ) ( "5a" | "7a" ) ( "\r\n" | [ " ", "\t", "\r", "\n", "\f"])? | "\\" "z" >
| < #UNICODE: "\\" <HNUM> ( "\r\n" | ( " " | "\t" | "\r" | "\n" | "\f" )? ) >
| < #HNUM: <H> | <H><H> | <H><H><H> | <H><H><H><H> | <H><H><H><H><H> | <H><H><H><H><H><H> >
| < #H: ["0"-"9","a"-"f"] >
}

<*> TOKEN:
{
    < UNKNOWN: ~[] >
    {
        if (!_quiet) {
            System.err.println("Illegal character : " + image.toString());
        }
    }
}

//
// stylesheet
//  : [ CHARSET_SYM STRING ';' ]?
//    [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
//    [ [ ruleset | media | page ] [S|CDO|CDC]* ]*
//  ;
//
void styleSheet() :
{
}
{
    try
    {
        { this.handleStartDocument(); }
        styleSheetRuleList()
        <EOF>
    }
    finally
    {
    	this.handleEndDocument();
    }
}

void styleSheetRuleList() :
{
}
{
    ( charsetRule() )?
    ( <S> | <CDO> | <CDC> )*
    ( importRule() ( <S> | <CDO> | <CDC> )* )*
    (
     ( styleRule() | mediaRule() | pageRule() )
     ( <S> | <CDO> | <CDC> )*
    )*
    (
     ( styleRule() | mediaRule() | pageRule() | importRuleIgnored() )
     ( <S> | <CDO> | <CDC> )*
    )*
}

//
// This is used by ASTStyleSheet.insertRule to parse a single rule
//
void styleSheetRuleSingle() :
{
}
{
  ( charsetRule() | importRule() | styleRule() | mediaRule() | pageRule() | unknownRule() )
}

void charsetRule() :
{
  Token t;
}
{
    try
    {
        <CHARSET_SYM> 
        t = <STRING> 
        <SEMICOLON>
        {
            this.handleCharset(t.toString());
        }
    }
    catch (ParseException e)
    {
        this.getErrorHandler().error(
            this.toCSSParseException("invalidCharsetRule", e));
    }
}

void unknownRule() :
{
    Token t;
    String s;
}
{
    try
    {
        t = <ATKEYWORD>
        {
            s = skip();
            this.handleIgnorableAtRule(s);
        }
    }
    catch (ParseException e)
    {
        this.getErrorHandler().error(
            this.toCSSParseException("invalidUnknownRule", e));
    }
}

//
// import
//  : IMPORT_SYM S*
//    [STRING|URI] S* [ medium [ COMMA S* medium]* ]? ';' S*
//  ;
//
void importRule() :
{
    Token t;
//    String s;
    SACMediaListImpl ml = new SACMediaListImpl();
}
{
    try
    {
        <IMPORT_SYM> ( <S> )*
        ( t = <STRING>
        | t = <URI> ) ( <S> )*
        ( mediaList(ml) )?
        <SEMICOLON>
        {
            this.handleImportStyle(unescape(t.image), ml, null);
        }
    }
    catch (CSSParseException e)
    {
        this.getErrorHandler().error(e);
        this.error_skipAtRule();
    }
    catch (ParseException e)
    {
        this.getErrorHandler().error(
            this.toCSSParseException("invalidImportRule", e));
        this.error_skipAtRule();
    }
}

void importRuleIgnored() :
{
    Token t;
    SACMediaListImpl ml = new SACMediaListImpl();
}
{
    { ParseException e = generateParseException(); }
    try
    {
        <IMPORT_SYM>
        ( <S> )*
        ( t = <STRING>
        | t = <URI> ) ( <S> )*
        ( mediaList(ml) )?
        <SEMICOLON>
    }
    finally
    {
        this.getErrorHandler().error(this.toCSSParseException("invalidImportRuleIgnored", e));
    }
}

//
// media
//  : MEDIA_SYM S* medium [ COMMA S* medium ]* LBRACE S* ruleset* '}' S*
//  ;
//
void mediaRule() :
{
    boolean start = false;
    SACMediaListImpl ml = new SACMediaListImpl();
}
{
    try
    {
        <MEDIA_SYM> ( <S> )*
        mediaList(ml)
        {
            start = true;
            this.handleStartMedia(ml);
        }
        <LBRACE> ( <S> )*
        ( mediaRuleList() )?
        <RBRACE>
    }
    catch (CSSParseException e)
    {
        this.getErrorHandler().error(e);
        this.error_skipblock();
    }
    catch(ParseException e)
    {
        CSSParseException cpe = this.toCSSParseException("invalidMediaRule", e);
        this.getErrorHandler().error(cpe);
        this.getErrorHandler().warning(this.createSkipWarning("ignoringRule", cpe));
        this.error_skipblock();
    }
    finally
    {
        if (start) {
            this.handleEndMedia(ml);
        }
    }
}

void mediaList(SACMediaListImpl ml) :
{
    String s;
}
{
    try
    {
        s = medium()
        ( <COMMA> ( <S> )* { ml.add(s); } s = medium() )*
        { ml.add(s); }
    }
    catch(ParseException e)
    {
        throw this.toCSSParseException("invalidMediaList", e);
    }
}

void mediaRuleList() :
{
}
{
  ( ( styleRule() | pageRule() | unknownRule() ) ( <S> )* )+ 
}

void mediaRuleSingle() :
{
}
{
  ( styleRule() | pageRule() | unknownRule() )
}

//
// medium
//   : IDENT S*
//   ;
//
String medium() :
{
  Token t;
}
{
  t = <IDENT> ( <S> )* { return t.image; }
}

//
// page
//  : PAGE_SYM S* pseudo_page? S*
//    LBRACE S* declaration [ ';' S* declaration ]* '}' S*
//  ;
//
void pageRule() :
{
    Token t = null;
    String s = null;
    boolean start = false;
}
{
    try {
        <PAGE_SYM> ( <S> )*

        ( LOOKAHEAD(2) ( s = pseudoPage() ( <S> )* ) )?

        <LBRACE> ( <S> )*
        {
            start = true;
            this.handleStartPage((t != null) ? unescape(t.image) : null, s);
        }

        ( declaration() )?
        ( <SEMICOLON> ( <S> )* ( declaration() )? )*
        <RBRACE>
    }
    catch(ParseException e)
    {
        throw this.toCSSParseException("invalidPageRule", e);
    }
    finally {
        if (start) {
            this.handleEndPage((t != null) ? unescape(t.image) : null, s);
        }
    }
}

//
// pseudoPage
//   : ':' IDENT
//   ;
//
String pseudoPage() :
{
  Token t;
}
{
  <COLON> t = <IDENT> { return t.image; }
}

//
// operator
//  : '/' S* | COMMA S* | /* empty */
//  ;
//
LexicalUnit operator(LexicalUnit prev) :
{
  Token t;
}
{
  t = <SLASH> ( <S> )*   { return new LexicalUnitImpl(prev, LexicalUnit.SAC_OPERATOR_SLASH); }
  | t = <COMMA> ( <S> )* { return new LexicalUnitImpl(prev, LexicalUnit.SAC_OPERATOR_COMMA); }
}

//
// combinator
//  : PLUS S*
//  | GREATER S*
//  | S
//  ;
//
char combinator() :
{
  char c = ' ';
}
{
  ( <PLUS> { c='+'; } ( <S> )*
  | <GREATER> { c='>'; } ( <S> )*
  | <S> ( ( <PLUS> { c='+'; } | <GREATER> { c='>'; } ) ( <S> )* )? )

  { return c; }
}

//
// unary_operator
//  : '-' | PLUS
//  ;
//
char unaryOperator() :
{
}
{
  ( <MINUS> { return '-'; } | <PLUS> { return '+'; } )
}

//
// property
//   : IDENT S*
//   ;
//
String property() :
{
    Token t;
}
{
    t = <IDENT> ( <S> )*
    { return unescape(t.image); }
}

//
// ruleset
//  : selector [ COMMA S* selector ]*
//    LBRACE S* declaration [ ';' S* declaration ]* '}' S*
//  ;
//
void styleRule() :
{
    SelectorList selList = null;
    boolean start = false;
}
{
    try {
        selList = selectorList()
        <LBRACE> ( <S> )*
        {
            start = true;
            this.handleStartSelector(selList);
        }
        styleDeclaration()
        /*
        ( declaration() )?
        ( <SEMICOLON> ( <S> )* ( declaration() )? )*
        */
        <RBRACE>
    }
    catch(CSSParseException e)
    {
        this.getErrorHandler().error(e);
        this.getErrorHandler().warning(this.createSkipWarning("ignoringRule", e));
        this.error_skipblock();
    }
    catch(ParseException e)
    {
        CSSParseException cpe = this.toCSSParseException("invalidStyleRule", e);
        this.getErrorHandler().error(cpe);
        this.getErrorHandler().warning(this.createSkipWarning("ignoringFollowingDeclarations", cpe));
        this.error_skipblock();
    }
    finally {
        if (start) {
            this.handleEndSelector(selList);
        }
    }
}

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

//
// selector
//   : simple_selector [ combinator simple_selector ]*
//   ;
//
Selector selector() :
{
    Selector sel;
    char comb;
}
{
    try {
        sel = simpleSelector(null, ' ')
        ( LOOKAHEAD(2) comb = combinator() sel = simpleSelector(sel, comb) )* ( <S> )*
        {
            return sel;
        }
    } catch (ParseException e) {
        throw this.toCSSParseException("invalidSelector", e);
        //skipSelector();
    }
}

//
// simple_selector
//  : element_name [ HASH | class | attrib | pseudo ]*
//  | [ HASH | class | attrib | pseudo ]+
//  ;
//
Selector simpleSelector(Selector sel, char comb) :
{
    SimpleSelector simpleSel = null;
    Condition c = null;
}
{
    try
    {
        (
            ( simpleSel = elementName()
                ( c = hash(c)
                | c = _class(c)
                | c = attrib(c)
                | c = pseudo(c)
                )*
            )
            |
            ( { simpleSel = this.getSelectorFactory().createElementSelector(null, null); }
                ( c = hash(c)
                | c = _class(c)
                | c = attrib(c)
                | c = pseudo(c)
                )+
            )
        )

        {
            if (c != null) {
                simpleSel = this.getSelectorFactory().createConditionalSelector(simpleSel, c);
            }

            if (sel != null) {
                switch (comb) {
                case ' ':
                    sel = this.getSelectorFactory().createDescendantSelector(sel, simpleSel);
                    break;
                case '+':
                    sel = this.getSelectorFactory().createDirectAdjacentSelector(sel.getSelectorType(), sel, simpleSel);
                    break;
                case '>':
                    sel = this.getSelectorFactory().createChildSelector(sel, simpleSel);
                    break;
                }
            } else {
                sel = simpleSel;
            }

            return sel;
        }
    }
    catch (ParseException e)
    {
        throw this.toCSSParseException("invalidSimpleSelector", e);
    }
}

//
// class
//   : '.' IDENT
//   ;
//
Condition _class(Condition pred) :
{
    Token t;
}
{
    try
    {
        <DOT> t = <IDENT>
        {
            Condition c = this.getConditionFactory().createClassCondition(null, t.image);
            return (pred == null) ? c : this.getConditionFactory().createAndCondition(pred, c);
        }
    }
    catch (ParseException e)
    {
        throw this.toCSSParseException("invalidClassSelector", e);
    }
}

//
// element_name
//   : IDENT | '*'
//   ;
//
SimpleSelector elementName() :
{
    Token t;
}
{
    try
    {
        t=<IDENT>
        { return this.getSelectorFactory().createElementSelector(null, unescape(t.image)); }
        | <ASTERISK>
        { return this.getSelectorFactory().createElementSelector(null, null); }
    }
    catch (ParseException e)
    {
        throw this.toCSSParseException("invalidElementName", e);
    }
}

//
// attrib
//   : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
//     [ IDENT | STRING ] S* ]? ']'
//   ;
//
Condition attrib(Condition pred) :
{
    Token t;
    String name = null;
    String value = null;
    int type = 0;
}
{
    try
    {
        <LSQUARE> ( <S> )*
        t = <IDENT> { name = unescape(t.image); } ( <S> )*
        (
            (
                <EQUALS> { type = 1; }
                |
                <INCLUDES> { type = 2; }
                |
                <DASHMATCH> { type = 3; }
            )
            ( <S> )*
            (
                t = <IDENT> { value = t.image; }
                |
                t = <STRING> { value = unescape(t.image); }
            )
            ( <S> )*
        )?
        <RSQUARE>

        {
            Condition c = null;
            switch (type) {
            case 0:
                c = this.getConditionFactory().createAttributeCondition(name, null, false, null);
                break;
            case 1:
                c = this.getConditionFactory().createAttributeCondition(name, null, false, value);
                break;
            case 2:
                c = this.getConditionFactory().createOneOfAttributeCondition(name, null, false, value);
                break;
            case 3:
                c = this.getConditionFactory().createBeginHyphenAttributeCondition(name, null, false, value);
                break;
            }
            return (pred == null) ? c : this.getConditionFactory().createAndCondition(pred, c);
        }
    }
    catch (ParseException e)
    {
        throw this.toCSSParseException("invalidAttrib", e);
    }
}

//
// pseudo
//  : ':' [ IDENT | FUNCTION S* IDENT? S* ')' ]
//  ;
//
Condition pseudo(Condition pred) :
{
    Condition c;
    Token t;
    String function;
    String arg = "";
}
{
    try
    {
        <COLON>
        (
            t = <IDENT>
            {
                // There appears to be an issue here regarding "first-letter" & "first-line"
                //String s = unescape(t.image);
                String s = t.image;
    		    c = this.getConditionFactory().createPseudoClassCondition(null, s);
                return (pred == null)
                    ? c
                    : this.getConditionFactory().createAndCondition(pred, c);
            }
            |
            (
                t = <FUNCTION> { function = unescape(t.image); } ( <S> )*
                (t = <IDENT> { arg = unescape(t.image); })? ( <S> )*
                <RROUND>
                {
                    if (function.equalsIgnoreCase("lang(")) {
                        c = this.getConditionFactory().createLangCondition(unescape(arg));
                        return (pred == null)
                            ? c
                            : this.getConditionFactory().createAndCondition(pred, c);
                    } else {
                    	c = this.getConditionFactory().createPseudoClassCondition(null, function + arg + ")");
						return (pred == null)
                            ? c
                            : this.getConditionFactory().createAndCondition(pred, c);
                    }
                    /*
                    else {
                        throw new TestCSSParseException(
                            "Invalid pseudo function name " + function,
                            getLocator(), this.getGrammarUri());
                    }
                    */
                }
            )
            
        )
    }
    catch (ParseException e)
    {
        throw this.toCSSParseException("invalidPseudo", e);
    }
}

Condition hash(Condition pred) :
{
    Token t;
}
{
    try
    {
        t = <HASH>
        {
            Condition c = this.getConditionFactory().createIdCondition(t.image.substring(1));
            return (pred == null) ? c : this.getConditionFactory().createAndCondition(pred, c);
        }
    }
    catch (ParseException e)
    {
        throw this.toCSSParseException("invalidHash", e);
    }
}

void styleDeclaration()  :
{
}
{
	//<LBRACE> ( <S> )*
	try
	{
        ( declaration() )?
        ( <SEMICOLON> ( <S> )* ( declaration() )? )*
    }
    catch (ParseException ex)
    {
        this.getErrorHandler().error(this.toCSSParseException("invalidDeclaration", ex));
        error_skipdecl();
    }
	//<RBRACE>
}

//
// declaration
//   : property ':' S* expr prio?
//   |
//   ;
//
void declaration() :
{
  String p;
  LexicalUnit e;
  boolean priority = false;
}
{
    try
    {
        p = property()
        <COLON> ( <S> )*
        e = expr()
        ( priority = prio() )?
        {
            this.handleProperty(p, e, priority);
        }
    }
    catch (CSSParseException ex)
    {
        this.getErrorHandler().error(ex);
        error_skipdecl();
    }
    catch (ParseException ex)
    {
        this.getErrorHandler().error(this.toCSSParseException("invalidDeclaration", ex));
        error_skipdecl();
    }
}

//
// prio
//   : IMPORTANT_SYM S*
//   ;
// change to <IMPORTANT_SYM> <W> <IMPORTANT_KEYWORD> ( <S> )*
boolean prio() :
{
}
{
  <IMPORTANT_SYM> ( <S> )* <IMPORTANT_KEYWORD> ( <S> )*
  { return true; }
}

//
// expr
//   : term [ operator term ]*
//   ;
//
// TODO: Deal with the operator
//
LexicalUnit expr() :
{
    LexicalUnit head;
    LexicalUnit body;
}
{
    try
    {
        head = term(null) { body = head; }
        ( ( body = operator(body) )? body = term(body) )*
        { return head; }
    }
    catch (ParseException ex)
    {
        throw this.toCSSParseException("invalidExpr", ex);
    }
}

//
// term
//  : unary_operator?
//    [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
//      TIME S* | FREQ S* ]
//  | STRING S* | IDENT S* | URI S* | hexcolor | function
//  ;
//
LexicalUnit term(LexicalUnit prev) :
{
  Token t;
  char op = ' ';
  String s;
  LexicalUnit value = null;
//  RGBColor color;
//  HexColor h;
//  CSSValueImpl value = null;
}
{
/*
  ( ( op = unaryOperator() )?
  ( ( t = <NUMBER>    { value = new CSSValueImpl( CSSPrimitiveValue.CSS_NUMBER, new Float( op + t.image ) ); }
    | t = <PERCENTAGE>  { value = new CSSValueImpl( CSSPrimitiveValue.CSS_PERCENTAGE, new Float( op + t.image ) ); }
    | t = <LENGTH_PX>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_PX, new Float( op + t.image ) ); }
    | t = <LENGTH_CM>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_CM, new Float( op + t.image ) ); }
    | t = <LENGTH_MM>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_MM, new Float( op + t.image ) ); }
    | t = <LENGTH_IN>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_IN, new Float( op + t.image ) ); }
    | t = <LENGTH_PT>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_PT, new Float( op + t.image ) ); }
    | t = <LENGTH_PC>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_PC, new Float( op + t.image ) ); }
    | t = <EMS>     { value = new CSSValueImpl( CSSPrimitiveValue.CSS_EMS, new Float( op + t.image ) ); }
    | t = <EXS>     { value = new CSSValueImpl( CSSPrimitiveValue.CSS_EXS, new Float( op + t.image ) ); }
    | t = <ANGLE_DEG>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_DEG, new Float( op + t.image ) ); }
    | t = <ANGLE_RAD>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_RAD, new Float( op + t.image ) ); }
    | t = <ANGLE_GRAD>  { value = new CSSValueImpl( CSSPrimitiveValue.CSS_GRAD, new Float( op + t.image ) ); }
    | t = <TIME_MS>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_MS, new Float( op + t.image ) ); }
    | t = <TIME_S>    { value = new CSSValueImpl( CSSPrimitiveValue.CSS_S, new Float( op + t.image ) ); }
    | t = <FREQ_HZ>   { value = new CSSValueImpl( CSSPrimitiveValue.CSS_HZ, new Float( op + t.image ) ); }
    | t = <FREQ_KHZ>    { value = new CSSValueImpl( CSSPrimitiveValue.CSS_KHZ, new Float( op + t.image ) ); })
  | t = <STRING>      { value = new CSSValueImpl( CSSPrimitiveValue.CSS_STRING, op + t.image ); }
  | t = <IDENT>     { value = new CSSValueImpl( CSSPrimitiveValue.CSS_IDENT, op + t.image ); }
  | t = <URI>       { value = new CSSValueImpl( CSSPrimitiveValue.CSS_URI, op + t.image ); }
  | color = hexcolor()  { value = new CSSValueImpl( CSSPrimitiveValue.CSS_RGBCOLOR, color ); }
  | s = function()    { value = new CSSValueImpl( CSSPrimitiveValue.CSS_IDENT, op + s ); }
  | t = <DIMENSION>     { value = new CSSValueImpl( CSSPrimitiveValue.CSS_DIMENSION, new Float( op + t.image ) ); }
  | t = <INHERIT>     { value = new CSSValueImpl( CSSPrimitiveValue.CSS_INHERIT, op + t.image ); } )
  ( <S> )* )
  { return value; }
*/
    ( op = unaryOperator() )?
    (
        ( t = <NUMBER>
        {
            try
            {
                value = LexicalUnitImpl.createNumber(prev, intValue(op, t.image));
            }
            catch (NumberFormatException e)
            {
                value = LexicalUnitImpl.createNumber(prev, floatValue(op, t.image));
            }
        }
        | t = <PERCENTAGE>  { value = LexicalUnitImpl.createPercentage(prev, floatValue(op, t.image)); }
        | t = <LENGTH_PX>   { value = LexicalUnitImpl.createPixel(prev, floatValue(op, t.image)); }
        | t = <LENGTH_CM>   { value = LexicalUnitImpl.createCentimeter(prev, floatValue(op, t.image)); }
        | t = <LENGTH_MM>   { value = LexicalUnitImpl.createMillimeter(prev, floatValue(op, t.image)); }
        | t = <LENGTH_IN>   { value = LexicalUnitImpl.createInch(prev, floatValue(op, t.image)); }
        | t = <LENGTH_PT>   { value = LexicalUnitImpl.createPoint(prev, floatValue(op, t.image)); }
        | t = <LENGTH_PC>   { value = LexicalUnitImpl.createPica(prev, floatValue(op, t.image)); }
        | t = <EMS>         { value = LexicalUnitImpl.createEm(prev, floatValue(op, t.image)); }
        | t = <EXS>         { value = LexicalUnitImpl.createEx(prev, floatValue(op, t.image)); }
        | t = <ANGLE_DEG>   { value = LexicalUnitImpl.createDegree(prev, floatValue(op, t.image)); }
        | t = <ANGLE_RAD>   { value = LexicalUnitImpl.createRadian(prev, floatValue(op, t.image)); }
        | t = <ANGLE_GRAD>  { value = LexicalUnitImpl.createGradian(prev, floatValue(op, t.image)); }
        | t = <TIME_MS>     { value = LexicalUnitImpl.createMillisecond(prev, floatValue(op, t.image)); }
        | t = <TIME_S>      { value = LexicalUnitImpl.createSecond(prev, floatValue(op, t.image)); }
        | t = <FREQ_HZ>     { value = LexicalUnitImpl.createHertz(prev, floatValue(op, t.image)); }
        | t = <FREQ_KHZ>    { value = LexicalUnitImpl.createKiloHertz(prev, floatValue(op, t.image)); }
        | value = function(prev)
        )
    | t = <STRING>          { value = new LexicalUnitImpl(prev, LexicalUnit.SAC_STRING_VALUE, unescape(t.image)); }
    | t = <IDENT>           { value = new LexicalUnitImpl(prev, LexicalUnit.SAC_IDENT, t.image); }
    | t = <URI>             { value = new LexicalUnitImpl(prev, LexicalUnit.SAC_URI, t.image); }
    | value = hexcolor(prev)
    | t = <DIMENSION>
    {
        int n = getLastNumPos(t.image);
        value = LexicalUnitImpl.createDimension(
            prev,
            floatValue(op, t.image.substring(0, n+1)),
            t.image.substring(n+1));
    }
    | t = <INHERIT>         { value = new LexicalUnitImpl(prev, LexicalUnit.SAC_INHERIT, t.image); }
    )
    ( <S> )*
    {
        if (value instanceof LexicalUnitImpl)
        {
            ((LexicalUnitImpl) value).setLocator(this.getLocator());
        }
        return value;
    }
}

//
// function
//   : FUNCTION S* expr ')' S*
//   ;
//
LexicalUnit function(LexicalUnit prev) :
{
    Token t;
    LexicalUnit params;
}
{
    t = <FUNCTION> ( <S> )*
    params = expr()
    <RROUND>
    {
        return this.functionInternal(prev, t, params);
    }
}

//
// rgb
//   : RGB S* expr ')' S*
//   ;
//
/*
LexicalUnit rgb(LexicalUnit prev) :
{
    Token t;
    LexicalUnit params;
}
{
    t = <RGB> ( <S> )*
    params = expr()
    <RROUND>
    {
        return LexicalUnitImpl.createRgbColor(prev, params);
    }
}
*/

//
// hexcolor
//   : HASH S*
//   ;
//
LexicalUnit hexcolor(LexicalUnit prev) :
{
    Token t;
}
{
    t = <HASH>
    {
        return this.hexcolorInternal(prev, t);
    }
}

JAVACODE
void skipSelector() {
    Token t = getToken(1);
    while (t.kind != COMMA && t.kind != SEMICOLON && t.kind != LBRACE && t.kind != EOF ) {
        getNextToken();
        t = getToken(1);
    }
}

JAVACODE
String skip() {
  StringBuffer sb = new StringBuffer();
  int nesting = 0;
  Token t = getToken(0);
  if (t.image != null) {
    sb.append(t.image);    
  }
  do {
    t = getNextToken();
    if (t.kind == EOF)
      break;
    sb.append(t.image);
    if (t.kind == LBRACE)
      nesting++;
    else if (t.kind == RBRACE)
      nesting--;
    else if (t.kind == SEMICOLON && nesting <= 0)
      break;
  }
  while ((t.kind != RBRACE) || (nesting > 0));

  return sb.toString();
}

JAVACODE
void error_skipblock()
{
    Token t;
    int nesting = 0;
    do
    {
        t = getNextToken();
        if (t.kind == LBRACE)
        {
            nesting++;
        }
        else if (t.kind == RBRACE)
        {
            nesting--;
        }
        else if (t.kind == EOF)
        {
            break;
        }
    }
    while ((t.kind != RBRACE) || (nesting > 0));
}

JAVACODE
void error_skipdecl()
{
    int nesting = 0;
    Token t = getToken(1);
    if (t.kind == LBRACE)
    {
        error_skipblock();
    }
    else
    {
        Token oldToken = t;
        while (t.kind != SEMICOLON && t.kind != RBRACE && t.kind != EOF )
        {
            oldToken = t;
            t = getNextToken();
        }
        if (t.kind == RBRACE)
        {
            token = oldToken;
        }
    }
}

JAVACODE
void error_skipAtRule()
{
    Token t = null;
    do
    {
        t = getNextToken();
    }
    while (t.kind != SEMICOLON);
}