|
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 |
|