All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.w3c.flute.parser.Parser.jj Maven / Gradle / Ivy

The newest version!
/* -*-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 org.w3c.flute.parser;

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


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;

/**
 * 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 StringBuffer SPACE = new StringBuffer(" ");

    // the document handler for the parser
    protected DocumentHandler 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 = 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 
 */


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


MORE : /* Comments */
{
    < "/*" > : IN_COMMENT
}


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


MORE :
{
    < ~[] > : IN_COMMENT
}


TOKEN :
{
  < CDO : "" >
  | < LBRACE : "{" >
  | < RBRACE : "}">
  | < DASHMATCH : "|=" >
  | < INCLUDES  : "~=" >
  | < EQ        : "=" >
  | < PLUS      : "+" >
  | < MINUS     : "-" >
  | < COMMA     : "," >
  | < SEMICOLON : ";" >
  | < PRECEDES  : ">" >
  | < DIV       : "/" >
  | < LBRACKET  : "[" >
  | < RBRACKET  : "]" >
  | < ANY       : "*" >
  | < DOT       : "." >
  | < LPARAN    : ")" >
  | < RPARAN    : "(">
}


TOKEN :
{
  < COLON     : ":" >
}



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


TOKEN :
{
  < STRING      : ( "\"" (  | "'" )* "\"" ) | 
                  ( "'" (  | "\"" )* "'" ) >
  | < IDENT         : ("-")?  (  )* >
  | < NUMBER	  : (  )+ | (  )* "." (  )+ > 
  | < #_URL       : [ "!","#","$","%","&","*"-"~" ] |  |  >
  | < URL         : "url(" (  )*
                    (  | ( <_URL> )* ) (  )* ")" >            
}


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


TOKEN :
{
  < HASH : "#"  >
}

/* RESERVED ATRULE WORDS */

TOKEN : 
{
    < IMPORT_SYM   : "@import">
  | < MEDIA_SYM    : "@media" >
  | < CHARSET_SYM  : "@charset" >
  | < PAGE_SYM     : "@page"  >
  | < FONT_FACE_SYM: "@font-face" >
  | < ATKEYWORD    : "@"  >
}


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


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


TOKEN :
{
 < FUNCTION :  "(" >
}


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() )?
	(  | ignoreStatement() )*
	( importDeclaration() ( ignoreStatement() (  )* )* )*
	afterImportDeclaration()
	
    } finally {
	documentHandler.endDocument(source);
    }
}


void charset() :
{ Token n; }
{  
    try {
	 (  )* n= (  )* ";"
    } 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;
}
{
  ( ( 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() (  )* )* )*
}

void ignoreStatement() :
{}
{
    |  | atRuleDeclaration()
}

/**
 * The import statement
 *
 * @exception ParseException exception during the parse
 */
void importDeclaration() :
{Token n;
 String uri;
 MediaListImpl ml = new MediaListImpl();
}
{
 try {
   
       (  )* ( n= { uri = convertStringIndex(n.image, 1, 
							n.image.length() -1); }
		  | n= 
	   {
	       uri = n.image.substring(4, n.image.length()-1).trim();
	       if ((uri.charAt(0) == '"')
		   || (uri.charAt(0) == '\'')) {
		   uri = uri.substring(1, uri.length()-1);
	       }
	   }
		  )
       (  )* ( mediaStatement(ml) )? ";"
       (  )*
	   {
	       if (ml.getLength() == 0) {
		   // see section 6.3 of the CSS2 recommandation.
		   ml.addItem("all");
	       }
	       documentHandler.importStyle(uri, ml, null);
	   }
 } 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 {
   (  )*
   mediaStatement(ml)
   { start = true; documentHandler.startMedia(ml); }
    (  )* ( styleRule() | skipUnknownRule() )*  (  )*
 } catch (ParseException e) {
     reportError(getLocator(), e);
     skipStatement();
     // reportWarningSkipText(getLocator(), skipStatement());
 } finally {
     if (start) {
	 documentHandler.endMedia(ml);
     }
 }
}

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

/**
 * @exception ParseException exception during the parse
 */
String medium() : /* tv, projection, screen, ... */
{Token n;}
{
    n= (  )* { 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 {
   (  )* ( n= (  )* )?
  ( pseudo=pseudo_page() )? 
      {
	  if (n != null) {
	      page = convertIdent(n.image);
	  }
      }
   ()* 
      {
	  start = true;
	  documentHandler.startPage(page, pseudo);
      }
     ( declaration() )? ( ";" (  )* ( declaration() )? )* 
      ()*
 } 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= (  )* { return convertIdent(n.image); }
}

void fontFace() :
{
    boolean start = false;
}
{
 try {
   (  )* 
      ()* 
      { start = true; documentHandler.startFontFace(); }
     ( declaration() )? ( ";" (  )* ( declaration() )? )* 
      ()*
 } 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=
    {
	ret=skipStatement();
	reportWarningSkipText(getLocator(), ret);
	if ((ret != null) && (ret.charAt(0) == '@')) {
	    documentHandler.ignorableAtRule(ret);
	}
    }
}

void skipUnknownRule() :
{ Token n;}
{
 ( n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=
| n=";"
| n="+"
| n=">"
| n="-"
| n=
   ) {
     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 = ' ';
}
{
    "+" (  )* { return '+'; }
    | ">" (  )* { return '>'; }
|   ( ( "+" { connector = '+'; } 
           | ">" { connector = '>'; } )
 (  )* )? { return connector; }
}

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

/**
 * @exception ParseException exception during the parse
 */
void styleRule() :
{
    boolean start = false;
    SelectorList l = null;
    Token save;
    Locator loc;
}
{
 try {
     l=selectorList() { save = token; }  ()*
     {
	 start = true;
	 documentHandler.startSelector(l);
     }
     ( declaration() )? ( ";" (  )* ( declaration() )? )*
      ()*
 } 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() (  ()* { 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) )* ()*
   {
       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 n;
Condition c;
}
{
  "." n=
      { 
	  c = conditionFactory.createClassCondition(null, n.image);
	  if (pred == null) {
	      return c;
	  } else {
	      return conditionFactory.createAndCondition(pred, c);
	  }
      }
}

/**
 * @exception ParseException exception during the parse
 */
SimpleSelector element_name() :
{Token n; }
{
    n= 
    { 
      return selectorFactory.createElementSelector(null, convertIdent(n.image)); 
    }
      | "*"
    { return selectorFactory.createElementSelector(null, null); }
}

/**
 * @exception ParseException exception during the parse
 */
Condition attrib(Condition pred) :
{
    int cases = 0;
    Token att = null;
    Token val = null;
    String attValue = null;
}
{
  "[" (  )* att= (  )*
      ( ( "="           { cases = 1; }
	  |   { cases = 2; }	  
	  |  { cases = 3; } ) (  )* 
	( val= { attValue = val.image; }
	  | val= { attValue = convertStringIndex(val.image, 1, 
							 val.image.length() -1);}
	)
	(  )* )?
  "]"
  {
      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 t1 = null;
Token language; 
}
{
    ":" [(t1 = )] ( n=
	{
	    String prefix = (t1 != null) ? ":" : "";
	    String s = prefix + convertIdent(n.image);
	    if (s.equals("first-letter") || s.equals("first-line")) {
		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= (  )* language= (  )* ")" 
	      {
		  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=
  {
      Condition d = 
	  conditionFactory.createIdCondition(n.image.substring(1));
      if (pred == null) {
	  return d;
      } else {
	  return conditionFactory.createAndCondition(pred, d);
      }
  }
}

/**
 * @exception ParseException exception during the parse
 */
void declaration() :
{ boolean important = false;
  String name;
  LexicalUnit exp;
  Token save;
}
{
 try {
     name=property()
     { save = token; }
     ":" (  )* 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() :
{}
{
   (  )* { return true; }
}

/**
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl operator(LexicalUnitImpl prev) :
{Token n;}
{
n="/" (  )* { return LexicalUnitImpl.createSlash(n.beginLine, 
						       n.beginColumn,
						       prev); }
| n="," (  )* { 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; }
    ( ( 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 = ' ';
}
{
  ( ( ( op=unaryOperator() )?
    ( n=
	{ result = LexicalUnitImpl.createNumber(n.beginLine, n.beginColumn,
						prev, number(op, n, 0)); }
    | n=
	{ result = LexicalUnitImpl.createPercentage(n.beginLine, n.beginColumn,
						    prev, number(op, n, 1)); }
    | n=
	{ result = LexicalUnitImpl.createPT(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createCM(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createMM(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createPC(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createIN(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createPX(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createEMS(n.beginLine, n.beginColumn,
					     prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createEXS(n.beginLine, n.beginColumn,
					     prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createDEG(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=
	{ result = LexicalUnitImpl.createRAD(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=
	{ result = LexicalUnitImpl.createGRAD(n.beginLine, n.beginColumn,
					      prev, number(op, n, 3)); }
    | n=
	{ result = LexicalUnitImpl.createS(n.beginLine, n.beginColumn,
					   prev, number(op, n, 1)); }
    | n=
	{ result = LexicalUnitImpl.createMS(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createHZ(n.beginLine, n.beginColumn,
					    prev, number(op, n, 2)); }
    | n=
	{ result = LexicalUnitImpl.createKHZ(n.beginLine, n.beginColumn,
					     prev, number(op, n, 3)); }
    | n=
	{
	    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=
	{ result =
	  LexicalUnitImpl.createString(n.beginLine, n.beginColumn, prev,
				       convertStringIndex(n.image, 1, 
							  n.image.length() -1));}
       | n= 
      { 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)
         ) ) (  )*
      {
	  return result;
      }
}

/**
 * Handle all CSS2 functions.
 * @exception ParseException exception during the parse
 */
LexicalUnitImpl function(char operator, LexicalUnitImpl prev) :
{Token n;
 LexicalUnit params = null;
}
{
    n= (  )* ( 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=
 {
     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=
 {
   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=
 { 
     int r;
     LexicalUnitImpl first, params = null;
     String s = n.image.substring(1);

     if (s.length() == 3) {
	 String sh = s.substring(0,1);
	 r = Integer.parseInt(sh+sh, 16);
	 first = params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
							params, r);
	 params = LexicalUnitImpl.createComma(n.beginLine, n.beginColumn,
						 params);
	 sh = s.substring(1,2);
	 r = Integer.parseInt(sh+sh, 16);
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, r);
	 params = LexicalUnitImpl.createComma(n.beginLine, n.beginColumn,
						 params);
	 sh = s.substring(2,3);
	 r = Integer.parseInt(sh+sh, 16);
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, r);
     } else if (s.length() == 6) {
	 r = Integer.parseInt(s.substring(0,2), 16);
	 first = params = LexicalUnitImpl.createInteger(n.beginLine, 
							n.beginColumn,
							params, r);
	 params = LexicalUnitImpl.createComma(n.beginLine, n.beginColumn,
						 params);
	 r = Integer.parseInt(s.substring(2,4), 16);
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, r);
	 params = LexicalUnitImpl.createComma(n.beginLine, n.beginColumn,
						 params);
	 r = Integer.parseInt(s.substring(4,6), 16);
	 params = LexicalUnitImpl.createInteger(n.beginLine, n.beginColumn,
						params, r);
     } else {
	 first = null;
	 throw new CSSParseException("invalid hexadecimal notation for RGB: " + s, 
				     getLocator());
     }
     return LexicalUnitImpl.createRGBColor(n.beginLine, n.beginColumn,
					   prev, first); 
 }
}

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 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());
}

/*
 * @@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;
}
{
	(  )*
	   ( 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() :
{
}
{
    (  )* importDeclaration()
}

void _parseMediaRule() :
{
}
{
    (  )* media()
}

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

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

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