/* -*-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$ */ 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$ */ 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 "CSS2". * @return the string "CSS2". */ public String getParserVersion() { return "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 (left 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(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 language; } { ":" ( n= { String s = 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= { result = LexicalUnitImpl.createIdent(n.beginLine, n.beginColumn, prev, convertIdent(n.image)); /* / * 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()); } LexicalUnitImpl l = (LexicalUnitImpl) params; boolean loop = true; if (n.image.equals("rgb(")) { // 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 (n.image.equals("counter(")) { 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 (n.image.equals("counters(")) { 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 (n.image.equals("attr(")) { if ((l != null) && (l.getNextLexicalUnit() == null) && (l.getLexicalUnitType() == LexicalUnit.SAC_IDENT)) { return LexicalUnitImpl.createAttr(l.getLineNumber(), l.getColumnNumber(), prev, l.getStringValue()); } } else if (n.image.equals("rect(")) { 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, n.image.substring(0, n.image.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: */