/* -*-java-extended-*- * Copyright (c) 1999 World Wide Web Consortium * (Massachusetts Institute of Technology, Institut National de Recherche * en Informatique et en Automatique, Keio University). * All Rights Reserved. http://www.w3.org/Consortium/Legal/ * * $Id: Parser.jj,v 1.15 2000/10/27 21:09:37 plehegar Exp $ */ options { IGNORE_CASE = true; STATIC = false; USER_CHAR_STREAM = true; /* DEBUG_TOKEN_MANAGER = true; DEBUG_PARSER = true; */ } PARSER_BEGIN(Parser) package com.vaadin.sass.parser; import java.io.*; import java.net.*; import java.util.ArrayList; import java.util.Locale; import java.util.Map; import org.w3c.css.sac.ConditionFactory; import org.w3c.css.sac.Condition; import org.w3c.css.sac.SelectorFactory; import org.w3c.css.sac.SelectorList; import org.w3c.css.sac.Selector; import org.w3c.css.sac.SimpleSelector; import org.w3c.css.sac.DocumentHandler; import org.w3c.css.sac.InputSource; import org.w3c.css.sac.ErrorHandler; import org.w3c.css.sac.CSSException; import org.w3c.css.sac.CSSParseException; import org.w3c.css.sac.Locator; import org.w3c.css.sac.LexicalUnit; import org.w3c.flute.parser.selectors.SelectorFactoryImpl; import org.w3c.flute.parser.selectors.ConditionFactoryImpl; import org.w3c.flute.util.Encoding; import com.vaadin.sass.handler.*; import com.vaadin.sass.tree.*; /** * A CSS2 parser * * @author Philippe Le H�garet * @version $Revision: 1.15 $ */ public class Parser implements org.w3c.css.sac.Parser { // replaces all \t, \n, etc with this StringBuffer. static final StringBuilder SPACE = new StringBuilder(" "); // the document handler for the parser protected SCSSDocumentHandlerImpl documentHandler; // the error handler for the parser protected ErrorHandler errorHandler; // the input source for the parser protected InputSource source; protected ConditionFactory conditionFactory; protected SelectorFactory selectorFactory; // temporary place holder for pseudo-element ... private String pseudoElt; /** * Creates a new Parser */ public Parser() { this((CharStream) null); } /** * @@TODO * @exception CSSException Not yet implemented */ public void setLocale(Locale locale) throws CSSException { throw new CSSException(CSSException.SAC_NOT_SUPPORTED_ERR); } /** * Set the document handler for this parser */ public void setDocumentHandler(DocumentHandler handler) { this.documentHandler = (SCSSDocumentHandlerImpl) handler; } public void setSelectorFactory(SelectorFactory selectorFactory) { this.selectorFactory = selectorFactory; } public void setConditionFactory(ConditionFactory conditionFactory) { this.conditionFactory = conditionFactory; } /** * Set the error handler for this parser */ public void setErrorHandler(ErrorHandler error) { this.errorHandler = error; } /** * Main parse methods * * @param source the source of the style sheet. * @exception IOException the source can't be parsed. * @exception CSSException the source is not CSS valid. */ public void parseStyleSheet(InputSource source) throws CSSException, IOException { this.source = source; ReInit(getCharStreamWithLurk(source)); if (selectorFactory == null) { selectorFactory = new SelectorFactoryImpl(); } if (conditionFactory == null) { conditionFactory = new ConditionFactoryImpl(); } parserUnit(); } /** * Convenient method for URIs. * * @param systemId the fully resolved URI of the style sheet. * @exception IOException the source can't be parsed. * @exception CSSException the source is not CSS valid. */ public void parseStyleSheet(String systemId) throws CSSException, IOException { parseStyleSheet(new InputSource(systemId)); } /** * This method parses only one rule (style rule or at-rule, except @charset). * * @param source the source of the rule. * @exception IOException the source can't be parsed. * @exception CSSException the source is not CSS valid. */ public void parseRule(InputSource source) throws CSSException, IOException { this.source = source; ReInit(getCharStreamWithLurk(source)); if (selectorFactory == null) { selectorFactory = new SelectorFactoryImpl(); } if (conditionFactory == null) { conditionFactory = new ConditionFactoryImpl(); } _parseRule(); } /** * This method parses a style declaration (including the surrounding curly * braces). * * @param source the source of the style declaration. * @exception IOException the source can't be parsed. * @exception CSSException the source is not CSS valid. */ public void parseStyleDeclaration(InputSource source) throws CSSException, IOException { this.source = source; ReInit(getCharStreamWithLurk(source)); if (selectorFactory == null) { selectorFactory = new SelectorFactoryImpl(); } if (conditionFactory == null) { conditionFactory = new ConditionFactoryImpl(); } _parseDeclarationBlock(); } /** * This methods returns "http://www.w3.org/TR/REC-CSS2". * @return the string "http://www.w3.org/TR/REC-CSS2". */ public String getParserVersion() { return "http://www.w3.org/TR/REC-CSS2"; } /** * Parse methods used by DOM Level 2 implementation. */ public void parseImportRule(InputSource source) throws CSSException, IOException { this.source = source; ReInit(getCharStreamWithLurk(source)); if (selectorFactory == null) { selectorFactory = new SelectorFactoryImpl(); } if (conditionFactory == null) { conditionFactory = new ConditionFactoryImpl(); } _parseImportRule(); } public void parseMediaRule(InputSource source) throws CSSException, IOException { this.source = source; ReInit(getCharStreamWithLurk(source)); if (selectorFactory == null) { selectorFactory = new SelectorFactoryImpl(); } if (conditionFactory == null) { conditionFactory = new ConditionFactoryImpl(); } _parseMediaRule(); } public SelectorList parseSelectors(InputSource source) throws CSSException, IOException { this.source = source; ReInit(getCharStreamWithLurk(source)); return null; } 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 : { "//" : IN_SINGLE_LINE_COMMENT } MORE : { < ~["\n","\r"] > : IN_SINGLE_LINE_COMMENT } SKIP : { < "\n"|"\r"|"\r\n" > : DEFAULT } MORE : { <"/**" ~["/"]> { input_stream.backup(1); } : IN_FORMAL_COMMENT | "/*" : IN_MULTI_LINE_COMMENT } SPECIAL_TOKEN : { : DEFAULT } SKIP : { : DEFAULT } MORE : { < ~[] > } TOKEN : { < CDO : "" > | < LBRACE : "{" > | < RBRACE : "}"> | < DASHMATCH : "|=" > | < INCLUDES : "~=" > | < EQ : "=" > | < PLUS : "+" > | < MINUS : "-" > | < COMMA : "," > | < SEMICOLON : ";" > | < PRECEDES : ">" > | < SUCCEEDS : "<" > | < DIV : "/" > | < LBRACKET : "[" > | < RBRACKET : "]" > | < ANY : "*" > | < PARENT : "&" > | < DOT : "." > | < LPARAN : "(" > | < RPARAN : ")"> | < COMPARE : "==" > | < OR : "||" > | < AND : "&&" > | < NOT_EQ : "!=" > } TOKEN : { < COLON : ":" > } < DEFAULT > TOKEN : { < INTERPOLATION : "#{"< VARIABLE > "}"> } 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 : { | | } /* DERECTIVES */ TOKEN : { | | | | | | | | | | | | | } < DEFAULT > TOKEN: { < MICROSOFT_RULE : "filter"|"-ms-filter" > } < DEFAULT > TOKEN: { < IF : "if" > } TOKEN: { < GUARDED_SYM : "!" ( )? "default"> } TOKEN : { < STRING : ( "\"" ( | "'" )* "\"" ) | ( "'" ( | "\"" )* "'" ) > | < IDENT : ( )* > | < NUMBER : ( )+ | ( )* "." ( )+ > | < #_URL : [ "!","#","$","%","&","*"-"~" ] | | > | < URL : "url(" ( )* ( | ( <_URL> )* ) ( )* ")" > } TOKEN: { < VARIABLE : "$" > } 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+" "-" > } < DEFAULT > TOKEN : { < REMOVE : "remove" (< S >)? "(" > } TOKEN : { < FUNCTION : (< S >)* "(" > } 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() )? ( comments() | 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; } { ( ( (LOOKAHEAD(5)removeDirective()|variable()) | mixinDirective()| eachDirective() | includeDirective() | styleRule() | media()| page() | fontFace() | { l = getLocator(); } ret=skipStatement() { if ((ret == null) || (ret.length() == 0)) { return; } reportWarningSkipText(l, ret); if (ret.charAt(0) == '@') { documentHandler.ignorableAtRule(ret); } } ) ( ignoreStatement() ( )* )* )* } void ignoreStatement() : {} { | | atRuleDeclaration() } /** * The import statement * * @exception ParseException exception during the parse */ void importDeclaration() : {Token n; String uri; MediaListImpl ml = new MediaListImpl(); boolean isURL = false; } { try { ( )* ( n= { uri = convertStringIndex(n.image, 1, n.image.length() -1); } | n= { isURL=true; uri = n.image.substring(4, n.image.length()-1).trim(); if ((uri.charAt(0) == '"') || (uri.charAt(0) == '\'')) { uri = uri.substring(1, uri.length()-1); } } ) ( )* ( mediaStatement(ml) )? ";" ( )* { if (ml.getLength() == 0) { // see section 6.3 of the CSS2 recommandation. ml.addItem("all"); } documentHandler.importStyle(uri, ml, isURL); } } catch (ParseException e) { reportError(getLocator(), e); skipStatement(); // reportWarningSkipText(getLocator(), skipStatement()); } } /** * @exception ParseException exception during the parse */ void media() : { boolean start = false; String ret; MediaListImpl ml = new MediaListImpl(); } { try { ( )* 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; } } void microsoftExtension() : { Token n; String name = ""; String value = ""; } { n = < MICROSOFT_RULE > (< S >)* { name = n.image; } < COLON > ((n = < IDENT > { value += n.image; }) | (n = < NUMBER > { value += n.image; }) | (n = < INTERPOLATION > { value += n.image; }) | (n = < COLON > { value += n.image; }) | (n = < FUNCTION > { value += n.image; }) | (n = < RPARAN > { value += n.image; }) | (n = < EQ > { value += n.image; }) | (n = < DOT > { value += n.image; }) | (n = < S > { if(value.lastIndexOf(' ') != value.length()-1) { value += n.image; } } ) )+ < SEMICOLON > (< S >)* { documentHandler.microsoftDirective(name, value); } } /** * @exception ParseException exception during the parse */ String property() : {Token n; } { n= ( )* { return convertIdent(n.image); } } String variableName() : {Token n;} { n= ()* {return convertIdent(n.image.substring(1));} } String functionName() : {Token n;} { n= ( )* {return convertIdent(n.image.substring(0, n.image.length()-1));} } /** * @exception ParseException exception during the parse */ void styleRule() : { boolean start = false; ArrayList l = null; Token save; Locator loc; } { try { l=selectorList() { save = token; } ()* { start = true; documentHandler.startSelector(l); } ( ifDirective() | LOOKAHEAD(5)removeDirective() | includeDirective() | media() | extendDirective()| eachDirective() | variable() | LOOKAHEAD(3) (microsoftExtension()|declarationOrNestedProperties()) | styleRule())* ()* } 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(); } } } ArrayList selectorList() : { ArrayList selectors = new ArrayList(); String selector; } { selector=selector() ( ()* { selectors.add(selector); } selector=selector() )* { selectors.add(selector); return selectors; } } /** * @exception ParseException exception during the parse */ String selector() : { String 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 */ String simple_selector(String selector, char comb) : { String simple_current = null; String 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 = ""; } if (cond != null) { simple_current = simple_current + cond; } if (selector != null) { switch (comb) { case ' ': selector = selector + comb + simple_current; break; case '+': selector = selector + " " + comb + " " + simple_current; break; case '>': selector = selector + " " + comb + " " + simple_current; break; default: throw new ParseException("invalid state. send a bug report"); } } else { selector= simple_current; } if (pseudoElt != null) { selector = selector + pseudoElt; } return selector; } } /** * @exception ParseException exception during the parse */ String _class(String pred) : {Token t; String s = "."; } { "." (t = {s += t.image; }|t = < INTERPOLATION >{ s += t.image; })+ { if (pred == null) { return s; } else { return pred + s; } } } /** * @exception ParseException exception during the parse */ String element_name() : {Token t; String s = "";} { (t = {s += t.image; }|t = < INTERPOLATION >{ s += t.image; })+ { return s; } | "*" { return "*"; } | "&" { return "&"; } } /** * @exception ParseException exception during the parse */ String attrib(String 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 = val.image; } ) ( )* )? "]" { String name = convertIdent(att.image); String c; switch (cases) { case 0: c = name; break; case 1: c = name + "=" + attValue; break; case 2: c = name + "~=" + attValue; break; case 3: c = name + "|=" +attValue; break; default: // never reached. c = null; } c = "[" + c + "]"; if (pred == null) { return c; } else { return pred + c; } } } /** * @exception ParseException exception during the parse */ String pseudo(String pred) : {Token n; Token language; boolean isPseudoElement = false; } { ":" (":"{isPseudoElement=true;})?( n= { String s = ":" + convertIdent(n.image); if (isPseudoElement) { if (pseudoElt != null) { throw new CSSParseException("duplicate pseudo element definition " + s, getLocator()); } else { pseudoElt = ":"+s; return pred; } } else { String c = s; if (pred == null) { return c; } else { return pred + c; } } } | ( n= ( )* language= ( )* ")" { String f = convertIdent(n.image); if (f.equals("lang(")) { String d = convertIdent(language.image); if (pred == null) { return d; } else { return pred + d; } } else { throw new CSSParseException("invalid pseudo function name " + f, getLocator()); } } ) ) } /** * @exception ParseException exception during the parse */ String hash(String pred) : {Token n; } { n= { String d = n.image; if (pred == null) { return d; } else { return pred + d; } } } void variable() : { String name; LexicalUnitImpl exp = null; boolean guarded = false; String raw; } { try{ name = variableName() ":" ( )* exp=expr() ( guarded=guarded() )?(";"()*)+ //raw=skipStatementUntilSemiColon() { documentHandler.variable(name, exp, guarded); } }catch (JumpException e) { skipAfterExpression(); } catch (NumberFormatException e) { if (errorHandler != null) { errorHandler.error(new CSSParseException("Invalid number " + e.getMessage(), getLocator(), e)); } reportWarningSkipText(getLocator(), skipAfterExpression()); } catch (ParseException e) { if (errorHandler != null) { if (e.currentToken != null) { LocatorImpl li = new LocatorImpl(this, e.currentToken.next.beginLine, e.currentToken.next.beginColumn-1); reportError(li, e); } else { reportError(getLocator(), e); } skipAfterExpression(); } else { skipAfterExpression(); } } } void ifDirective() : { Token n = null; String evaluator = ""; } { < IF_SYM > ( n = booleanExpressionToken() { evaluator += n.image; } )+ < LBRACE >(< S >)* { documentHandler.startIfElseDirective(); documentHandler.ifDirective(evaluator); } ( includeDirective() | media() | extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())* < RBRACE >(< S >)* (elseDirective())* { documentHandler.endIfElseDirective(); } } void elseDirective() : { String evaluator = ""; Token n = null; } { < ELSE_SYM >(< S >)* ( < IF > (n = booleanExpressionToken() { if(n != null) evaluator += n.image; })*)? < LBRACE >(< S >)* { if(!evaluator.trim().equals("")){ documentHandler.ifDirective(evaluator); } else{ documentHandler.elseDirective(); } } ( includeDirective() | media() | extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())* < RBRACE >(< S >)* } Token booleanExpressionToken() : { Token n = null; } { ( n = < VARIABLE > |n = < IDENT > |n = < NUMBER > |n = < LPARAN > |n = < RPARAN > |n = < PLUS > |n = < MINUS > |n = < DIV > |n = < ANY > |n = < COMPARE > |n = < EQ > |n = < PRECEDES > |n = < SUCCEEDS > |n = < OR > |n = < AND > |n = < S > |n = < NOT_EQ > ){ return n; } } void eachDirective() : { Token var; ArrayList list = null; String listVariable = null; } { < EACH_SYM > (< S >)* var = < VARIABLE > (< S >)* < EACH_IN > (< S >)* (list = stringList() {documentHandler.startEachDirective(var.image, list);} |listVariable = variableName() {documentHandler.startEachDirective(var.image, listVariable);} ) < LBRACE >(< S >)* ( includeDirective() | LOOKAHEAD(5)removeDirective() | media() | extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())* < RBRACE >(< S >)* { documentHandler.endEachDirective();} } ArrayList stringList(): { ArrayList strings = new ArrayList(); Token input; } { (input = < IDENT > (< S >)*) { strings.add(input.image); } (< COMMA >(< S >)* input = < IDENT > { strings.add(input.image); } (< S >)*)* { return strings; } } void mixinDirective() : { String name; ArrayList args = null; String body; } { ()* (name = property() |(name = functionName() args = arglist()) ()*) ()* {documentHandler.startMixinDirective(name, args);} ( includeDirective() | media() | eachDirective() | extendDirective()| variable() | LOOKAHEAD(3) declarationOrNestedProperties() | styleRule())* ()* {documentHandler.endMixinDirective(name, args);} } ArrayList arglist() : { ArrayList args = new ArrayList(); VariableNode arg; } { arg=mixinArg() ( ()* { args.add(arg); } arg=mixinArg() )* { args.add(arg); return args; } } VariableNode mixinArg() : { String name; LexicalUnitImpl first = null; LexicalUnitImpl next = null; LexicalUnitImpl prev = null; } { name=variableName() (":" ()* first=term(null){ prev = first; } (LOOKAHEAD(2)(< COMMA >(< S >)*)?next=term(prev){prev.setNextLexicalUnit(next); prev = next;})* )? { VariableNode arg = new VariableNode(name, first, false); return arg; } } ArrayList argValuelist() : { ArrayList args = new ArrayList(); LexicalUnitImpl first = null; LexicalUnitImpl next = null; LexicalUnitImpl prev = null; } { first = term(null) { args.add(first); prev = first;}(next=term(prev){prev.setNextLexicalUnit(next); prev = next;})* ( ()* first = term(null) { args.add(first); prev = first;}(next=term(prev){prev.setNextLexicalUnit(next); prev = next;})* )* {return args;} } void includeDirective() : { String name; ArrayList args=null; } { ()* (name = property() |(name = functionName() args = argValuelist()) )(";"()*)+ {documentHandler.includeDirective(name, args);} } /** * @exception ParseException exception during the parse */ void removeDirective() : { String list = null; String remove = null; String separator = null; String variable = null; Token n = null; } { n = < VARIABLE >{ variable = n.image; }(< S >)* ":" (< S >)* < REMOVE >(< S >)* (list = removeDirectiveArgs(0)) (< RPARAN >)? < COMMA >(< S >)* (remove = removeDirectiveArgs(1)) ( < COMMA >(< S >)* n = < IDENT >{ separator = n.image; } (< S >)*)? < RPARAN >(< S >)* < SEMICOLON >(< S >)* { documentHandler.removeDirective(variable,list,remove,separator); } } JAVACODE String removeDirectiveArgs(int nest) { String list = ""; int nesting = nest; Token t = null; while(true) { t = getToken(1); String s = t.image; if(t.kind == VARIABLE||t.kind == IDENT) { list += s; }else if(s.toLowerCase().equals("auto")||s.toLowerCase().equals("space")||s.toLowerCase().equals("comma")) { int i = 2; Token temp = getToken(i); boolean isLast = true; while(temp.kind != SEMICOLON) { if(temp.kind != RPARAN || temp.kind != S) { isLast = false; } i++; temp = getToken(i); } if(isLast) { return list; } } else if(t.kind == STRING) { list += s.substring(1,s.length()).substring(0,s.length()-2); }else if(t.kind == LPARAN) { nesting++; if(nesting > nest+1) { throw new CSSParseException("Only one ( ) pair per parameter allowed", getLocator()); } }else if(t.kind == RPARAN) { nesting--; if(nesting == 0) { return list; } } else if(t.kind == COMMA) { if(nesting == nest) { return list; }else { list += ","; } }else if(t.kind == S) { list += " "; } else if(t.kind == LBRACE) { throw new CSSParseException("Invalid token,'{' found", getLocator()); } getNextToken(); } } Node returnDirective() : { String raw; } { raw = skipStatement() {return null;} } JAVACODE void debugDirective(){ } JAVACODE void warnDirective(){ } Node forDirective() : { String var; String from; String to; boolean exclusive; String body; Token tok; } { var = variableName() { int[] toThrough = {TO, THROUGH}; from = skipStatementUntil(toThrough); } (tok = {exclusive = true;} | tok = {exclusive = false;}) to = skipStatementUntilLeftBrace() ()* body = skipStatement() {return documentHandler.forDirective(var, from, to, exclusive, body);} } Node whileDirective() : { String condition; String body; } { condition = skipStatementUntilLeftBrace() body = skipStatement() { return documentHandler.whileDirective(condition, body);} } void extendDirective() : {ArrayList list;} { ()* list = selectorList() (";"()*)+ {documentHandler.extendDirective(list);} } JAVACODE Node importDirective(){ return null; } JAVACODE Node charsetDirective(){ return null; } JAVACODE Node mozDocumentDirective(){ return null; } JAVACODE Node supportsDirective(){ return null; } void nestedProperties(): {String name; LexicalUnit exp;} { name=property() ":" ( )* ()* { documentHandler.startNestedProperties(name); } ( declaration() )? ( ";" ( )* ( declaration() )? )* { documentHandler.endNestedProperties(name); } ()* } /** * @exception ParseException exception during the parse */ void declarationOrNestedProperties() : { boolean important = false; String name; LexicalUnitImpl exp; Token save; String comment = null; } { try { name=property() { save = token; } ":" ( )* (exp=expr() ( important=prio() )? { Token next = getToken(1); if(next.kind == SEMICOLON || next.kind == RBRACE){ while(next.kind == SEMICOLON){ skipStatement(); next = getToken(1); } if(token.specialToken!=null){ documentHandler.property(name, exp, important, token.specialToken.image); }else{ documentHandler.property(name, exp, important, null); } } } | ()* { documentHandler.startNestedProperties(name); } ( declaration() )? ( ";" ( )* ( declaration() )? )* ()* { documentHandler.endNestedProperties(name); } ) } catch (JumpException e) { skipAfterExpression(); // reportWarningSkipText(getLocator(), skipAfterExpression()); } catch (NumberFormatException e) { if (errorHandler != null) { errorHandler.error(new CSSParseException("Invalid number " + e.getMessage(), getLocator(), e)); } reportWarningSkipText(getLocator(), skipAfterExpression()); } catch (ParseException e) { if (errorHandler != null) { if (e.currentToken != null) { LocatorImpl li = new LocatorImpl(this, e.currentToken.next.beginLine, e.currentToken.next.beginColumn-1); reportError(li, e); } else { reportError(getLocator(), e); } skipAfterExpression(); /* LocatorImpl loc = (LocatorImpl) getLocator(); loc.column--; reportWarningSkipText(loc, skipAfterExpression()); */ } else { skipAfterExpression(); } } } /** * @exception ParseException exception during the parse */ void declaration() : { boolean important = false; String name; LexicalUnit exp; Token save; } { try { name=property() { save = token; } ":" ( )* 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; } } boolean guarded() : {} { ()* {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 */ LexicalUnitImpl expr() : { LexicalUnitImpl first, res; char op; } { first=term(null){ res = first; } ( LOOKAHEAD(2) ( res=operator(res) )? res=term(res))* { return first; } } /** * @exception ParseException exception during the parse */ char unaryOperator() : {} { "-" { return '-'; } | "+" { return '+'; } } /** * @exception ParseException exception during the parse */ LexicalUnitImpl term(LexicalUnitImpl prev) : { LexicalUnitImpl result = null; Token n = null; char op = ' '; String varName; } { (( ( ( op=unaryOperator() )? ( n= { 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) ) ) ( )* | varName = variableName() {result = LexicalUnitImpl.createVariable(token.beginLine, token.beginColumn, prev, varName);}) { return result; } } /** * Handle all CSS2 functions. * @exception ParseException exception during the parse */ LexicalUnitImpl function(char operator, LexicalUnitImpl prev) : {Token n; LexicalUnit params = null; } { n= ( )* { String fname = convertIdent(n.image); if("alpha(".equals(fname)){ String body = skipStatementUntilSemiColon(); return LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn, null, "alpha("+body); } } ( params=expr() )? ")" { if (operator != ' ') { throw new CSSParseException("invalid operator before a function.", getLocator()); } String f = convertIdent(n.image); LexicalUnitImpl l = (LexicalUnitImpl) params; boolean loop = true; if ("rgb(".equals(f)) { // this is a RGB declaration (e.g. rgb(255, 50%, 0) ) int i = 0; while (loop && l != null && i < 5) { switch (i) { case 0: case 2: case 4: if ((l.getLexicalUnitType() != LexicalUnit.SAC_INTEGER) && (l.getLexicalUnitType() != LexicalUnit.SAC_PERCENTAGE)) { loop = false; } break; case 1: case 3: if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) { loop = false; } break; default: throw new ParseException("implementation error"); } if (loop) { l = (LexicalUnitImpl) l.getNextLexicalUnit(); i ++; } } if ((i == 5) && loop && (l == null)) { return LexicalUnitImpl.createRGBColor(n.beginLine, n.beginColumn, prev, params); } else { if (errorHandler != null) { String errorText; Locator loc; if (i < 5) { if (params == null) { loc = new LocatorImpl(this, n.beginLine, n.beginColumn-1); errorText = "not enough parameters."; } else if (l == null) { loc = new LocatorImpl(this, n.beginLine, n.beginColumn-1); errorText = "not enough parameters: " + params.toString(); } else { loc = new LocatorImpl(this, l.getLineNumber(), l.getColumnNumber()); errorText = "invalid parameter: " + l.toString(); } } else { loc = new LocatorImpl(this, l.getLineNumber(), l.getColumnNumber()); errorText = "too many parameters: " + l.toString(); } errorHandler.error(new CSSParseException(errorText, loc)); } throw new JumpException(); } } else if ("counter".equals(f)) { int i = 0; while (loop && l != null && i < 3) { switch (i) { case 0: case 2: if (l.getLexicalUnitType() != LexicalUnit.SAC_IDENT) { loop = false; } break; case 1: if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) { loop = false; } break; default: throw new ParseException("implementation error"); } l = (LexicalUnitImpl) l.getNextLexicalUnit(); i ++; } if (((i == 1) || (i == 3)) && loop && (l == null)) { return LexicalUnitImpl.createCounter(n.beginLine, n.beginColumn, prev, params); } } else if ("counters(".equals(f)) { int i = 0; while (loop && l != null && i < 5) { switch (i) { case 0: case 4: if (l.getLexicalUnitType() != LexicalUnit.SAC_IDENT) { loop = false; } break; case 2: if (l.getLexicalUnitType() != LexicalUnit.SAC_STRING_VALUE) { loop = false; } break; case 1: case 3: if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) { loop = false; } break; default: throw new ParseException("implementation error"); } l = (LexicalUnitImpl) l.getNextLexicalUnit(); i ++; } if (((i == 3) || (i == 5)) && loop && (l == null)) { return LexicalUnitImpl.createCounters(n.beginLine, n.beginColumn, prev, params); } } else if ("attr(".equals(f)) { if ((l != null) && (l.getNextLexicalUnit() == null) && (l.getLexicalUnitType() == LexicalUnit.SAC_IDENT)) { return LexicalUnitImpl.createAttr(l.getLineNumber(), l.getColumnNumber(), prev, l.getStringValue()); } } else if ("rect(".equals(f)) { int i = 0; while (loop && l != null && i < 7) { switch (i) { case 0: case 2: case 4: case 6: switch (l.getLexicalUnitType()) { case LexicalUnit.SAC_INTEGER: if (l.getIntegerValue() != 0) { loop = false; } break; case LexicalUnit.SAC_IDENT: if (!l.getStringValue().equals("auto")) { loop = false; } break; case LexicalUnit.SAC_EM: case LexicalUnit.SAC_EX: case LexicalUnit.SAC_PIXEL: case LexicalUnit.SAC_CENTIMETER: case LexicalUnit.SAC_MILLIMETER: case LexicalUnit.SAC_INCH: case LexicalUnit.SAC_POINT: case LexicalUnit.SAC_PICA: // nothing break; default: loop = false; } break; case 1: case 3: case 5: if (l.getLexicalUnitType() != LexicalUnit.SAC_OPERATOR_COMMA) { loop = false; } break; default: throw new ParseException("implementation error"); } l = (LexicalUnitImpl) l.getNextLexicalUnit(); i ++; } if ((i == 7) && loop && (l == null)) { return LexicalUnitImpl.createRect(n.beginLine, n.beginColumn, prev, params); } } return LexicalUnitImpl.createFunction(n.beginLine, n.beginColumn, prev, f.substring(0, f.length() -1), params); } } LexicalUnitImpl unicode(LexicalUnitImpl prev) : { Token n; } { n= { 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(); 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 && s.length()!=6) { first = null; throw new CSSParseException("invalid hexadecimal notation for RGB: " + s, getLocator()); } return LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn, prev, n.image); } } JAVACODE float number(char operator, Token n, int lengthUnit) { String image = n.image; float f = 0; if (lengthUnit != 0) { image = image.substring(0, image.length() - lengthUnit); } f = Float.valueOf(image).floatValue(); return (operator == '-')? -f: f; } JAVACODE String skipStatementUntilSemiColon(){ int[] semicolon = {SEMICOLON}; return skipStatementUntil(semicolon); } JAVACODE String skipStatementUntilLeftBrace(){ int[] lBrace = {LBRACE}; return skipStatementUntil(lBrace); } JAVACODE String skipStatementUntilRightParan(){ int[] rParan = {RPARAN}; return skipStatementUntil(rParan); } JAVACODE String skipStatementUntil(int[] symbols){ StringBuffer s = new StringBuffer(); boolean stop = false; Token tok; while(!stop){ tok = getToken(1); if(tok.kind == EOF) { return null; } for(int sym : symbols){ if(tok.kind == sym){ stop = true; break; } } if(!stop){ if (tok.image != null) { s.append(tok.image); } getNextToken(); } } return s.toString().trim(); } JAVACODE String skipStatement() { StringBuffer s = new StringBuffer(); Token tok = getToken(0); if (tok.image != null) { s.append(tok.image); } while (true) { tok = getToken(1); if (tok.kind == EOF) { return null; } s.append(tok.image); if (tok.kind == LBRACE) { getNextToken(); s.append(skip_to_matching_brace()); getNextToken(); tok = getToken(1); break; } else if (tok.kind == RBRACE) { getNextToken(); tok = getToken(1); break; } else if (tok.kind == SEMICOLON) { getNextToken(); tok = getToken(1); break; } getNextToken(); } // skip white space while (true) { if (tok.kind != S) { break; } tok = getNextToken(); tok = getToken(1); } return s.toString().trim(); } JAVACODE String skip_to_matching_brace() { StringBuffer s = new StringBuffer(); Token tok; int nesting = 1; while (true) { tok = getToken(1); if (tok.kind == EOF) { break; } s.append(tok.image); if (tok.kind == LBRACE) { nesting++; } else if (tok.kind == RBRACE) { nesting--; if (nesting == 0) { break; } } getNextToken(); } return s.toString(); } /* * Here I handle all CSS2 unicode character stuffs. * I convert all \XXXXXX character into a single character. * Don't forget that the parser has recognize the token before. * (So IDENT won't contain newline and stuffs like this). */ JAVACODE String convertStringIndex(String s, int start, int len) { StringBuffer buf = new StringBuffer(len); int index = start; while (index < len) { char c = s.charAt(index); if (c == '\\') { if (++index < len) { c = s.charAt(index); switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': int numValue = Character.digit(c, 16); int count = 0; int p = 16; while (index + 1 < len && count < 6) { c = s.charAt(index+1); if (Character.digit(c, 16) != -1) { numValue = (numValue * 16) + Character.digit(c, 16); p *= 16; index++; } else { if (c == ' ') { // skip the latest white space index++; } break; } } buf.append((char) numValue); break; case '\n': case '\f': break; case '\r': if (index + 1 < len) { if (s.charAt(index + 1) == '\n') { index ++; } } break; default: buf.append(c); } } else { throw new CSSParseException("invalid string " + s, getLocator()); } } else { buf.append(c); } index++; } return buf.toString(); } JAVACODE String convertIdent(String s) { return convertStringIndex(s, 0, s.length()); } JAVACODE String convertString(String s) { return convertStringIndex(s, 0, s.length()); } JAVACODE void comments(){ if (token.specialToken != null){ Token tmp_t = token.specialToken; while (tmp_t.specialToken != null) tmp_t = tmp_t.specialToken; while (tmp_t != null) { documentHandler.comment(tmp_t.image); tmp_t = tmp_t.next; } } } /* * @@HACK * I can't insert a token into the tokens flow. * It's jj_consume_token implementation dependant! :-( */ JAVACODE void rejectToken(Token t) { Token fakeToken = new Token(); t.next = token; fakeToken.next = t; token = fakeToken; } /** * skip after an expression */ JAVACODE String skipAfterExpression() { Token t = getToken(1); StringBuffer s = new StringBuffer(); s.append(getToken(0).image); while ((t.kind != RBRACE) && (t.kind != SEMICOLON) && (t.kind != EOF)) { s.append(t.image); getNextToken(); t = getToken(1); } return s.toString(); } /** * The following functions are useful for a DOM CSS implementation only and are * not part of the general CSS2 parser. */ void _parseRule() : {String ret = null; } { ( )* ( 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() )? )* } ArrayList _parseSelectors() : { ArrayList 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: */