//
// $Id$
// From Philippe Le Hegaret (Philippe.Le_Hegaret@sophia.inria.fr)
//
// (c) COPYRIGHT MIT, ERCIM and Keio, 2003.
// Please first read the full copyright statement in file COPYRIGHT.html
/*
This class is the front end of the CSS parser
*/
package org.w3c.css.parser;
import org.w3c.css.atrules.css.AtRuleImport;
import org.w3c.css.atrules.css.AtRuleMedia;
import org.w3c.css.atrules.css.AtRuleNamespace;
import org.w3c.css.atrules.css.media.MediaFeature;
import org.w3c.css.css.StyleSheetOrigin;
import org.w3c.css.parser.analyzer.CssParser;
import org.w3c.css.parser.analyzer.CssParserTokenManager;
import org.w3c.css.parser.analyzer.ParseException;
import org.w3c.css.parser.analyzer.TokenMgrError;
import org.w3c.css.properties.PropertiesLoader;
import org.w3c.css.properties.css.CssProperty;
import org.w3c.css.properties.css3.CssCustomProperty;
import org.w3c.css.util.ApplContext;
import org.w3c.css.util.CssVersion;
import org.w3c.css.util.HTTPURL;
import org.w3c.css.util.InvalidParamException;
import org.w3c.css.util.Util;
import org.w3c.css.util.WarningParamException;
import org.w3c.css.util.Warnings;
import org.w3c.css.values.CssExpression;
import org.w3c.css.values.CssValue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.ArrayList;
/**
* This class is a front end of the CSS1 parser.
*
*
* Example:
*
* CssFouffa parser =
* new CssFouffa(new URL("http://www.w3.org/drafts.css"));
* CssValidatorListener myListener = new MyParserListener();
*
* parser.addListener(myListener);
* parser.parseStyle();
*
*
* @version $Revision$
*/
public final class CssFouffa extends CssParser {
// all properties
CssPropertyFactory properties = null;
// all listeners
ArrayList listeners;
// all errors
Errors errors;
// origin of the style sheet
int origin;
ArrayList visited = null;
/**
* Create a new CssFouffa with a data input and a begin line number.
*
* @param ac The validation context
* @param reader The data stream reader
* @param file The source file (use for errors, warnings and import)
* @param beginLine The begin line number in the file. (used for HTML for example)
* @throws IOException if an I/O error occurs.
*/
public CssFouffa(ApplContext ac, Reader reader, URL file, int beginLine)
throws IOException {
super(reader);
if (ac.getOrigin() == -1) {
setOrigin(StyleSheetOrigin.AUTHOR); // default is user
} else {
setOrigin(ac.getOrigin()); // default is user
}
ac.setFrame(new Frame(this, file.toString(), beginLine,
ac.getWarningLevel()));
setApplContext(ac);
// @@this is a default media ...
/*
* AtRuleMedia media = new AtRuleMedia();
*
* if (ac.getMedium() == null) { try { media.addMedia("all", ac); }
* catch (InvalidParamException e) {} //ignore } else { try {
* media.addMedia(ac.getMedium(), ac); } catch (Exception e) {
* System.err.println(e.getMessage()); try { media.addMedia("all", ac); }
* catch (InvalidParamException ex) {} //ignore } } setAtRule(media);
*/
setURL(file);
if (Util.onDebug) {
System.err.println("[DEBUG] CSS version " + ac.getCssVersionString() +
" medium " + ac.getMedium() + " at-rule "
+ getAtRule() + " profile " + ac.getProfileString());
}
// load the CssStyle
String spec = ac.getPropertyKey();
String classStyle;
classStyle = PropertiesLoader.config.getProperty(spec);
if (classStyle == null) {
spec = CssVersion.getDefault().toString();
classStyle = PropertiesLoader.config.getProperty(spec);
}
Class style;
try {
style = Class.forName(classStyle);
ac.setCssSelectorsStyle(style);
} catch (ClassNotFoundException e) {
System.err.println("org.w3c.css.parser.CssFouffa: couldn't" +
" load the style");
e.printStackTrace();
}
properties = new CssPropertyFactory(spec);
listeners = new ArrayList();
}
/**
* Create a new CssFouffa with a data input and a begin line number.
*
* @param input data input
* @param file The source file (use for errors, warnings and import)
* @param beginLine The begin line number in the file. (used for HTML for example)
* @throws IOException if an I/O error occurs.
*/
public CssFouffa(ApplContext ac, InputStream input, String charset,
URL file, int beginLine)
throws IOException {
this(ac, new InputStreamReader(input, (charset == null) ?
"iso-8859-1" : charset), file, beginLine);
}
/**
* Create a new CssFouffa with a data input.
*
* @param input data input
* @param file The source file (use for errors, warnings and import)
* @throws IOException if an I/O error occurs.
*/
public CssFouffa(ApplContext ac, InputStream input, URL file)
throws IOException {
this(ac, input, (ac.getCharsetForURL(file) != null) ?
ac.getCharsetForURL(file) : "iso-8859-1", file, 0);
}
/**
* Create a new CssFouffa.
*
* @param file The source file (use for data input, errors, warnings and
* import)
* @throws IOException if an I/O error occurs.
*/
public CssFouffa(ApplContext ac, URL file) throws IOException {
this(ac, HTTPURL.getConnection(file, ac));
}
/**
* Create a new CssFouffa. internal, to get the URLCOnnection and fill the
* URL with the relevant one
*/
private CssFouffa(ApplContext ac, URLConnection uco) throws IOException {
this(ac, HTTPURL.getInputStream(ac, uco),
HTTPURL.getCharacterEncoding(ac, uco), uco.getURL(), 0);
String httpCL = uco.getHeaderField("Content-Location");
if (httpCL != null) {
setURL(HTTPURL.getURL(getURL(), httpCL));
}
}
/**
* Create a new CssFouffa. Used by handleImport.
*
* @param in The source input stream (use for data input, errors,
* warnings and import)
* @param listeners Works with this listeners
* @throws IOException if an I/O error occurs.
*/
private CssFouffa(ApplContext ac, InputStream in, URL url,
ArrayList listeners,
ArrayList urlvisited,
CssPropertyFactory cssfactory, boolean mode)
throws IOException {
this(ac, in, ac.getCharsetForURL(url), url, 0);
this.visited = urlvisited;
setURL(url);
ac.setFrame(new Frame(this, url.toString(), ac.getWarningLevel()));
setApplContext(ac);
this.listeners = listeners;
this.properties = cssfactory;
this.mode = mode;
}
private void ReInit(ApplContext ac, InputStream input,
URL file, Frame frame) {
// reinitialize the parser with a new data input
// and a new frame for errors and warnings
super.ReInitWithAc(input, ac, ac.getCharsetForURL(file));
// @@this is a default media ...
// AtRuleMedia media;
// if ("css1".equals(ac.getCssVersionString())) {
// media = new AtRuleMediaCSS1();
// } else if ("css2".equals(ac.getCssVersionString())) {
// media = new AtRuleMedia();
// } else {
// media = new AtRuleMedia();
// }
/*
* if (ac.getMedium() == null) { try { media.addMedia("all", ac); }
* catch (InvalidParamException e) {} //ignore } else { try {
* media.addMedia(ac.getMedium(), ac); } catch (Exception e) {
* System.err.println(e.getMessage()); try { media.addMedia("all", ac); }
* catch (InvalidParamException ex) {} //ignore } } setAtRule(media);
*/
setURL(file);
if (Util.onDebug) {
System.err.println("[DEBUG] CSS version " + ac.getCssVersionString() + " medium " + ac.getMedium() + " profile "
+ ac.getProfileString());
}
String spec = ac.getPropertyKey();
// load the CssStyle
String classStyle = PropertiesLoader.config.getProperty(spec);
if (classStyle == null) {
spec = CssVersion.getDefault().toString();
classStyle = PropertiesLoader.config.getProperty(spec);
}
Class style;
try {
style = Class.forName(classStyle);
ac.setCssSelectorsStyle(style);
} catch (ClassNotFoundException e) {
System.err.println("org.w3c.css.parser.CssFouffa: couldn't" + " load the style");
e.printStackTrace();
}
properties = new CssPropertyFactory(spec);
// loadConfig(ac.getCssVersionString(), ac.getProfileString());
}
/**
* Reinitializes a new CssFouffa with a data input and a begin line number.
*
* @param input data input
* @param file The source file (use for errors, warnings and import)
* @param beginLine The begin line number in the file. (used for HTML for example)
* @throws IOException if an I/O error occurs.
*/
public void ReInit(ApplContext ac, InputStream input, URL file,
int beginLine)
throws IOException {
Frame f = new Frame(this, file.toString(), beginLine,
ac.getWarningLevel());
ac.setFrame(f);
ReInit(ac, input, file, f);
}
/**
* Reinitializes a new CssFouffa with a data input.
*
* @param input data input
* @param file The source file (use for errors, warnings and import)
* @throws IOException if an I/O error occurs.
*/
public void ReInit(ApplContext ac, InputStream input, URL file)
throws IOException {
Frame f = new Frame(this, file.toString(), ac.getWarningLevel());
ac.setFrame(f);
ReInit(ac, input, file, f);
}
/**
* Reinitializes a new CssFouffa.
*
* @param file The source file (use for data input, errors, warnings and
* import)
* @throws IOException if an I/O error occurs.
*/
public void ReInit(ApplContext ac, URL file) throws IOException {
InputStream is;
URL url;
Frame f;
f = new Frame(this, file.toString(), ac.getWarningLevel());
ac.setFrame(f);
if (ac.isInputFake()) {
is = ac.getFakeInputStream(file);
url = file;
} else {
URLConnection urlC = HTTPURL.getConnection(file, ac);
is = HTTPURL.getInputStream(ac, urlC);
url = urlC.getURL();
}
ReInit(ac, is, url, f);
}
/**
* Set the attribute origin
*
* @param origin the new value for the attribute
*/
private final void setOrigin(int origin) {
this.origin = origin;
}
/**
* Returns the attribute origin
*
* @return the value of the attribute
*/
public final int getOrigin() {
return origin;
}
/**
* Adds a listener to the parser.
*
* @param listener The listener
* @see org.w3c.css.parser.CssValidatorListener
*/
public final void addListener(CssValidatorListener listener) {
listeners.add(listener);
}
/**
* Removes a listener from the parser
*
* @param listener The listener
* @see org.w3c.css.parser.CssValidatorListener
*/
public final void removeListener(CssValidatorListener listener) {
listeners.remove(listener);
}
/**
* Parse the style sheet. This is the main function of this parser.
*
*
* Example:
*
* CssFouffa parser = new CssFouffa(new
* URL("http://www.w3.org/drafts.css"));
* CssValidatorListener myListener = new MyParserListener();
*
* parser.addListener(myListener);
* parser.parseStyle();
*
*
* @see org.w3c.css.parser.CssFouffa#addListener
*/
public void parseStyle() {
try {
parserUnit();
} catch (TokenMgrError e) {
throw e;
} catch (Throwable e) {
if (Util.onDebug) {
e.printStackTrace();
}
RuntimeException ne = new RuntimeException(e.getMessage());
ne.fillInStackTrace();
throw (ne);
}
Errors errors = ac.getFrame().getErrors();
Warnings warnings = ac.getFrame().getWarnings();
// That's all folks, notify all errors and warnings
for (CssValidatorListener listener : listeners) {
listener.notifyErrors(errors);
listener.notifyWarnings(warnings);
}
}
/**
* Call the namespace declaration statement
*
* @param url, the style sheet where this declaration statement appears.
* @param prefix, the namespace prefix
* @param nsname, the file/url name in the declaration statement
* @param is_url, if the nsname is a file or an url
*/
public void handleNamespaceDeclaration(URL url, String prefix,
String nsname,
boolean is_url) {
AtRuleNamespace nsrule = new AtRuleNamespace(prefix, nsname, is_url);
newAtRule(nsrule);
endOfAtRule();
// add the NS in the global context definition
ac.setNamespace(url, prefix, nsname);
}
/**
* Call by the import statement.
*
* @param url The style sheet where this import statement appears.
* @param file the file name in the import statement
*/
public void handleImport(URL url, String file, boolean is_url,
AtRuleMedia media) {
// CssError cssError = null;
AtRuleImport importrule = new AtRuleImport(file, is_url, media);
newAtRule(importrule);
endOfAtRule();
URL importedURL;
try {
importedURL = HTTPURL.getURL(url, file);
} catch (MalformedURLException mue) {
if (!Util.noErrorTrace) {
ac.getFrame()
.addError(new CssError(getSourceFile(), getBeginLine(),
getBeginColumn(), getEndLine(), getEndColumn(),
mue));
}
return;
}
// add it to the list of linked URIs
// only if it was a string (otherwise the URI is already there
// as CssURL contains code to add it directly
if (!is_url) {
ac.addLinkedURI(importedURL);
}
// check if we need to follow it
if (!ac.followlinks()) {
// TODO add a warning ?
return;
}
//if it's not permitted to import... (direct input)
if (url.getProtocol().equals("file")) {
ac.getFrame().addWarning("unsupported-import");
return;
}
try {
String surl = importedURL.toString();
if (visited == null) {
visited = new ArrayList();
} else {
// check that we didn't already got this URL, or that the
// number of imports is not exploding
if (visited.contains(surl)) {
CssError cerr = new CssError(getSourceFile(),
getBeginLine(), getBeginColumn(), getEndLine(),
getEndColumn(), new Exception(
"Import loop" + " detected in " + surl));
ac.getFrame().addError(cerr);
return;
} else if (visited.size() > 42) {
CssError cerr = new CssError(getSourceFile(),
getBeginLine(), getBeginColumn(), getEndLine(),
getEndColumn(), new Exception("Maximum number"
+ " of imports " + "reached"));
ac.getFrame().addError(cerr);
return;
}
}
ArrayList newVisited = new ArrayList(visited);
newVisited.add(surl);
if (Util.importSecurity) {
throw new FileNotFoundException("[SECURITY] You can't " +
"import URL sorry.");
}
URLConnection importURL = HTTPURL.getConnection(importedURL, ac);
String charset = HTTPURL.getCharacterEncoding(ac, importURL);
if (importURL instanceof HttpURLConnection) {
HttpURLConnection httpURL = (HttpURLConnection) importURL;
String httpCL = httpURL.getHeaderField("Content-Location");
if (httpCL != null) {
importedURL = HTTPURL.getURL(importedURL, httpCL);
}
String mtype = httpURL.getContentType();
if (mtype == null) {
throw new FileNotFoundException(importURL.getURL() +
"No Media Type defined");
} else {
if (mtype.toLowerCase().indexOf("text/html") != -1) {
throw new FileNotFoundException(importURL.getURL() +
": You can't import" +
" an HTML document");
}
}
}
Frame f = ac.getFrame();
try {
CssFouffa cssFouffa = new CssFouffa(ac,
HTTPURL.getInputStream(ac, importURL),
importedURL, listeners, newVisited,
properties, mode);
cssFouffa.setOrigin(getOrigin());
if (!media.isEmpty()) {
cssFouffa.setAtRule(media);
} else {
cssFouffa.setAtRule(getAtRule());
}
cssFouffa.parseStyle();
} finally {
ac.setFrame(f);
}
} catch (Exception e) {
if (!Util.noErrorTrace) {
ac.getFrame()
.addError(new CssError(getSourceFile(), getBeginLine(),
getBeginColumn(), getEndLine(), getEndColumn(),
e));
}
}
}
/**
* Call by the at-rule statement.
*
* @param ident The ident for this at-rule (for example: 'font-face')
* @param string The string representation of this at-rule
*/
public void handleAtRule(String ident, String string) {
if (mode) {
for (CssValidatorListener listener : listeners) {
listener.handleAtRule(ac, ident, string);
}
} else {
if (!Util.noErrorTrace) {
// only @import ; or @import ; are valids in CSS1
ParseException error = new ParseException("at-rules are not implemented in CSS1");
ac.getFrame()
.addError(new CssError(getSourceFile(), getBeginLine(),
getBeginColumn(), getEndLine(), getEndColumn(),
error));
}
}
}
/**
* Treat the "\9" CSS declaration hack as a vendor extension warning
* rather than a fatal error?
*/
private boolean allowBackslash9Hack() {
return this.ac.getTreatCssHacksAsWarnings();
}
/**
* Assign an expression to a property. This function create a new property
* with property
and assign to it the expression with the
* importance.
*
* @param property the name of the property
* @param expression The expression representation of expression
* @param important true if expression id important
* @return a CssProperty
* @throw InvalidParamException
* An error appears during the property creation.
*/
public CssProperty handleDeclaration(String property, CssExpression expression, boolean important)
throws InvalidParamException {
CssProperty prop;
if (Util.onDebug) {
System.err.println("Creating " + property + ": " + expression);
}
if (property.startsWith("--") && (ac.getCssVersion().compareTo(CssVersion.CSS3) >= 0)) {
prop = new CssCustomProperty(ac, property, expression);
// css variable
} else {
final CssValue lastValue = expression.getLastValue();
if (allowBackslash9Hack() && lastValue != null && lastValue.hasBackslash9Hack()) {
expression.markCssHack();
}
try {
prop = properties.createProperty(ac, getAtRule(), property, expression);
} catch (InvalidParamException e) {
throw e;
} catch (Exception e) {
if (Util.onDebug) {
e.printStackTrace();
}
throw new InvalidParamException(e.toString(), ac);
}
// set the importance
if (important) {
prop.setImportant();
}
}
prop.setOrigin(origin);
// set informations for errors and warnings
prop.setInfo(ac.getFrame().getLine(), ac.getFrame().getSourceFile());
// ok, return the new property
return prop;
}
/**
* Assign an expression to a MediaFeature. This function create a new
* media feature with feature
and assign to it the expression
*
* @param feature the name of the media feature
* @param expression The expression representation of expression
* @return a CssProperty
* @throw InvalidParamException
* An error appears during the property creation.
*/
public MediaFeature handleMediaFeature(AtRuleMedia rule, String feature,
CssExpression expression)
throws InvalidParamException {
MediaFeature mf;
if (Util.onDebug) {
System.err.println("Creating MediaFeature" + feature + ": " + expression);
}
try {
mf = properties.createMediaFeature(ac, rule, feature, expression);
} catch (WarningParamException w) {
ac.getFrame().addWarning(w.getMessage(), feature);
return null;
} catch (InvalidParamException e) {
ac.getFrame().addError(new CssError(getSourceFile(), getBeginLine(),
getBeginColumn(), getEndLine(), getEndColumn(), e));
return null;
} catch (Exception e) {
e.printStackTrace();
if (Util.onDebug) {
e.printStackTrace();
}
throw new InvalidParamException(e.toString(), ac);
}
mf.setOrigin(origin);
// set informations for errors and warnings
mf.setInfo(ac.getFrame().getLine(), ac.getFrame().getSourceFile());
// ok, return the new property
return mf;
}
/**
* Parse only a list of declarations. This is useful to parse the
* STYLE
attribute in a HTML document.
*
*
* Example:
*
* CssFouffa parser =
* new CssFouffa(new URL("http://www.w3.org/drafts.css"));
* CssValidatorListener myListener = new MyParserListener();
* CssSelector selector = new CssSelector();
* selector.setElement("H1");
*
* parser.addListener(myListener);
* parser.parseDeclarations(selector);
*
*
* @param context The current context
* @see org.w3c.css.parser.CssFouffa#addListener
*/
public void parseDeclarations(CssSelectors context) {
// here we have an example for reusing the parser.
try {
ArrayList properties = attributeDeclarations();
if (properties != null && properties.size() != 0) {
handleRule(context, properties);
}
} catch (ParseException e) {
if (!Util.noErrorTrace) {
CssParseException ex = new CssParseException(e);
ex.skippedString = "";
ex.property = currentProperty;
ex.contexts = currentContext;
CssError error = new CssError(getSourceFile(), getBeginLine(),
getBeginColumn(), getEndLine(), getEndColumn(), ex);
ac.getFrame().addError(error);
}
}
if (!Util.noErrorTrace) {
Errors errors = ac.getFrame().getErrors();
Warnings warnings = ac.getFrame().getWarnings();
for (CssValidatorListener listener : listeners) {
listener.notifyErrors(errors);
listener.notifyWarnings(warnings);
}
}
}
/**
* used for the output of the stylesheet
*
* @param atRule the
* @rule that just has been found by the parser in the stylesheet, it is
* added to the storage for the output
*/
public void newAtRule(AtRule atRule) {
for (CssValidatorListener listener : listeners) {
listener.newAtRule(atRule);
}
}
/**
* used for the output of the stylesheet
*
* @param charset the
* @charset rule that has been found by the parser
*/
public void addCharSet(String charset) throws ParseException {
for (CssValidatorListener listener : listeners) {
listener.addCharSet(charset);
}
Charset c = null;
try {
c = Charset.forName(charset);
} catch (Exception ex) {
return;
}
boolean charsetFromBOM = ac.isCharsetFromBOM(getURL());
if (charsetFromBOM && ac.getCssVersion().compareTo(CssVersion.CSS3) >= 0) {
// TODO FIXME proper execption type.
throw new ParseException(ac.getMsg().getString("parser.charset"));
// CssError cerr = new CssError(getSourceFile(), getBeginLine(),
// getBeginColumn(), getEndLine(), getEndColumn(), ex);
// ac.getFrame().addError(cerr);
} else {
Charset originalCharset = ac.getCharsetObjForURL(getURL());
if (originalCharset == null) {
ac.setCharsetForURL(getURL(), c);
try {
ReInit(ac, getURL());
} catch (IOException ioex) {
}
} else if (c.compareTo(originalCharset) != 0) {
InvalidParamException ex = new InvalidParamException("conflicting-charset",
new String[]{originalCharset.toString(), charset}, ac);
CssError cerr = new CssError(getSourceFile(), getBeginLine(),
getBeginColumn(), getEndLine(), getEndColumn(), ex);
ac.getFrame().addError(cerr);
}
}
}
/**
* used for the output of the stylesheet the
*
* @rule that had been found before is closed here after the content that's
* in it.
*/
public void endOfAtRule() {
for (CssValidatorListener listener : listeners) {
listener.endOfAtRule();
}
}
/**
* used for the output of the stylesheet
*
* @param important true if the rule was declared important in the stylesheet
*/
public void setImportant(boolean important) {
for (CssValidatorListener listener : listeners) {
listener.setImportant(important);
}
}
/**
* used for the output of the stylesheet
*
* @param selectors a list of one or more selectors to be added to the output
* stylesheet
*/
public void setSelectorList(ArrayList selectors) {
for (CssValidatorListener listener : listeners) {
listener.setSelectorList(selectors);
}
}
/**
* used for the output of the stylesheet
*
* @param properties A list of properties that are following eachother in the
* stylesheet (for example: all properties in an
* @rule)
*/
public void addProperty(ArrayList properties) {
for (CssValidatorListener listener : listeners) {
listener.setProperty(properties);
}
}
/**
* used for the output of the stylesheet used to close a rule when it has
* been read by the parser
*/
public void endOfRule() {
for (CssValidatorListener listener : listeners) {
listener.endOfRule();
}
}
/**
* used for the output of the stylesheet if an error is found this function
* is used to remove the whole stylerule from the memorystructure so that it
* won't appear on the screen
*/
public void removeThisRule() {
for (CssValidatorListener listener : listeners) {
listener.removeThisRule();
}
}
/**
* used for the output of the stylesheet if an error is found this function
* is used to remove the whole
*
* @rule from the memorystructure so that it won't appear on the screen
*/
public void removeThisAtRule() {
for (CssValidatorListener listener : listeners) {
listener.removeThisAtRule();
}
}
/**
* Adds a vector of properties to a selector.
*
* @param selector the selector
* @param declarations Properties to associate with contexts
*/
public void handleRule(CssSelectors selector, ArrayList declarations) {
for (CssValidatorListener listener : listeners) {
listener.handleRule(ac, selector, declarations);
}
}
/**
* Return the class name for a property
*
* @param property the property name ('font-size' for example)
* @return the class name ('org.w3c.css.properties.CssFontSize' for example)
*/
public String getProperty(String property) {
return properties.getProperty(property);
}
/**
* Set the style
*/
public void setStyle(Class style) {
ac.setCssSelectorsStyle(style);
}
public CssFouffa(java.io.InputStream stream) {
super(stream);
properties = new CssPropertyFactory("css21");
// loadConfig("css2", null);
}
public CssFouffa(java.io.Reader stream) {
super(stream);
properties = new CssPropertyFactory("css21");
// loadConfig("css2", null);
}
public CssFouffa(CssParserTokenManager tm) {
super(tm);
properties = new CssPropertyFactory("css21");
// loadConfig("css2", null);
}
public CssFouffa(ApplContext ac, Reader reader) {
super(reader);
this.ac = ac;
properties = new CssPropertyFactory(ac.getPropertyKey());
}
}