/*
 * Copyright 2004-2005 The Trix Development Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
PARSER_BEGIN(CueryParser)

package org.trix.cuery.parser;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Locale;

import org.trix.cuery.filter.DenialFIlter;
import org.trix.cuery.filter.FirstNumeralFilter;
import org.trix.cuery.filter.FirstTypedNumeralFilter;
import org.trix.cuery.filter.IndirectAdjacentFilter;
import org.trix.cuery.filter.LastNumeralFilter;
import org.trix.cuery.filter.LastTypedNumeralFilter;
import org.trix.cuery.filter.PrefixMatchFilter;
import org.trix.cuery.filter.SubstringMatchFilter;
import org.trix.cuery.filter.SuffixMatchFilter;
import org.trix.cuery.sac.ConditionFactoryImpl;
import org.trix.cuery.sac.SACMediaListImpl;
import org.trix.cuery.sac.SelectorFactoryImpl;
import org.trix.cuery.sac.SelectorListImpl;
import org.trix.cuery.util.CSSUtil;
import org.trix.cuery.value.CSSAttrFunction;
import org.trix.cuery.value.CSSColor;
import org.trix.cuery.value.CSSCounterFunction;
import org.trix.cuery.value.CSSCountersFunction;
import org.trix.cuery.value.CSSFunction;
import org.trix.cuery.value.CSSIdent;
import org.trix.cuery.value.CSSNumber;
import org.trix.cuery.value.CSSOperator;
import org.trix.cuery.value.CSSPercentage;
import org.trix.cuery.value.CSSRectFunction;
import org.trix.cuery.value.CSSString;
import org.trix.cuery.value.CSSURI;
import org.trix.cuery.value.CSSUnicodeRange;
import org.trix.cuery.value.CSScmLength;
import org.trix.cuery.value.CSSdegAngle;
import org.trix.cuery.value.CSSemLength;
import org.trix.cuery.value.CSSexLength;
import org.trix.cuery.value.CSSgradAngle;
import org.trix.cuery.value.CSShzFrequency;
import org.trix.cuery.value.CSSinLength;
import org.trix.cuery.value.CSSkhzFrequency;
import org.trix.cuery.value.CSSmmLength;
import org.trix.cuery.value.CSSmsTime;
import org.trix.cuery.value.CSSpcLength;
import org.trix.cuery.value.CSSptLength;
import org.trix.cuery.value.CSSpxLength;
import org.trix.cuery.value.CSSradAngle;
import org.trix.cuery.value.CSSsTime;
import org.trix.cuery.value.Value;

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


/**
 * DOCUMENT.
 *
 * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
 * @version $ Id: CueryParser.java,v 1.16 2005/08/13 17:27:18 Teletha Exp $
 */
public class CueryParser implements Parser {

    /** The stylesheet source to parse. */
    private InputSource source;

    /** The document handler. */
    private DocumentHandler handler;

    /** The error handler. */
    private ErrorHandler errorHandler;

    /** The selector factory. */
    private SelectorFactory selectorFactory = new SelectorFactoryImpl();

    /** The condition factory. */
    private ConditionFactory conditionFactory = new ConditionFactoryImpl();

    /** The locale. */
    private Locale locale = Locale.getDefault();

    /**
     * Create CueryParser instance.
     */
    public CueryParser() {
        this(System.in);
    }

    /**
     * @see org.w3c.css.sac.Parser#getParserVersion()
     */
    public String getParserVersion() {
        return "http://www.w3.org/TR/REC-CSS2";
    }

    /**
     * @see org.w3c.css.sac.Parser#setLocale(java.util.Locale)
     */
    public void setLocale(Locale locale) throws CSSException {
        // check null
        if (locale == null) {
            return;
        }
        this.locale = locale;
    }

    /**
     * @see org.w3c.css.sac.Parser#setDocumentHandler(org.w3c.css.sac.DocumentHandler)
     */
    public void setDocumentHandler(DocumentHandler handler) {
        this.handler = handler;
    }

    /**
     * @see org.w3c.css.sac.Parser#setSelectorFactory(org.w3c.css.sac.SelectorFactory)
     */
    public void setSelectorFactory(SelectorFactory selectorFactory) {
        // check null
        if (selectorFactory == null) {
            return;
        }
        this.selectorFactory = selectorFactory;
    }

    /**
     * @see org.w3c.css.sac.Parser#setConditionFactory(org.w3c.css.sac.ConditionFactory)
     */
    public void setConditionFactory(ConditionFactory conditionFactory) {
        // check null
        if (conditionFactory == null) {
            return;
        }
        this.conditionFactory = conditionFactory;
    }

    /**
     * @see org.w3c.css.sac.Parser#setErrorHandler(org.w3c.css.sac.ErrorHandler)
     */
    public void setErrorHandler(ErrorHandler errorHandler) {
        // check null
        if (errorHandler == null) {
            return;
        }
        this.errorHandler = errorHandler;
    }

    /**
     * @see org.w3c.css.sac.Parser#parseStyleSheet(java.lang.String)
     */
    public void parseStyleSheet(String uri) throws CSSException, IOException {
        // check null
        if (uri == null || uri.length() == 0) {
            throw new IOException("Unable to parse a stylesheet because this uri is null.");
        }

        // create input stream
        InputSource source = null;

        try {
            source = CSSUtil.getSource(uri);
            
            // start parsing
            parseStyleSheet(source);
        } finally {
            if (source != null) {
                source.getCharacterStream().close();
            }
        }
    }

    /**
     * @see org.w3c.css.sac.Parser#parseStyleSheet(org.w3c.css.sac.InputSource)
     */
    public void parseStyleSheet(InputSource source) throws CSSException, IOException {
        initializeParser(source);

        try {
            handler.startDocument(source);
            styleSheet();
        } catch (ParseException e) {
            throw new CSSException(CSSException.SAC_SYNTAX_ERR, e.getMessage(), e);
        } finally {
            handler.endDocument(source);
        }
    }

    /**
     * @see org.w3c.css.sac.Parser#parseStyleDeclaration(org.w3c.css.sac.InputSource)
     */
    public void parseStyleDeclaration(InputSource source) throws CSSException, IOException {
        initializeParser(source);

        try {
            parseStyleDeclaration();
        } catch (ParseException e) {
            throw new CSSException(CSSException.SAC_SYNTAX_ERR, e.getMessage(), e);
        }
    }

    /**
     * @see org.w3c.css.sac.Parser#parseRule(org.w3c.css.sac.InputSource)
     */
    public void parseRule(InputSource source) throws CSSException, IOException {
        initializeParser(source);

        try {
            parseStyleRule();
        } catch (ParseException e) {
            throw new CSSException(CSSException.SAC_SYNTAX_ERR, e.getMessage(), e);
        }
    }

    /**
     * @see org.w3c.css.sac.Parser#parseSelectors(org.w3c.css.sac.InputSource)
     */
    public SelectorList parseSelectors(InputSource source) throws CSSException, IOException {
        initializeParser(source);

        try {
            return selectorList();
        } catch (ParseException e) {
            throw new CSSException(CSSException.SAC_SYNTAX_ERR, e.getMessage(), e);
        }
    }

    /**
     * @see org.w3c.css.sac.Parser#parsePropertyValue(org.w3c.css.sac.InputSource)
     */
    public LexicalUnit parsePropertyValue(InputSource source) throws CSSException, IOException {
        initializeParser(source);

        try {
            return expr();
        } catch (ParseException e) {
            throw new CSSException(CSSException.SAC_SYNTAX_ERR, e.getMessage(), e);
        }
    }

    /**
     * @see org.w3c.css.sac.Parser#parsePriority(org.w3c.css.sac.InputSource)
     */
    public boolean parsePriority(InputSource source) throws CSSException, IOException {
        initializeParser(source);

        try {
            return priority();
        } catch (ParseException e) {
            throw new CSSException(CSSException.SAC_SYNTAX_ERR, e.getMessage(), e);
        }
    }

    /**
     * Parse a selector expression.
     * 
     * @param expression A selector expression for css.
     * @return A parsed selector instance.
     * @throws CSSException If this expression is invalid.
     */
    public Selector parseSelector(String expression) throws CSSException {
        // cehck null
        if (expression == null || expression.length() == 0) {
            throw new CSSException("This selector expression is null.");
        }

        ReInit(new StringReader(expression));

        try {
            return selector();
        } catch (ParseException e) {
            throw new CSSException(CSSException.SAC_SYNTAX_ERR, e.getMessage(), e);
        }
    }

    /**
     * Initialize parser.
     * 
     * @param source A stylesheet source.
     */
    private void initializeParser(InputSource source) {
        this.source = source;
        Reader reader = source.getCharacterStream();

        if (reader == null) {
            ReInit(source.getByteStream(), source.getEncoding());
        } else {
            ReInit(reader);
        }
    }

    /**
     * Handle parse error message.
     * 
     * @param message A message.
     * @param cause A cause of this error.
     */
    private void handleError(String message, Exception cause) {
        // check null
        if (errorHandler == null) {
            return;
        }
        CSSParseException exception = new CSSParseException(message, source.getURI(), token.beginLine, token.beginColumn, cause);
        errorHandler.warning(exception);
    }
}
PARSER_END(CueryParser)



TOKEN_MGR_DECLS : {
    private String trimBy(StringBuffer buffer, int left, int right) {
        return buffer.toString().substring(left, buffer.length() - right);
    }

    private String trimUrl(StringBuffer buffer) {
        buffer = new StringBuffer(trimBy(buffer, 4, 1).trim());
        int end = buffer.length() - 1;

        if ((buffer.charAt(0) == '"' && buffer.charAt(end) == '"') || (buffer.charAt(0) == '\'' && buffer.charAt(end) == '\'')) {
            return trimBy(buffer, 1, 1);
        } else {
            return buffer.toString();
        }
    }
}


/*======================    Comment Context    ======================*/
<DEFAULT> MORE : {
    < "/*" > : COMMENT
}


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

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


/*======================    Special Tokens    ======================*/
<DEFAULT> TOKEN : {
    < LBRACE            	: "{" >
|   < RBRACE            : "}" >
|   < COMMA             	: "," >
|   < DOT                  	: "." >
|   < SEMICOLON    	: ";" >
|   < COLON             	: ":" >
|   < ASTERISK         	: "*" >
|   < SLASH             	: "/" >
|   < PLUS                 	: "+" >
|   < MINUS             	: "-" >
|   < EQUALS         	: "=" >
|   < GT                     	: ">" >
|   < LSQUARE        	: "[" >
|   < RSQUARE       	: "]" >
|   < LROUND         	: "(" >
|   < RROUND        	: ")" >
|   < TILDE              	: "~" >
}



/*======================    Reserved @Rule    ======================*/
<DEFAULT> TOKEN : {
    < ATBOTTOM                                  	: "@bottom" >
|   < ATCOUNTER                             	: "@counter" >
|   < ATLEFT                                        	: "@left" >
|   < ATRIGHT                                       	: "@right" >
|   < ATTOP                                         	: "@top" >
|   < CHARSET_SYM                           	: "@charset" >
|   < COLOR_PROFILE                     	: "@color-profile" >
|   < FONT_FACE_SYM                     	: "@font-face" >
|   < IMPORT_SYM                               	: "@import">
|   < MEDIA_SYM                                 	: "@media" >
|   < PAGE_SYM                                  	: "@page"  >
|   < PHONETIC_ALPHABET_SYM  	: "@phonetic-alphabet" >
|   < PREF_SYM                                  	: "@preference" >
|   < ATKEYWORD                             	: "@" <IDENT> >
}


/*======================    Basic Tokens    ======================*/
<DEFAULT> TOKEN : {
    < S								: ( [ " ", "\t" , "\n" , "\r", "\f" ] )+ >
|   < #DIGIT            		: [ "0"-"9" ] >
|   < #ESCAPE				: <UNICODE> | ( "\\" [ " "-"~","\200"-"\377" ] ) >
|   < #HEX						: [ "a"-"f" ] | <DIGIT> >
|   < #NAME             		: ( <NMCHAR> )+ >
|   < #NMCHAR				: [ "a"-"z", "_", "-" ] | <DIGIT> | <NONASCII> | <ESCAPE> >
|   < #NMSTART			: [ "a"-"z", "_" ] | <NONASCII> | <ESCAPE> >
|   < #NONASCII			: [ "\u0080"-"\uffff" ] >
|   < #STRINGCHAR	: [ "\t"," ","!","#","$","%","&","("-"~" ] | "\\\n" | "\\\r\n" | <NONASCII> | <ESCAPE> >
|   < #UNICODE			: "\\" <HEX> ( <HEX> )? ( <HEX> )? ( <HEX> )? ( <HEX> )? ( <HEX> )? ( [ " ", "\t" , "\n" , "\r", "\f" ] )? >
|   < #URI             			: [ "!","#","$","%","&","*"-"~" ] | <NONASCII> | <ESCAPE> >
}


/*======================    Complex Tokens    ======================*/
<DEFAULT> TOKEN : {
    < STRING      				: ( "\"" ( <STRINGCHAR> | "'" )* "\"" ) | ( "'" ( <STRINGCHAR> | "\"" )* "'" ) > { matchedToken.image = trimBy(image, 1, 1); }
|   < IDENT           				: <NMSTART> ( <NMCHAR> )* >
|   < NUMBER     				: ( ["+","-"] )? ( <DIGIT> )+ | ( ["+","-"] )? ( <DIGIT> )* "." ( <DIGIT> )+ >
|   < NUMERAL   				: ( ["+","-"] )? ( <DIGIT> )? "n" ( ["+","-"] (<DIGIT>)+ )? >
|   < URL               				: "url(" ( <S> )* ( <STRING> | ( <URI> )* ) ( <S> )* ")" > { matchedToken.image = trimUrl(image); }
}


/*======================    Unicode Tokens    ======================*/
<DEFAULT> TOKEN : {
    < UNICODERANGE  	: "U+" <RANGE> | "U+" <UNI> "-" <UNI> >
|   < #RANGE0               	: <HEX> <HEX> <HEX> <HEX>  <HEX> >
|   < #RANGE1                	: <HEX> <HEX> <HEX> <HEX> <HEX> ( "?" )? >
|   < #RANGE2                	: <HEX> <HEX> <HEX> <HEX> ( "?" )? ( "?" )? >
|   < #RANGE3                	: <HEX> <HEX> <HEX> ( "?" )? ( "?" )? ( "?" )? >
|   < #RANGE4                	: <HEX> <HEX> ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
|   < #RANGE5                 	: <HEX> ( "?" )? ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
|   < #RANGE6                 	: "?" ( "?" )? ( "?" )? ( "?" )? ( "?" )? ( "?" )? >
|   < #RANGE                  	: <RANGE0> | <RANGE1> | <RANGE2> | <RANGE3> | <RANGE4> | <RANGE5> | <RANGE6> >
|   < #UNI                         	: <HEX> ( <HEX> )? ( <HEX> )? ( <HEX> )? ( <HEX> )? ( <HEX> )? >
}


/*======================    Unit Tokens    ======================*/
<DEFAULT> TOKEN : {
    < ANGLE_DEG       					: <NUMBER> "deg" >    	{ matchedToken.image = trimBy(image, 0, 3); }
|   < ANGLE_GRAD     					: <NUMBER> "grad" >    	{ matchedToken.image = trimBy(image, 0, 4); }
|   < ANGLE_RAD       					: <NUMBER> "rad" >      	{ matchedToken.image = trimBy(image, 0, 3); }
|   < EMS                       					: <NUMBER> "em" >     	{ matchedToken.image = trimBy(image, 0, 2); }
|   < EXS                       					: <NUMBER> "ex" >       	{ matchedToken.image = trimBy(image, 0, 2); }
|   < FREQ_HZ             					: <NUMBER> "Hz" >       	{ matchedToken.image = trimBy(image, 0, 2); }
|   < FREQ_KHZ          					: <NUMBER> "kHz" >      	{ matchedToken.image = trimBy(image, 0, 3); }
|   < LENGTH_CM     					: <NUMBER> "cm" >       	{ matchedToken.image = trimBy(image, 0, 2); }
|   < LENGTH_IN         					: <NUMBER> "in" >        	{ matchedToken.image = trimBy(image, 0, 2); }
|   < LENGTH_GD     					: <NUMBER> "gd" >      	{ matchedToken.image = trimBy(image, 0, 2); }
|   < LENGTH_MM     					: <NUMBER> "mm" >    	{ matchedToken.image = trimBy(image, 0, 2); }
|   < LENGTH_PC      					: <NUMBER> "pc" >       	{ matchedToken.image = trimBy(image, 0, 2); }
|   < LENGTH_PT       					: <NUMBER> "pt" >        	{ matchedToken.image = trimBy(image, 0, 2); }
|   < LENGTH_PX       					: <NUMBER> "px" >       	{ matchedToken.image = trimBy(image, 0, 2); }
|   < PERCENTAGE    					: <NUMBER> "%" >
|   < TIME_MS               					: <NUMBER> "ms" >      	{ matchedToken.image = trimBy(image, 0, 2); }
|   < TIME_S                					: <NUMBER> "s" >          	{ matchedToken.image = trimBy(image, 0, 1); }
|   < RESOLUTION    					: <NUMBER> ("dpi" | "dpcm") >
|   < DIMEN                  					: <NUMBER> <IDENT> >
| 	< DATE                     					: <NUMBER> "/" <NUMBER> "/" <NUMBER> >
}


/*======================    CSS Tokens    ======================*/
<DEFAULT> TOKEN : {
    < CDO: "<!--" >
|   < CDC: "-->" >
|	< HASH: "#" <NAME> >
|   < INCLUDES  : "~=" >
|   < DASHMATCH : "|=" >
|   < PREFIXMATCH : "^=" >
|   < SUFFIXMATCH : "$=" >
|   < SUBSTRINGMATCH : "*=" >
|   < IMPORTANT_SYM: "!" ( <S> )? "important" >
|   < INHERIT: "inherit" >
|   < RGB: "rgb(" >
|	< NOT: "not(" >
|   < FUNCTION: <IDENT> "(" >
}

<*> TOKEN: {
    < UNKNOWN: ~[] > {
    	// do nothing
    }
}


/*======================    Parse Style Declaration    ======================*/
void parseStyleDeclaration()  : {} {
    <LBRACE> ( <S> )* ( declaration() )? ( <SEMICOLON> ( <S> )* ( declaration() )? )* <RBRACE>
}


/*======================    Parse Style Rule    ======================*/
void parseStyleRule() : {} {
    ( importRule() | styleRule() | mediaRule() | pageRule() | fontFaceRule() | unknownRule() )
}


/*======================    Entire Stylesheet    ======================*/
void styleSheet() : {} {
	try {
		( <CHARSET_SYM> ( <S> )* <STRING> ( <S> )* <SEMICOLON> )?
	    ( <S> | <CDO> | <CDC> )*
	    ( importRule() ( <S> | <CDO> | <CDC> )* )*
	    ( ( styleRule() | mediaRule() | pageRule() | fontFaceRule() | unknownRule() ) ( <S> | <CDO> | <CDC> )* )*
	    <EOF>
	} catch (ParseException e) {
		skipBlock(e);
		styleSheet();
	}
}


/*======================    Import Rule    ======================*/
void importRule() : {
    Token token;
    SACMediaListImpl mediaList = new SACMediaListImpl();
} {
    <IMPORT_SYM> ( <S> )* ( token = <STRING> | token = <URL> ) ( <S> )* ( mediaList(mediaList) )? <SEMICOLON> {
        handler.importStyle(token.image, mediaList, null);
    }
}


/*======================    Media Rule    ======================*/
void mediaRule() : {
    boolean start = false;
    SACMediaListImpl media = new SACMediaListImpl();
} {
    try {
        <MEDIA_SYM> ( <S> )*
        mediaList(media)
        {
            start = true;
            handler.startMedia(media);
        }
        <LBRACE> ( <S> )*
        ( mediaRuleList() )?
        <RBRACE>
    } finally {
        if (start) {
            handler.endMedia(media);
        }
    }
}


/*======================    Unknown Rule    ======================*/
void unknownRule() : {
	String token;
} {
    <ATKEYWORD> {
    	token = skip();
        handler.ignorableAtRule(token);
    }
}


/*======================    Media List    ======================*/
void mediaList(SACMediaListImpl list) : {
    String medium;
} {
    medium = medium() ( <COMMA> ( <S> )* { list.appendMedium(medium); } medium = medium() )* {
        list.appendMedium(medium);
    }
}


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


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


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


//===============================================
void pageRule() : {
    Token t = null;
    String s = null;
    boolean start = false;
} {
    try {
        <PAGE_SYM> ( <S> )*
        ( LOOKAHEAD(2) ( t = <IDENT> ( <S> )* ) |
        ( t = <IDENT> s = pseudoPage() ( <S> )* ) |
        ( s = pseudoPage() ( <S> )* ) )?
        <LBRACE> ( <S> )*
        {
            start = true;
            handler.startPage((t != null) ? t.image : null, s);
        }

        ( declaration() )?
        ( <SEMICOLON> ( <S> )* ( declaration() )? )*
        <RBRACE>
    } finally {
        if (start) {
            handler.endPage((t != null) ? t.image : null, s);
        }
    }
}


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


//===============================================
void fontFaceRule() : {
    boolean start = false;
} {
    try {
        <FONT_FACE_SYM> ( <S> )*
        <LBRACE> ( <S> )* { start = true; handler.startFontFace(); }
        ( declaration() )?
        ( <SEMICOLON> ( <S> )* ( declaration() )? )*
        <RBRACE>
    } finally {
        if (start) {
            handler.endFontFace();
        }
    }
}


/*======================    Cominator Unit   ======================*/
char combinator() : {
    char c = ' ';
} {
    ( ( <PLUS> { c='+'; } | <GT> { c='>'; } | <TILDE> { c='~'; } ) ( <S> )*
    |
    <S> ( ( <PLUS> { c='+'; } | <GT> { c='>'; } | <TILDE> { c='~'; } ) ( <S> )* )? ) {
    	return c;
	}
}


/*======================    Property Name   ======================*/
String property() : {
    Token tokne;
} {
    tokne = <IDENT> ( <S> )* {
    	return tokne.image;
    }
}


/*======================    Selector Rule   ======================*/
void styleRule() : {
    SelectorList selectorList = null;
    boolean start = false;
} {
    try {
        selectorList = selectorList()
        <LBRACE> ( <S> )* {
            start = true;
            handler.startSelector(selectorList);
        }
        ( declaration() )?
        ( <SEMICOLON> ( <S> )* ( declaration() )? )*
        <RBRACE>
    } catch(ParseException e) {
    	skipBlock(e);
    } finally {
        if (start) {
            handler.endSelector(selectorList);
        }
    }
}


/*======================    Selector List   ======================*/
//	All parse exception ocurred in selector context, rethrow up.
SelectorList selectorList() : {
    SelectorListImpl selectorList = new SelectorListImpl();
    Selector selector;
} {
    selector = selector() ( <COMMA> ( <S> )* { selectorList.add(selector); } selector = selector() )* {
        selectorList.add(selector);
        return selectorList;
    }
}


/*======================    Selector   ======================*/
//	All parse exception ocurred in selector context, rethrow up.
Selector selector() : {
    Selector selector;
    char combinator;
} {
    selector = simpleSelector(null, ' ') ( LOOKAHEAD(2) combinator = combinator() selector = simpleSelector(selector, combinator) )* ( <S> )* {
        return selector;
    }
}


/*======================    Simple Selector   ======================*/
//	All parse exception ocurred in simple selector context, rethrow up.
Selector simpleSelector(Selector selector, char combinator) : {
    SimpleSelector current = null;
    Condition con = null;
} {
	((current = elementName() ( con = hash(con) | con = clazz(con) | con = attrib(con) | con = pseudo(con) )* )
	|
	( con = hash(con) | con = clazz(con) | con = attrib(con) | con = pseudo(con) )+ ) {
    	// check whether the current selector is a universal selector or not
    	if (current == null) {
    		current = selectorFactory.createElementSelector(null, null);
    	}
    	
    	// check whether the current selector is a conditional selector or not
        if (con != null) {
            current = selectorFactory.createConditionalSelector(current, con);
        }
        
        // check parent selector
        if (selector == null) {
        	return current;
        }

        switch (combinator) {
        case ' ':
            return selectorFactory.createDescendantSelector(selector, current);
        case '+':
            return selectorFactory.createDirectAdjacentSelector(selector.getSelectorType(), selector, current);
        case '>':
            return selectorFactory.createChildSelector(selector, current);
        case '~':
            return new IndirectAdjacentFilter(selector.getSelectorType(), selector, current);
        }
    }
}


/*======================    Class Selector   ======================*/
Condition clazz(Condition previous) : {
    Token token;
} {
    <DOT> token = <IDENT> {
        Condition condition = conditionFactory.createClassCondition(null, token.image);
        
        if (previous == null) {
        	return condition;
        } else {
        	return conditionFactory.createAndCondition(previous, condition);
        }
    }
}


/*======================    Element Selector   ======================*/
SimpleSelector elementName() : {
    Token token;
} {
    token = <IDENT> {
    	return selectorFactory.createElementSelector(null, token.image);
    }
	|
	<ASTERISK> {
		return selectorFactory.createElementSelector(null, null);
	}
}


/*======================    Attribute Selector   ======================*/
Condition attrib(Condition previous) : {
    Token name = null;
    Token value = null;
    int type = 0;
} {
    <LSQUARE> ( <S> )* name = <IDENT> ( <S> )*
    (
        ( <EQUALS> { type = 1; } | <INCLUDES> { type = 2; } | <DASHMATCH> { type = 3; } | <PREFIXMATCH> { type = 4; } | <SUFFIXMATCH> { type = 5; } | <SUBSTRINGMATCH> { type = 6; } )
        ( <S> )*
        ( value = <IDENT> | value = <STRING> )
        ( <S> )*
    )?
    <RSQUARE> {
        Condition condition = null;

        switch (type) {
        case 0:
            condition = conditionFactory.createAttributeCondition(name.image, null, false, null);
            break;
        case 1:
            condition = conditionFactory.createAttributeCondition(name.image, null, false, value.image);
            break;
        case 2:
            condition = conditionFactory.createOneOfAttributeCondition(name.image, null, false, value.image);
            break;
        case 3:
            condition = conditionFactory.createBeginHyphenAttributeCondition(name.image, null, false, value.image);
            break;
        case 4:
            condition = new PrefixMatchFilter(name.image, value.image);
            break;
        case 5:
            condition = new SuffixMatchFilter(name.image, value.image);
            break;
        case 6:
            condition = new SubstringMatchFilter(name.image, value.image);
            break;
        }

        // check conditional
        if (previous == null) {
        	return condition;
        } else {
        	return conditionFactory.createAndCondition(previous, condition);
        }
    }
}


/*======================    Pseudo Selector   ======================*/
Condition pseudo(Condition previous) : {
    Condition condition = null;
    Selector selector;
    Token function;
    Token argument;
} {
    <COLON>
    (
        function = <IDENT> {
            try {
                condition = conditionFactory.createPseudoClassCondition(null, function.image);
            } catch (CSSException e) {
                throw new ParseException(e.getMessage());
            }
        }
        |
        <NOT> ( <S> )* selector = selector() ( <S> )* <RROUND> {
        	condition = new DenialFIlter(CSSUtil.converte(selector));
        }
        |
        function = <FUNCTION> ( <S> )* ( argument = <NUMBER> | argument = <NUMERAL> | argument = <IDENT> | argument = <STRING> ) ( <S> )* <RROUND> {
			String name = function.image.substring(0, function.image.length() - 1);

			// check function name
            if (name.equals("lang")) {
                condition = conditionFactory.createLangCondition(argument.image);
            } else if (name.equals("contains")) {
                condition = conditionFactory.createContentCondition(argument.image);
            } else if (name.equals("nth-child")) {
                condition = new FirstNumeralFilter(argument.image);
            } else if (name.equals("nth-of-type")) {
                condition = new FirstTypedNumeralFilter(argument.image);
            } else if (name.equals("nth-last-child")) {
                condition = new LastNumeralFilter(argument.image);
            } else if (name.equals("nth-last-of-type")) {
                condition = new LastTypedNumeralFilter(argument.image);
            } else {
                throw new ParseException("Invalid pseudo function name '" + name + "'.");
            }
        }
    ) { return (previous == null) ? condition : conditionFactory.createAndCondition(previous, condition); }
}


/*======================    Identified Selector    ======================*/
Condition hash(Condition previous) : {
    Token token;
} {
    token = <HASH> {
        Condition condition = conditionFactory.createIdCondition(token.image.substring(1));

		// check conditional
        if (previous == null) {
        	return condition;
        } else {
        	return conditionFactory.createAndCondition(previous, condition);
        }
    }
}


/*======================    Property Declaration    ======================*/
void declaration() : {
	String property;
	LexicalUnit unit;
	boolean priority = false;
} {
	try {
        property = property() <COLON> ( <S> )* unit = expr() ( priority = priority() )? {
            handler.property(property, unit, priority);
        }
    } catch (ParseException e) {
        skipDeclaration(e);
	}
}


/*======================    Property Priority    ======================*/
boolean priority() : {} {
    <IMPORTANT_SYM> ( <S> )* {
    	return true;
    }
}


/*======================    Property Expression    ======================*/
Value expr() : {
    Value head;
    Value body;
} {
    head = term(null) { body = head; } ( ( body = operator(body) )? body = term(body) )* {
    	return head;
   	}
}


/*======================    Operator    ======================*/
Value operator(Value prev) : {
	Token token;
} {
	( token = <SLASH> | token = <COMMA> ) ( <S> )* {
    	 return new CSSOperator(token.image, prev);
    }
}


/*======================    Property Value    ======================*/
Value term(Value prev) : {
  Token token;
  Value value = null;
} {
    (
        ( token = <NUMBER>      { value = new CSSNumber(token.image, prev); }
        | token = <PERCENTAGE>  { value = new CSSPercentage(token.image.substring(0, token.image.length() - 1), prev); }
        | token = <LENGTH_PX>   { value = new CSSpxLength(token.image, prev); }
        | token = <LENGTH_CM>   { value = new CSScmLength(token.image, prev); }
        | token = <LENGTH_MM>   { value = new CSSmmLength(token.image, prev); }
        | token = <LENGTH_IN>   { value = new CSSinLength(token.image, prev); }
        | token = <LENGTH_PT>   { value = new CSSptLength(token.image, prev); }
        | token = <LENGTH_PC>   { value = new CSSpcLength(token.image, prev); }
        | token = <EMS>         { value = new CSSemLength(token.image, prev); }
        | token = <EXS>         { value = new CSSexLength(token.image, prev); }
        | token = <ANGLE_DEG>   { value = new CSSdegAngle(token.image, prev); }
        | token = <ANGLE_RAD>   { value = new CSSradAngle(token.image, prev); }
        | token = <ANGLE_GRAD>  { value = new CSSgradAngle(token.image, prev); }
        | token = <TIME_MS>     { value = new CSSmsTime(token.image, prev); }
        | token = <TIME_S>      { value = new CSSsTime(token.image, prev); }
        | token = <FREQ_HZ>     { value = new CSShzFrequency(token.image, prev); }
        | token = <FREQ_KHZ>    { value = new CSSkhzFrequency(token.image, prev); }
        | token = <DIMEN> { throw new ParseException("This value '" + token.image + "' has a unknown dimension."); }
        | value = function(prev)
		)
    | token = <STRING>          { value = new CSSString(token.image, prev); }
    | token = <IDENT> {
        // check color
        if (CSSColor.isDefinedColor(token.image)) {
            value = new CSSColor(token.image, prev);
        } else {
        	value = new CSSIdent(token.image, prev);
        }
	}
    | token = <URL>             { value = new CSSURI(token.image, prev); }
    | value = rgb(prev)
    | value = hexcolor(prev)
    | token = <UNICODERANGE>    { value = new CSSUnicodeRange(token.image, prev);}
    )
    ( <S> )*
    { return value; }
}


/*======================    Special Function    ======================*/
Value function(Value prev) : {
    Token token;
    Value params;
} {
    token = <FUNCTION> ( <S> )* params = expr() <RROUND> {
    	String function = token.image.substring(0, token.image.length() - 1);
    	
        if (function.equals("counter")) {
           return new CSSCounterFunction(params, prev);
        } else if (function.equals("counters")) {
           return new CSSCountersFunction(params, prev);
        } else if (function.equals("attr")) {
           return new CSSAttrFunction(params, prev);
        } else if (function.equals("rect")) {
           return new CSSRectFunction(params, prev);
        }
        return new CSSFunction(function, params, prev);
    }
}


/*======================    RGB Color Units    ======================*/
Value rgb(Value previous) : {
    Token red;
    Token green;
    Token blue;
} {
    <RGB> ( <S> )*
    (red = <NUMBER> | red = <PERCENTAGE> )
    ( <S> )* <COMMA> ( <S> )*
    (green = <NUMBER> | green = <PERCENTAGE> )
    ( <S> )* <COMMA> ( <S> )*
    (blue = <NUMBER> | blue = <PERCENTAGE> )
    ( <S> )* <RROUND> {
    	try {
        	return new CSSColor(red.image, green.image, blue.image, previous);
        } catch (CSSException e) {
	        throw new ParseException(e.getMessage());
	    }
    }
}


/*======================    HEX Color Units    ======================*/
//	All parse exception ocurred in hex color context are resolved at declaration context.
Value hexcolor(Value previous) : {
    Token token;
} {
    token = <HASH> {
	    try {
	        return new CSSColor(token.image, previous);
	    } catch (CSSException e) {
	        throw new ParseException(e.getMessage());
	    }
    }
}


/*======================    Java Code    ======================*/
JAVACODE
    /**
     * Skip to the end of statement or the end of file.
     * 
     * @return A skipped contents.
     */
    String skip() throws ParseException {
        int nesting = 0;
        Token token = getToken(0);
        StringBuffer buffer = new StringBuffer();

        if (token.image != null) {
            buffer.append(token.image);
        }

        do {
            token = getNextToken();

            // check end of file
            if (token.kind == EOF) {
                break;
            }

            buffer.append(token.image);

            // check end of statement
            if (token.kind == LBRACE) {
                nesting++;
            } else if (token.kind == RBRACE) {
                nesting--;
            } else if (token.kind == SEMICOLON && nesting <= 0) {
                break;
            }
        } while (token.kind != RBRACE || nesting > 0);

        return buffer.toString();
    }


JAVACODE
    /**
     * Catch a parse error and skip to the end of statement.
     * 
     * @param e A cause.
     * @throws ParseException If parse error is occured.
     */
    void skipDeclaration(Exception e) {
        Token token = getToken(1);
        handleError("Unexpected token is appered in the declaration context, so skip to the next one.", e);

        // check statement
        while (token.kind != SEMICOLON && token.kind != RBRACE && token.kind != EOF) {
            if (token.kind == LBRACE) {
                skipNext();
            }
            getNextToken();
            token = getToken(1);
        }
    }


JAVACODE
    void skipNext() {
        int counter = 0;
        getNextToken();
        Token token = getToken(1);

        while (token.kind != RBRACE || counter > 0) {
            if (token.kind == LBRACE) {
                counter++;
            }

            if (token.kind == RBRACE) {
                counter--;
            }
            getNextToken();
            token = getToken(1);
        }
    }
    
JAVACODE
    /**
     * Skip the parser pointing to the next valid block statement.
     *
     * @param e A cause.
     * @throws ParseException If parse error is occured.
     */
    void skipBlock(ParseException e) {
        int nesting = -1;
        Token token = getNextToken();
        
        // check EOF
        if (token.kind == EOF) {
        	return;
        }
        handleError("Unexpected token is appered in the block context, so skip to the next one.", e);

        // cehck end of block statement
        while (token.kind != RBRACE || nesting > 0) {
            if (token.kind == LBRACE) {
                nesting++;
            } else if (token.kind == RBRACE && nesting > 0) {
                nesting--;
            } else if (token.kind == EOF) {
                break;
            }
            token = getNextToken();
        }
    }
