|
CssParser |
|
/* ** Luxor - XML User Interface Language (XUL) Toolkit ** Copyright (c) 2001, 2002 by Gerald Bauer ** ** This program is free software. ** ** You may redistribute it and/or modify it under the terms of the GNU ** General Public License as published by the Free Software Foundation. ** Version 2 of the license should be included with this distribution in ** the file LICENSE, as well as License.html. If the license is not ** included with this distribution, you may find a copy at the FSF web ** site at 'www.gnu.org' or 'www.fsf.org', or you may write to the ** Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139 USA. ** ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR ** REDISTRIBUTION OF THIS SOFTWARE. ** */ package luxor.css; import java.io.*; import java.net.*; import java.util.*; import luxor.css.spi.*; public class CssParser { protected ConditionFactory _conditionFactory; /** * The current token. */ protected int _current; protected DocumentHandler _documentHandler; protected String _documentUri; protected ErrorHandler _errorHandler; /** * The lexer used to break the input source into tokens. */ protected CssLexer _lexer; protected SelectorFactory _selectorFactory; public void setConditionFactory( ConditionFactory factory ) { _conditionFactory = factory; } public void setDocumentHandler( DocumentHandler handler ) { _documentHandler = handler; } public void setErrorHandler( ErrorHandler handler ) { _errorHandler = handler; } public void setSelectorFactory( SelectorFactory factory ) { _selectorFactory = factory; } public String formatMessage( String key, Object[] args ) { return key; } public CssValue parsePropertyValue( InputSource source ) throws CssParseException, IOException { _lexer = new CssLexer( characterStream( source ) ); return parsePropertyValueInternal(); } public CssValue parsePropertyValue( String source ) throws CssParseException, IOException { _lexer = new CssLexer( source ); return parsePropertyValueInternal(); } public void parseRule( InputSource source ) throws CssParseException, IOException { _lexer = new CssLexer( characterStream( source ) ); parseRuleInternal(); } public void parseRule( String source ) throws CssParseException, IOException { _lexer = new CssLexer( source ); parseRuleInternal(); } public List parseSelectors( InputSource source ) throws CssParseException, IOException { _lexer = new CssLexer( characterStream( source ) ); return parseSelectorsInternal(); } public List parseSelectors( String source ) throws CssParseException, IOException { _lexer = new CssLexer( source ); return parseSelectorsInternal(); } public void parseStyleDeclaration( InputSource source ) throws CssParseException, IOException { _lexer = new CssLexer( characterStream( source ) ); parseStyleDeclarationInternal(); } public void parseStyleDeclaration( String source ) throws CssParseException, IOException { _lexer = new CssLexer( source ); parseStyleDeclarationInternal(); } public void parseStyleSheet( InputSource source ) throws CssParseException, IOException { _lexer = new CssLexer( characterStream( source ) ); try { _documentHandler.startDocument( source ); _current = _lexer.next(); switch ( _current ) { case Css.Token.COMMENT: _documentHandler.comment( _lexer.getStringValue() ); } skipSpacesAndCDOCDC(); loop : for( ; ; ) { switch ( _current ) { case Css.Token.EOF: break loop; default: parseRuleSet(); } skipSpacesAndCDOCDC(); } } finally { _documentHandler.endDocument( source ); _lexer = null; } } public void parseStyleSheet( String uri ) throws CssParseException, IOException { parseStyleSheet( new InputSource( uri ) ); } /** * Converts the given input source into a Reader. */ protected Reader characterStream( InputSource source ) throws CssParseException { Reader r = source.getCharacterStream(); if( r == null ) { InputStream is = source.getByteStream(); if( is != null ) { r = characterStream( source, is ); } else { String uri = source.getUri(); if( uri != null ) { try { URL url = new URL( uri ); is = url.openStream(); r = characterStream( source, is ); } catch( IOException e ) { throw new CssParseException( e ); } } else { throw new CssParseException( formatMessage( "empty.source", null ) ); } } } return r; } /** * Converts the given input stream into a Reader. */ protected Reader characterStream( InputSource source, InputStream is ) { _documentUri = source.getUri(); if( _documentUri == null ) _documentUri = ""; return new InputStreamReader( is ); } protected CssParseException createCssParseException( String key ) { return createCssParseException( key, null ); } protected CssParseException createCssParseException( String key, Object[] params ) { /* * return new CssParseException(formatMessage(key, params), * _documentURI, * _lexer.getLine(), * _lexer.getColumn()); */ return new CssParseException( formatMessage( key, params ), _lexer.getLine(), _lexer.getColumn() ); } /** * Converts the current lexical unit to a dimension. */ protected CssValue dimension( boolean positive, CssValue prev ) throws CssParseException { try { float sgn = ( positive ) ? 1 : -1; String val = _lexer.getStringValue(); int i; loop : for( i = 0; i < val.length(); i++ ) { switch ( val.charAt( i ) ) { default: break loop; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': } } nextIgnoreSpaces(); return CssDimension.createDimension( ( sgn * Float.parseFloat( val.substring( 0, i ) ) ), val.substring( i ), prev ); } catch( NumberFormatException e ) { throw createCssParseException( "number.format" ); } } /** * Converts a hash unit to a RGB color. */ protected CssValue hexcolor( CssValue prev ) throws CssParseException { String val = _lexer.getStringValue(); int len = val.length(); CssValue params = null; switch ( len ) { case 3: char rc = Character.toLowerCase( val.charAt( 0 ) ); char gc = Character.toLowerCase( val.charAt( 1 ) ); char bc = Character.toLowerCase( val.charAt( 2 ) ); if( !CssCharUtils.isHexadecimal( rc ) || !CssCharUtils.isHexadecimal( gc ) || !CssCharUtils.isHexadecimal( bc ) ) { throw createCssParseException( "rgb.color", new Object[]{val} ); } int t; int r = t = ( rc >= '0' && rc <= '9' ) ? rc - '0' : rc - 'a' + 10; t <<= 4; r |= t; int g = t = ( gc >= '0' && gc <= '9' ) ? gc - '0' : gc - 'a' + 10; t <<= 4; g |= t; int b = t = ( bc >= '0' && bc <= '9' ) ? bc - '0' : bc - 'a' + 10; t <<= 4; b |= t; params = CssInteger.createInteger( r, null ); CssValue tmp; tmp = CssOperator.createComma( params ); tmp = CssInteger.createInteger( g, tmp ); tmp = CssOperator.createComma( tmp ); tmp = CssInteger.createInteger( b, tmp ); break; case 6: char rc1 = Character.toLowerCase( val.charAt( 0 ) ); char rc2 = Character.toLowerCase( val.charAt( 1 ) ); char gc1 = Character.toLowerCase( val.charAt( 2 ) ); char gc2 = Character.toLowerCase( val.charAt( 3 ) ); char bc1 = Character.toLowerCase( val.charAt( 4 ) ); char bc2 = Character.toLowerCase( val.charAt( 5 ) ); if( !CssCharUtils.isHexadecimal( rc1 ) || !CssCharUtils.isHexadecimal( rc2 ) || !CssCharUtils.isHexadecimal( gc1 ) || !CssCharUtils.isHexadecimal( gc2 ) || !CssCharUtils.isHexadecimal( bc1 ) || !CssCharUtils.isHexadecimal( bc2 ) ) { throw createCssParseException( "rgb.color" ); } r = ( rc1 >= '0' && rc1 <= '9' ) ? rc1 - '0' : rc1 - 'a' + 10; r <<= 4; r |= ( rc2 >= '0' && rc2 <= '9' ) ? rc2 - '0' : rc2 - 'a' + 10; g = ( gc1 >= '0' && gc1 <= '9' ) ? gc1 - '0' : gc1 - 'a' + 10; g <<= 4; g |= ( gc2 >= '0' && gc2 <= '9' ) ? gc2 - '0' : gc2 - 'a' + 10; b = ( bc1 >= '0' && bc1 <= '9' ) ? bc1 - '0' : bc1 - 'a' + 10; b <<= 4; b |= ( bc2 >= '0' && bc2 <= '9' ) ? bc2 - '0' : bc2 - 'a' + 10; params = CssInteger.createInteger( r, null ); tmp = CssOperator.createComma( params ); tmp = CssInteger.createInteger( g, tmp ); tmp = CssOperator.createComma( tmp ); tmp = CssInteger.createInteger( b, tmp ); break; default: throw createCssParseException( "rgb.color", new Object[]{val} ); } nextIgnoreSpaces(); return CssBuiltInFunction.createRgb( params, prev ); } /** * Advances to the next token, ignoring comments. */ protected int next() { try { for( ; ; ) { _lexer.clearBuffer(); _current = _lexer.next(); if( _current == Css.Token.COMMENT ) _documentHandler.comment( _lexer.getStringValue() ); else break; } return _current; } catch( CssParseException e ) { reportError( e ); return _current; } } /** * Advances to the next token and skip the spaces, ignoring comments. */ protected int nextIgnoreSpaces() { try { loop : for( ; ; ) { _lexer.clearBuffer(); _current = _lexer.next(); switch ( _current ) { case Css.Token.COMMENT: _documentHandler.comment( _lexer.getStringValue() ); break; default: break loop; case Css.Token.SPACE: } } return _current; } catch( CssParseException e ) { _errorHandler.error( createCssParseException( e.getMessage() ) ); return _current; } } /** * Converts the current lexical unit to a float. */ protected float number( boolean positive ) throws CssParseException { try { float sgn = ( positive ) ? 1 : -1; String val = _lexer.getStringValue(); nextIgnoreSpaces(); return sgn * Float.parseFloat( val ); } catch( NumberFormatException e ) { throw createCssParseException( "number.format" ); } } /** * Parses a CSS2 expression. * *@param lex The type of the current lexical unit. */ protected CssValue parseExpression( boolean param ) throws CssParseException { CssValue result = parseTerm( null ); CssValue curr = result; for( ; ; ) { boolean op = false; switch ( _current ) { case Css.Token.COMMA: op = true; curr = CssOperator.createComma( curr ); nextIgnoreSpaces(); break; case Css.Token.DIVIDE: op = true; curr = CssOperator.createSlash( curr ); nextIgnoreSpaces(); } if( param ) { if( _current == Css.Token.RIGHT_BRACE ) { if( op ) throw createCssParseException( "token", new Object[]{new Integer( _current )} ); return result; } curr = parseTerm( curr ); } else { switch ( _current ) { case Css.Token.IMPORTANT_SYMBOL: case Css.Token.SEMI_COLON: case Css.Token.RIGHT_CURLY_BRACE: case Css.Token.EOF: if( op ) throw createCssParseException( "token", new Object[]{new Integer( _current )} ); return result; default: curr = parseTerm( curr ); } } } } /** * Parses a CSS2 function. */ protected CssValue parseFunction( boolean positive, CssValue prev ) throws CssParseException { String name = _lexer.getStringValue(); nextIgnoreSpaces(); CssValue params = parseExpression( true ); if( _current != Css.Token.RIGHT_BRACE ) throw createCssParseException( "token", new Object[]{new Integer( _current )} ); nextIgnoreSpaces(); predefined : switch ( name.charAt( 0 ) ) { case 'r': case 'R': CssValue lu; if( name.equalsIgnoreCase( "rgb" ) ) { lu = params; if( lu == null ) break; switch ( lu.getType() ) { default: break predefined; case CssValue.INTEGER: case CssValue.PERCENTAGE: lu = lu.getNext(); } if( lu == null ) break; switch ( lu.getType() ) { default: break predefined; case CssValue.OPERATOR_COMMA: lu = lu.getNext(); } if( lu == null ) break; switch ( lu.getType() ) { default: break predefined; case CssValue.INTEGER: case CssValue.PERCENTAGE: lu = lu.getNext(); } if( lu == null ) break; switch ( lu.getType() ) { default: break predefined; case CssValue.OPERATOR_COMMA: lu = lu.getNext(); } if( lu == null ) break; switch ( lu.getType() ) { default: break predefined; case CssValue.INTEGER: case CssValue.PERCENTAGE: lu = lu.getNext(); } if( lu != null ) break; return CssBuiltInFunction.createRgb( params, prev ); } } return CssUserFunction.createFunction( name, params, prev ); } /** * Parses property value using the current scanner. */ protected CssValue parsePropertyValueInternal() throws CssParseException, IOException { nextIgnoreSpaces(); CssValue exp = null; try { exp = parseExpression( false ); } catch( CssParseException e ) { reportError( e ); throw e; } _lexer = null; if( _current != Css.Token.EOF ) _errorHandler.fatal( createCssParseException( "eof.expected" ) ); return exp; } /** * Parses a rule. */ protected void parseRule() { parseRuleSet(); } /** * Parses a rule using the current scanner. */ protected void parseRuleInternal() throws CssParseException, IOException { nextIgnoreSpaces(); parseRule(); _lexer = null; } /** * Parses a ruleset. */ protected void parseRuleSet() { List sl = null; try { sl = parseSelectorList(); } catch( CssParseException e ) { reportError( e ); return; } try { _documentHandler.startSelector( sl ); if( _current != Css.Token.LEFT_CURLY_BRACE ) { reportError( "left.curly.brace" ); if( _current == Css.Token.RIGHT_CURLY_BRACE ) nextIgnoreSpaces(); } else { nextIgnoreSpaces(); try { parseStyleDeclaration( true ); } catch( CssParseException e ) { reportError( e ); } } } finally { _documentHandler.endSelector( sl ); } } /** * Parses a selector. */ protected Selector parseSelector() throws CssParseException { return parseSimpleSelector(); } /** * Parses a selector list */ protected List parseSelectorList() throws CssParseException { ArrayList selectors = new ArrayList(); selectors.add( parseSelector() ); for( ; ; ) { if( _current != Css.Token.COMMA ) return selectors; nextIgnoreSpaces(); selectors.add( parseSelector() ); } } /** * Parses selectors using the current scanner. */ protected List parseSelectorsInternal() throws CssParseException, IOException { nextIgnoreSpaces(); List ret = parseSelectorList(); _lexer = null; return ret; } /** * Parses a simple selector. */ protected Selector parseSimpleSelector() throws CssParseException { Selector result; switch ( _current ) { case Css.Token.IDENTIFIER: result = _selectorFactory.createElementSelector( _lexer.getStringValue() ); next(); break; case Css.Token.ANY: next(); // fall through to default branch; no break default: result = _selectorFactory.createAnySelector(); } Condition cond = null; switch ( _current ) { case Css.Token.HASH: cond = _conditionFactory.createIdCondition( _lexer.getStringValue() ); next(); break; case Css.Token.DOT: if( next() != Css.Token.IDENTIFIER ) throw createCssParseException( "identifier" ); cond = _conditionFactory.createClassCondition( _lexer.getStringValue() ); next(); break; } skipSpaces(); if( cond != null ) result = _selectorFactory.createConditionalSelector( result, cond ); return result; } /** * Parses the given reader. */ protected void parseStyleDeclaration( boolean inSheet ) throws CssParseException { for( ; ; ) { switch ( _current ) { case Css.Token.EOF: if( inSheet ) throw createCssParseException( "eof" ); return; case Css.Token.RIGHT_CURLY_BRACE: if( !inSheet ) throw createCssParseException( "eof.expected" ); nextIgnoreSpaces(); return; case Css.Token.SEMI_COLON: nextIgnoreSpaces(); continue; default: throw createCssParseException( "identifier" ); case Css.Token.IDENTIFIER: } String name = _lexer.getStringValue(); if( nextIgnoreSpaces() != Css.Token.COLON ) throw createCssParseException( "colon" ); nextIgnoreSpaces(); CssValue exp = null; try { exp = parseExpression( false ); } catch( CssParseException e ) { reportError( e ); } if( exp != null ) { boolean important = false; if( _current == Css.Token.IMPORTANT_SYMBOL ) { important = true; nextIgnoreSpaces(); } _documentHandler.property( name, exp, important ); } } } /** * Parses a style declaration using the current scanner. */ protected void parseStyleDeclarationInternal() throws CssParseException, IOException { nextIgnoreSpaces(); try { parseStyleDeclaration( false ); } catch( CssParseException e ) { reportError( e ); } finally { _lexer = null; } } /** * Parses a CSS2 term. */ protected CssValue parseTerm( CssValue prev ) throws CssParseException { boolean plus = true; boolean sgn = false; switch ( _current ) { case Css.Token.MINUS: plus = false; case Css.Token.PLUS: next(); sgn = true; default: switch ( _current ) { case Css.Token.INTEGER: int s = ( plus ) ? 1 : -1; int val = s * Integer.parseInt( _lexer.getStringValue() ); nextIgnoreSpaces(); return CssInteger.createInteger( val, prev ); case Css.Token.REAL: return CssFloat.createReal( number( plus ), prev ); case Css.Token.PERCENTAGE: return CssFloat.createPercentage( number( plus ), prev ); case Css.Token.PT: return CssFloat.createPoint( number( plus ), prev ); case Css.Token.PC: return CssFloat.createPica( number( plus ), prev ); case Css.Token.PX: return CssFloat.createPixel( number( plus ), prev ); case Css.Token.CM: return CssFloat.createCentimeter( number( plus ), prev ); case Css.Token.MM: return CssFloat.createMillimeter( number( plus ), prev ); case Css.Token.IN: return CssFloat.createInch( number( plus ), prev ); case Css.Token.EM: return CssFloat.createEm( number( plus ), prev ); case Css.Token.EX: return CssFloat.createEx( number( plus ), prev ); case Css.Token.DIMENSION: return dimension( plus, prev ); case Css.Token.FUNCTION: return parseFunction( plus, prev ); } if( sgn ) throw createCssParseException( "token", new Object[]{new Integer( _current )} ); } switch ( _current ) { case Css.Token.STRING: String val = _lexer.getStringValue(); nextIgnoreSpaces(); return CssString.createString( val, prev ); case Css.Token.IDENTIFIER: val = _lexer.getStringValue(); nextIgnoreSpaces(); if( val.equalsIgnoreCase( "inherit" ) ) return CssOperator.createInherit( prev ); else return CssString.createIdent( val, prev ); case Css.Token.URI: val = _lexer.getStringValue(); nextIgnoreSpaces(); return CssString.createUri( val, prev ); case Css.Token.HASH: return hexcolor( prev ); default: throw createCssParseException( "token", new Object[]{new Integer( _current )} ); } } /** * Reports a parsing error. */ protected void reportError( String key ) { reportError( key, null ); } /** * Reports a parsing error. */ protected void reportError( String key, Object[] params ) { reportError( createCssParseException( key, params ) ); } /** * Reports a parsing error. */ protected void reportError( CssParseException e ) { _errorHandler.error( e ); int cbraces = 1; for( ; ; ) { switch ( _current ) { case Css.Token.EOF: return; case Css.Token.SEMI_COLON: case Css.Token.RIGHT_CURLY_BRACE: if( --cbraces == 0 ) { nextIgnoreSpaces(); return; } case Css.Token.LEFT_CURLY_BRACE: cbraces++; } nextIgnoreSpaces(); } } /** * Skips the white spaces. */ protected int skipSpaces() { int lex = _lexer.getType(); while( lex == Css.Token.SPACE ) { lex = next(); } return lex; } /** * Skips the white spaces and CDO/CDC untis. */ protected int skipSpacesAndCDOCDC() { loop : for( ; ; ) { switch ( _current ) { default: break loop; case Css.Token.COMMENT: case Css.Token.SPACE: case Css.Token.CDO: case Css.Token.CDC: } _lexer.clearBuffer(); next(); } return _current; } }
|
CssParser |
|