/*
* $Id: AbstractSACParser.java,v 1.4 2008/03/26 02:17:24 sdanig Exp $
*
* CSS Parser Project
*
* Copyright (C) 2005-2008 David Schweinsberg. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* To contact the authors of the library:
*
* http://cssparser.sourceforge.net/
* mailto:davidsch@users.sourceforge.net
*/
package com.steadystate.css.parser;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.ConditionFactory;
import org.w3c.css.sac.DocumentHandler;
import org.w3c.css.sac.ErrorHandler;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.LexicalUnit;
import org.w3c.css.sac.Locator;
import org.w3c.css.sac.Parser;
import org.w3c.css.sac.SACMediaList;
import org.w3c.css.sac.Selector;
import org.w3c.css.sac.SelectorFactory;
import org.w3c.css.sac.SelectorList;
import com.steadystate.css.parser.selectors.ConditionFactoryImpl;
import com.steadystate.css.parser.selectors.SelectorFactoryImpl;
import com.steadystate.css.sac.DocumentHandlerExt;
import com.steadystate.css.sac.TestCSSParseException;
/**
* Base implementation of {@link Parser}.
*
* @author koch
*/
abstract class AbstractSACParser implements Parser
{
private DocumentHandlerExt documentHandler = null;
private ErrorHandler errorHandler = null;
private InputSource source = null;
private Locale locale = null;
private SelectorFactory selectorFactory = null;
private ConditionFactory conditionFactory = null;
private ResourceBundle sacParserMessages;
protected abstract Token getToken();
protected DocumentHandlerExt getDocumentHandler()
{
if (this.documentHandler == null)
{
this.setDocumentHandler(new HandlerBase());
}
return this.documentHandler;
}
public void setDocumentHandler(DocumentHandler handler)
{
if (handler instanceof DocumentHandlerExt)
{
this.documentHandler = (DocumentHandlerExt) handler;
}
}
protected ErrorHandler getErrorHandler()
{
if (this.errorHandler == null)
{
this.setErrorHandler(new HandlerBase());
}
return this.errorHandler;
}
public void setErrorHandler(ErrorHandler eh)
{
this.errorHandler = eh;
}
protected InputSource getInputSource()
{
return this.source;
}
public void setLocale(Locale locale)
{
if (this.locale != locale)
{
this.sacParserMessages = null;
}
this.locale = locale;
}
protected Locale getLocale()
{
if (this.locale == null)
{
this.setLocale(Locale.getDefault());
}
return this.locale;
}
protected SelectorFactory getSelectorFactory()
{
if (this.selectorFactory == null)
{
this.selectorFactory = new SelectorFactoryImpl();
}
return this.selectorFactory;
}
public void setSelectorFactory(SelectorFactory selectorFactory)
{
this.selectorFactory = selectorFactory;
}
protected ConditionFactory getConditionFactory()
{
if (this.conditionFactory == null)
{
this.conditionFactory = new ConditionFactoryImpl();
}
return this.conditionFactory;
}
public void setConditionFactory(ConditionFactory conditionFactory)
{
this.conditionFactory = conditionFactory;
}
protected ResourceBundle getSACParserMessages()
{
if (this.sacParserMessages == null)
{
this.sacParserMessages = ResourceBundle.getBundle(
"com.steadystate.css.parser.SACParserMessages",
this.getLocale());
}
return this.sacParserMessages;
}
public Locator getLocator()
{
return new LocatorImpl(this.getInputSource().getURI(),
this.getToken() == null ? 0 : this.getToken().beginLine,
this.getToken() == null ? 0 : this.getToken().beginColumn);//this.locator;
}
protected String add_escapes(String str)
{
StringBuilder retval = new StringBuilder();
char ch;
for (int i = 0; i < str.length(); i++)
{
switch (str.charAt(i))
{
case 0 :
continue;
case '\b':
retval.append("\\b");
continue;
case '\t':
retval.append("\\t");
continue;
case '\n':
retval.append("\\n");
continue;
case '\f':
retval.append("\\f");
continue;
case '\r':
retval.append("\\r");
continue;
case '\"':
retval.append("\\\"");
continue;
case '\'':
retval.append("\\\'");
continue;
case '\\':
retval.append("\\\\");
continue;
default:
if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e)
{
String s = "0000" + Integer.toString(ch, 16);
retval.append("\\u"
+ s.substring(s.length() - 4, s.length()));
}
else
{
retval.append(ch);
}
continue;
}
}
return retval.toString();
}
protected CSSParseException toCSSParseException(String key, ParseException e)
{
String messagePattern1 =
this.getSACParserMessages().getString("invalidExpectingOne");
String messagePattern2 =
this.getSACParserMessages().getString("invalidExpectingMore");
int maxSize = 0;
StringBuilder expected = new StringBuilder();
for (int i = 0; i < e.expectedTokenSequences.length; i++)
{
if (maxSize < e.expectedTokenSequences[i].length)
{
maxSize = e.expectedTokenSequences[i].length;
}
for (int j = 0; j < e.expectedTokenSequences[i].length; j++)
{
expected.append(e.tokenImage[e.expectedTokenSequences[i][j]]);
}
//if (e.expectedTokenSequences[i][e.expectedTokenSequences[i].length - 1] != 0)
if (i < e.expectedTokenSequences.length - 1)
{
expected.append(", ");
}
}
StringBuilder invalid = new StringBuilder();
Token tok = e.currentToken.next;
for (int i = 0; i < maxSize; i++)
{
if (i != 0)
{
invalid.append(" ");
}
if (tok.kind == 0)
{
invalid.append(e.tokenImage[0]);
break;
}
invalid.append(this.add_escapes(tok.image));
tok = tok.next;
}
String s = null;
try
{
s = this.getSACParserMessages().getString(key);
}
catch (MissingResourceException ex)
{
s = key;
}
StringBuffer message = new StringBuffer(s);
message.append(' ');
if (e.expectedTokenSequences.length == 1)
{
message.append(MessageFormat.format(
messagePattern1, new Object[] {invalid, expected}));
}
else
{
message.append(MessageFormat.format(
messagePattern2, new Object[] {invalid, expected}));
}
return new TestCSSParseException(message.toString(),
this.getInputSource().getURI(), e.currentToken.next.beginLine,
e.currentToken.next.beginColumn, this.getGrammarUri());
}
protected CSSParseException toCSSParseException(TokenMgrError e)
{
String messagePattern =
this.getSACParserMessages().getString("tokenMgrError");
return new TestCSSParseException(messagePattern,
this.getInputSource().getURI(), 1, 1, this.getGrammarUri());
}
protected CSSParseException createSkipWarning(String key, CSSParseException e)
{
String s = null;
try
{
s = this.getSACParserMessages().getString(key);
}
catch (MissingResourceException ex)
{
s = key;
}
return new TestCSSParseException(s, e.getURI(), e.getLineNumber(),
e.getColumnNumber(), this.getGrammarUri());
}
public void parseStyleSheet(InputSource source)
throws IOException
{
this.source = source;
this.ReInit(getCharStream(source));
try
{
this.styleSheet();
}
catch (ParseException e)
{
this.getErrorHandler().error(
this.toCSSParseException("invalidStyleSheet", e));
}
catch (TokenMgrError e)
{
this.getErrorHandler().error(
this.toCSSParseException(e));
}
catch (CSSParseException e)
{
this.getErrorHandler().error(e);
}
}
public void parseStyleSheet(String uri) throws IOException
{
this.parseStyleSheet(new InputSource(uri));
}
public void parseStyleDeclaration(InputSource source)
throws IOException
{
this.source = source;
this.ReInit(getCharStream(source));
try
{
this.styleDeclaration();
}
catch (ParseException e)
{
this.getErrorHandler().error(
this.toCSSParseException("invalidStyleDeclaration", e));
}
}
public void parseRule(InputSource source) throws IOException
{
this.source = source;
this.ReInit(getCharStream(source));
try
{
this.styleSheetRuleSingle();
}
catch (ParseException e)
{
this.getErrorHandler().error(
this.toCSSParseException("invalidRule", e));
}
}
public SelectorList parseSelectors(InputSource source)
throws IOException
{
this.source = source;
this.ReInit(getCharStream(source));
SelectorList sl = null;
try
{
sl = this.selectorList();
}
catch (ParseException e)
{
this.getErrorHandler().error(
this.toCSSParseException("invalidSelectorList", e));
}
return sl;
}
public LexicalUnit parsePropertyValue(InputSource source)
throws IOException
{
this.source = source;
this.ReInit(getCharStream(source));
LexicalUnit lu = null;
try
{
lu = expr();
}
catch (ParseException e)
{
this.getErrorHandler().error(
this.toCSSParseException("invalidExpr", e));
}
return lu;
}
public boolean parsePriority(InputSource source)
throws IOException
{
this.source = source;
this.ReInit(getCharStream(source));
boolean b = false;
try
{
b = prio();
}
catch (ParseException e)
{
this.getErrorHandler().error(
this.toCSSParseException("invalidPrio", e));
}
return b;
}
public SACMediaList parseMedia(InputSource source) throws IOException
{
this.source = source;
this.ReInit(this.getCharStream(source));
SACMediaListImpl ml = new SACMediaListImpl();
try
{
this.mediaList(ml);
}
catch (ParseException e)
{
this.getErrorHandler().error(
this.toCSSParseException("invalidMediaList", e));
}
catch (CSSParseException e)
{
this.getErrorHandler().error(e);
}
return ml;
}
private CharStream getCharStream(InputSource source)
throws IOException
{
if (source.getCharacterStream() != null)
{
return new ASCII_CharStream(
source.getCharacterStream(), 1, 1);
}
else if (source.getByteStream() != null)
{
return new ASCII_CharStream(new InputStreamReader(
source.getByteStream()), 1, 1);
}
else if (source.getURI() != null)
{
return new ASCII_CharStream(new InputStreamReader(
new URL(source.getURI()).openStream()), 1, 1);
}
return null;
}
public abstract String getParserVersion();
protected abstract String getGrammarUri();
protected abstract void ReInit(CharStream charStream);
protected abstract void styleSheet() throws CSSParseException, ParseException;
protected abstract void styleDeclaration() throws ParseException;
protected abstract void styleSheetRuleSingle() throws ParseException;
protected abstract SelectorList selectorList() throws ParseException;
protected abstract LexicalUnit expr() throws ParseException;
protected abstract boolean prio() throws ParseException;
protected abstract void mediaList(SACMediaListImpl ml) throws ParseException;
protected void handleStartDocument()
{
this.getDocumentHandler().startDocument(this.getInputSource());
}
protected void handleEndDocument()
{
this.getDocumentHandler().endDocument(this.getInputSource());
}
protected void handleIgnorableAtRule(String s)
{
this.getDocumentHandler().ignorableAtRule(s);
}
protected void handleCharset(String characterEncoding)
{
this.getDocumentHandler().charset(characterEncoding);
}
protected void handleImportStyle(String uri, SACMediaList media,
String defaultNamespaceURI)
{
this.getDocumentHandler().importStyle(uri, media, defaultNamespaceURI);
}
protected void handleStartMedia(SACMediaList media)
{
this.getDocumentHandler().startMedia(media);
}
protected void handleMedium(String medium)
{
}
protected void handleEndMedia(SACMediaList media)
{
this.getDocumentHandler().endMedia(media);
}
protected void handleStartPage(String name, String pseudo_page)
{
this.getDocumentHandler().startPage(name, pseudo_page);
}
protected void handleEndPage(String name, String pseudo_page)
{
this.getDocumentHandler().endPage(name, pseudo_page);
}
protected void handleStartFontFace()
{
this.getDocumentHandler().startFontFace();
}
protected void handleEndFontFace()
{
this.getDocumentHandler().endFontFace();
}
protected void handleSelector(Selector selector)
{
}
protected void handleStartSelector(SelectorList selectors)
{
this.getDocumentHandler().startSelector(selectors);
}
protected void handleEndSelector(SelectorList selectors)
{
this.getDocumentHandler().endSelector(selectors);
}
protected void handleProperty(String name, LexicalUnit value,
boolean important)
{
this.getDocumentHandler().property(name, value, important);
}
protected LexicalUnit functionInternal(LexicalUnit prev, Token t,
LexicalUnit params)
{
if (t.image.equalsIgnoreCase("counter(")) {
return LexicalUnitImpl.createCounter(prev, params);
} else if (t.image.equalsIgnoreCase("counters(")) {
return LexicalUnitImpl.createCounters(prev, params);
} else if (t.image.equalsIgnoreCase("attr(")) {
return LexicalUnitImpl.createAttr(prev, params);
} else if (t.image.equalsIgnoreCase("rect(")) {
return LexicalUnitImpl.createRect(prev, params);
}
return LexicalUnitImpl.createFunction(
prev,
t.image.substring(0, t.image.length() - 1),
params);
}
protected LexicalUnit hexcolorInternal(LexicalUnit prev, Token t)
{
// Step past the hash at the beginning
int i = 1;
int r = 0;
int g = 0;
int b = 0;
int len = t.image.length() - 1;
String pattern = this.getSACParserMessages().getString("invalidColor");
try
{
if (len == 3) {
r = Integer.parseInt(t.image.substring(i + 0, i + 1), 16);
g = Integer.parseInt(t.image.substring(i + 1, i + 2), 16);
b = Integer.parseInt(t.image.substring(i + 2, i + 3), 16);
r = (r << 4) | r;
g = (g << 4) | g;
b = (b << 4) | b;
} else if (len == 6) {
r = Integer.parseInt(t.image.substring(i + 0, i + 2), 16);
g = Integer.parseInt(t.image.substring(i + 2, i + 4), 16);
b = Integer.parseInt(t.image.substring(i + 4, i + 6), 16);
} else {
throw new TestCSSParseException(MessageFormat.format(
pattern, new Object[] {t}),
this.getInputSource().getURI(), t.beginLine,
t.beginColumn, this.getGrammarUri());
}
// Turn into an "rgb()"
LexicalUnit lr = LexicalUnitImpl.createNumber(null, r);
LexicalUnit lc1 = LexicalUnitImpl.createComma(lr);
LexicalUnit lg = LexicalUnitImpl.createNumber(lc1, g);
LexicalUnit lc2 = LexicalUnitImpl.createComma(lg);
LexicalUnit lb = LexicalUnitImpl.createNumber(lc2, b);
return LexicalUnitImpl.createRgbColor(prev, lr);
}
catch (NumberFormatException ex)
{
throw new TestCSSParseException(MessageFormat.format(
pattern, new Object[] {t}),
this.getInputSource().getURI(), t.beginLine,
t.beginColumn, ex, this.getGrammarUri());
}
}
int intValue(char op, String s)
{
return ((op == '-') ? -1 : 1) * Integer.parseInt(s);
}
float floatValue(char op, String s) {
return ((op == '-') ? -1 : 1) * Float.parseFloat(s);
}
int getLastNumPos(String s) {
int i;
for (i = 0; i < s.length(); i++) {
if (Character.isLetter(s.charAt(i))) {
break;
}
}
return i - 1;
}
/**
* Unescapes escaped characters in the specified string, according to the
* <a href="http://www.w3.org/TR/CSS21/syndata.html#escaped-characters">CSS specification</a>.
*
* This could be done directly in the parser, but portions of the lexer would have to be moved
* to the parser, meaning that the grammar would no longer match the standard grammar specified
* by the W3C. This would make the parser and lexer much less maintainable.
*/
String unescape(String s) {
int len = s.length();
StringBuffer buf = new StringBuffer(len);
int index = 0;
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 (Character.isWhitespace(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 TestCSSParseException("invalid string " + s,
getLocator(), this.getGrammarUri());
}
} else {
buf.append(c);
}
index++;
}
return buf.toString();
}
}