// /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
%name CSS_Parser
%include {
/*
require_once('CSS/DOM/CSSCharsetRule.php');
require_once('CSS/DOM/CSSImportRule.php');
require_once('CSS/DOM/CSSMediaRule.php');
*/
require_once('Structures/CSS.php');
require_once('Structures/CSS/Value/Generic.php');
require_once('Structures/CSS/Value/Numeric.php');
require_once('Structures/CSS/Value/Unit.php');
require_once('Structures/CSS/Value/Color.php');
require_once('Structures/CSS/Value/Function.php');
require_once('Structures/CSS/Value/Uri.php');
require_once('Structures/CSS/Declaration.php');
require_once('Structures/CSS/Rule/Set.php');
require_once('Structures/CSS/Rule/AtPage.php');
require_once('Structures/CSS/Rule/AtMedia.php');
require_once('Structures/CSS/Rule/AtImport.php');
require_once('Structures/CSS/Selector.php');
require_once('CSS/Parser/Exception/Syntax.php');
}
%declare_class {class CSS_Parser}
%syntax_error {
throw new CSS_Parser_Exception_Syntax();
var_dump($this);
echo "Syntax error\n";
exit;
}
%include_class {
public $result;
public function tokenizeAndParse($lexer, $input)
{
$lexer->setInput($input);
try {
$lastTokenLength = 0;
while ($token = $lexer->nextToken()) {
$this->doParse($token['token'], $token['value']);
$lastTokenLength = strlen($token['value']); // We may need this for error reporting
}
$unexpectedEOF = true;
$this->doParse(0,0);
unset($unexpectedEOF);
return $this->result;
} catch (Exception $e) {
// We have a parsing error. Collect some usefull info
// 1. Was this an EOF error?
if (isset($unexpectedEOF) && $unexpectedEOF) {
require_once('CSS/Parser/Exception/EOF.php');
throw new CSS_Parser_Exception_EOF;
} else {
require_once('CSS/Parser/Exception/Syntax.php');
// 2. No eof error, find out which line/column it ocurred in
$rows = explode("\n", $input);
$errorCharacter = $lexer->_cursor - $lastTokenLength;
$line = 0;
$marker = 0;
while ($marker <= $errorCharacter) {
$marker += strlen($rows[$line]) + 1; // explode removed the \n so we add 1 phantom character
$line++;
}
$line--; // The cycle overshoots the line, so go back one line
$marker -= strlen($rows[$line]) + 1; // and reset the marker
$column = $errorCharacter - $marker; // What's leftover in the marker is the column
$line++; $column++; // Humans tend to start counting by 1, not 0
// 3. Let's grab contextual input for presentation purposes
$contextual = array();
for ($i = 0; $i < 3; $i++) if (array_key_exists($line - 2 + $i, $rows)) $contextual[$i] = $rows[$line - 2 + $i]; else $contextual[$i] = null;
throw new CSS_Parser_Exception_Syntax($line, $column, $lastTokenLength, $contextual);
}
}
}
}
start ::= stylesheet(B). { $this->result = B; }
//stylesheet {{{
// : [ CHARSET_SYM STRING ';' ]?
// [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
// [ [ ruleset | media | page ] [S|CDO|CDC]* ]*
// ;
stylesheet(A) ::= stylesheet_1(B) stylesheet_2(C). {
A = new Structures_CSS(C, B);
}
stylesheet(A) ::= stylesheet_1(B). {
$a = array();
A = new Structures_CSS($a, B);
}
stylesheet(A) ::= stylesheet_2(B). {
A = new Structures_CSS(B);
}
stylesheet(A) ::= . {
A = new Structures_CSS();
}
stylesheet_1(A) ::= stylesheet_charset(B) s_cdo_cdc_plus. { A =& B; }
stylesheet_1(A) ::= s_cdo_cdc_plus. { A = null; }
stylesheet_charset(A) ::= charset_sym_sstar string_sstar(B) SEMICOLON. { A =& B; }
charset_sym_sstar ::= CHARSET_SYM.
charset_sym_sstar ::= CHARSET_SYM splus.
splus ::= SS.
splus ::= SS splus.
string_sstar(A) ::= STRING(B). { A = substr(substr(B, 0, strlen(B)-1), 1); }
string_sstar(A) ::= STRING(B) splus. { A = substr(substr(B, 0, strlen(B)-1), 1); }
s_cdo_cdc_plus ::= s_cdo_cdc.
s_cdo_cdc_plus ::= s_cdo_cdc_plus s_cdo_cdc.
s_cdo_cdc ::= SS|CDO|CDC.
stylesheet_2(A) ::= stylesheet_import(B) stylesheet_ruleset_media_page(C). { A = array_merge(B,C); }
stylesheet_2(A) ::= stylesheet_ruleset_media_page(B). { A =& B; }
stylesheet_import(A) ::= import(B) s_cdo_cdc_plus. { A =& B; }
stylesheet_import(A) ::= import(B). { A =& B; }
stylesheet_import(A) ::= stylesheet_import(B) import(C) s_cdo_cdc_plus. { A = array_merge(B, C); }
stylesheet_import(A) ::= stylesheet_import(B) import(C). { A = array_merge(B, C); }
stylesheet_ruleset_media_page(A) ::= ruleset(B). { A =& B; }
stylesheet_ruleset_media_page(A) ::= media(B). { A =& B; }
stylesheet_ruleset_media_page(A) ::= page(B). { A =& B; }
stylesheet_ruleset_media_page(A) ::= stylesheet_ruleset_media_page(B) ruleset(C). { A = array_merge(B, C); }
stylesheet_ruleset_media_page(A) ::= stylesheet_ruleset_media_page(B) media(C). { A = array_merge(B, C); }
stylesheet_ruleset_media_page(A) ::= stylesheet_ruleset_media_page(B) page(C). { A = array_merge(B, C); }
// }}}
// import {{{
// : IMPORT_SYM S*
// [STRING|URI] S* [ medium [ COMMA S* medium]* ]? ';' S*
// ;
// NOTE: I removed the end S* from the rule, as it conflicts with "import s_cdo_cdc" on the stylesheet rule above
import(A) ::= import_sym_sstar string_or_uri_sstar(B) SEMICOLON. { A = array(new Structures_CSS_Rule_AtImport(B)); }
import(A) ::= import_sym_sstar string_or_uri_sstar(B) medialist(C) SEMICOLON. { A = array(new Structures_CSS_Rule_AtImport(B, C)); }
import_sym_sstar ::= IMPORT_SYM.
import_sym_sstar ::= IMPORT_SYM splus.
string_or_uri_sstar(A) ::= string_sstar(B). { A = B; }
string_or_uri_sstar(A) ::= URI(B) splus. { A = B; }
string_or_uri_sstar(A) ::= URI(B). { A = B; }
medialist(A) ::= medium(B). { A = array(B); }
medialist(A) ::= medialist(B) COMMA medium(C). { A =& B; A[] =& C; }
medialist(A) ::= medialist(B) COMMA splus medium(C). { A =& B; A[] =& C; }
// }}}
//media {{{
// : MEDIA_SYM S* medium [ COMMA S* medium ]* LBRACE S* ruleset* '}' S*
// ;
media(A) ::= media_sym_sstar medialist(B) LBRACE splus rulesetplus(C) RBRACE. {
A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE splus rulesetplus(C) RBRACE splus. {
A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE rulesetplus(C) RBRACE. {
A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE rulesetplus(C) RBRACE splus. {
A = array(new Structures_CSS_Rule_AtMedia(B, C));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE splus RBRACE. {
A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE splus RBRACE splus. {
A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE RBRACE. {
A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
media(A) ::= media_sym_sstar medialist(B) LBRACE RBRACE splus. {
A = array(new Structures_CSS_Rule_AtMedia(B, array()));
}
media_sym_sstar ::= MEDIA_SYM.
media_sym_sstar ::= MEDIA_SYM splus.
rulesetplus(A) ::= ruleset(B). { A =& B; }
rulesetplus(A) ::= rulesetplus(B) ruleset(C). { A =& B; A[] =& C; }
// }}}
//medium {{{
// : IDENT S*
// ;
medium(A) ::= IDENT(B). { A = B; }
medium(A) ::= IDENT(B) splus. { A = B; }
// }}}
//page {{{
// : PAGE_SYM S* pseudo_page? S*
// LBRACE S* declaration [ ';' S* declaration ]* '}' S*
// ;
// NOTE: I removed the end S* from the rule, as it conflicts with "ruleset_media_page s_cdo_cdc"
// on the stylesheet rule above
page(A) ::= ppage(B). {
A = array(new Structures_CSS_Rule_AtPage(B['declarations'], B['pseudo']));
}
ppage(A) ::= pagesym_sstar pseudo_page(B) splus LBRACE declarations(C) RBRACE. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar pseudo_page(B) splus LBRACE declarations(C) RBRACE splus. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar pseudo_page(B) LBRACE declarations(C) RBRACE. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar pseudo_page(B) LBRACE declarations(C) RBRACE splus. { A = array(); A['pseudo'] =& B; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar LBRACE declarations(C) RBRACE. { A = array(); A['pseudo'] = null; A['declarations'] =& C; }
ppage(A) ::= pagesym_sstar LBRACE declarations(C) RBRACE splus. { A = array(); A['pseudo'] = null; A['declarations'] =& C; }
pagesym_sstar ::= PAGE_SYM.
pagesym_sstar ::= PAGE_SYM splus.
declarations(A) ::= sstar_declaration(B). { A = array(B); }
declarations(A) ::= declarations(B) SEMICOLON sstar_declaration(C). { A =& B; A[] =& C; }
sstar_declaration(A) ::= declaration(B). { A =& B; }
sstar_declaration(A) ::= splus declaration(B). { A =& B; }
// }}}
//pseudo_page {{{
// : ':' IDENT
// ;
pseudo_page(A) ::= COLON(B) IDENT(C). { A = B . C; }
// }}}
//operator {{{
// : '/' S* | COMMA S* | /* empty */
// ;
operator(A) ::= SLASH. { A = '/'; }
operator(A) ::= SLASH splus. { A = '/'; }
operator(A) ::= COMMA. { A = ','; }
operator(A) ::= COMMA splus. { A = ','; }
operator(A) ::= . { A = ''; }
// }}}
//combinator {{{
// : PLUS S*
// | GREATER S*
// | S
// ;
combinator(A) ::= GREATER splus. { A = '>'; }
combinator(A) ::= PLUS splus. { A = '+'; }
combinator(A) ::= SS. { A = ' '; }
// }}}
//unary_operator {{{
// : '-' | PLUS
// ;
unary_operator(A) ::= MINUS. { A = '-'; }
unary_operator(A) ::= PLUS. { A = '+'; }
// }}}
//property {{{
// : IDENT S*
// ;
property(A) ::= IDENT(B). { A =& B; }
property(A) ::= IDENT(B) splus. { A =& B; }
// }}}
//ruleset {{{
// : selector [ COMMA S* selector ]*
// LBRACE S* declaration [ ';' S* declaration ]* '}' S*
// ;
ruleset(A) ::= rruleset(B). {
A = array();
$clone = null;
foreach (B['selectors'] as $selector) {
if (!is_null($clone)) B['declarations'] = unserialize($clone);
A[] = new Structures_CSS_Rule_Set($selector, B['declarations']);
if (is_null($clone)) $clone = serialize(B['declarations']);
}
unset($clone);
}
rruleset(A) ::= selectors(B) LBRACE declarations(C) RBRACE splus. { A = array('selectors' => B, 'declarations' => C); }
rruleset(A) ::= selectors(B) LBRACE declarations(C) RBRACE. { A = array('selectors' => B, 'declarations' => C); }
selectors(A) ::= selector(B). { A = array(B); }
selectors(A) ::= selectors(B) comma_sstar selector(C). { A =& B; A[] =& C; }
comma_sstar ::= COMMA.
comma_sstar ::= COMMA splus.
// Extra spec addition!
// ruleset : selector COMMA S* selector LBRACE S*
rruleset(A) ::= selectors(B) LBRACE RBRACE. { A= array('selectors' => B, 'declarations' => array()); }
rruleset(A) ::= selectors(B) LBRACE splus RBRACE. { A= array('selectors' => B, 'declarations' => array()); }
rruleset(A) ::= selectors(B) LBRACE RBRACE splus. { A= array('selectors' => B, 'declarations' => array()); }
rruleset(A) ::= selectors(B) LBRACE splus RBRACE splus. { A= array('selectors' => B, 'declarations' => array()); }
// /Extra spec addition
// }}}
//selector {{{
// : simple_selector [ combinator simple_selector ]*
// ;
selector(A) ::= simple_selector(B). { A =& B; }
selector(A) ::= simple_selector(B) combinator(C) selector(D). { A =& B; A->appendSelector(D, C); }
// }}}
//simple_selector {{{
// : element_name [ HASH | class | attrib | pseudo ]*
// | [ HASH | class | attrib | pseudo ]+
// ;
simple_selector(A) ::= element_name(B). { A= new Structures_CSS_Selector(strtolower(B), Structures_CSS_Selector::C); }
simple_selector(A) ::= element_name(B) hash_class_attrib_pseudo_plus(D). {
A = new Structures_CSS_Selector(strtolower(B), Structures_CSS_Selector::C);
A->appendSelector(D, '');
}
simple_selector(A) ::= hash_class_attrib_pseudo_plus(B). { A =& B; }
hash_class_attrib_pseudo_plus(A) ::= hash_class_attrib_pseudo(B). { A =& B; }
hash_class_attrib_pseudo_plus(A) ::= hash_class_attrib_pseudo_plus(B) hash_class_attrib_pseudo(C). {
A =& B;
A->appendSelector(C, '');
}
hash_class_attrib_pseudo(A) ::= HASH(B). { A = new Structures_CSS_Selector(B, 1000000); }
hash_class_attrib_pseudo(A) ::= class(B). { A = new Structures_CSS_Selector(B, 1000); }
hash_class_attrib_pseudo(A) ::= attrib(B). { A = new Structures_CSS_Selector(B, 1000); }
hash_class_attrib_pseudo(A) ::= pseudo(B). { A = new Structures_CSS_Selector(B, 1000); }
// }}}
//class {{{
// : '.' IDENT
// ;
class(A) ::= DOT IDENT(B). { A = '.' . B; }
// }}}
//element_name {{{
// : IDENT | '*'
// ;
element_name(A) ::= IDENT(B). { A =& B; }
element_name(A) ::= STAR(B). { A =& B; }
//}}}
//attrib {{{
// : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
// [ IDENT | STRING ] S* ]? ']'
// ;
attrib(A) ::= LBRACKET trimmed_ident(B) RBRACKET. { A = '[' . B . ']'; }
attrib(A) ::= LBRACKET trimmed_ident(B) attrib_operator_sstar(C) attrib_operator_rhand_sstar(D) RBRACKET. { A = '[' . B . C . D . ']'; }
attrib(A) ::= LBRACKET trimmed_ident(B) attrib_operator_sstar(C) RBRACKET. { A = '[' . B . C . ']'; }
trimmed_ident(A) ::= IDENT(B). { A =& B; }
trimmed_ident(A) ::= splus IDENT(B). { A =& B; }
trimmed_ident(A) ::= IDENT(B) splus. { A =& B; }
trimmed_ident(A) ::= splus IDENT(B) splus. { A =& B; }
attrib_operator_sstar(A) ::= attrib_operator(B). { A =& B; }
attrib_operator_sstar(A) ::= attrib_operator(B) splus. { A =& B; }
attrib_operator(A) ::= INCLUDES(B). { A =& B; }
attrib_operator(A) ::= DASHMATCH(B). { A =& B; }
attrib_operator(A) ::= EQUAL(B). { A =& B; }
attrib_operator_rhand_sstar(A) ::= attrib_operator_rhand(B). { A =& B; }
attrib_operator_rhand_sstar(A) ::= attrib_operator_rhand(B) splus. { A =& B; }
attrib_operator_rhand(A) ::= STRING(B). { A =& B; }
attrib_operator_rhand(A) ::= IDENT(B). { A =& B; }
// }}}
//pseudo {{{
// : ':' [ IDENT | FUNCTION S* IDENT? S* ')' ]
// ;
pseudo(A) ::= COLON IDENT(B). { A = ':' . B; }
pseudo(A) ::= COLON CSSFUNCTION(B) trimmed_ident(C) RPAREN. { A = ':' . B . C; }
pseudo(A) ::= COLON CSSFUNCTION(B) splus RPAREN. { A =& B; }
pseudo(A) ::= COLON CSSFUNCTION(B) RPAREN. { A =& B; }
// }}}
//declaration {{{
// : property ':' S* expr prio?
// | /* empty */
// ;
declaration(A) ::= ddeclaration(B). {
A = Structures_CSS_Declaration::create(B['property'], B['expressions'], B['priority']);
}
ddeclaration(A) ::= property(B) COLON splus expr(C) prio. { A = array( 'property' => B, 'expressions' => C, 'priority' => true ); }
ddeclaration(A) ::= property(B) COLON splus expr(C). { A = array( 'property' => B, 'expressions' => C, 'priority' => false ); }
ddeclaration(A) ::= property(B) COLON expr(C) prio. { A = array( 'property' => B, 'expressions' => C, 'priority' => true ); }
ddeclaration(A) ::= property(B) COLON expr(C). { A = array( 'property' => B, 'expressions' => C, 'priority' => false ); }
// }}}
//prio {{{
// : IMPORTANT_SYM S*
// ;
prio ::= important_sym_sstar.
important_sym_sstar ::= IMPORTANT_SYM.
important_sym_sstar ::= IMPORTANT_SYM splus.
// }}}
//expr {{{
// : term [ operator term ]*
// ;
expr(A) ::= term(B). { A = array(B); }
expr(A) ::= expr(B) operator(C) term(D). { A =& B; A[] =& D; A[] =& C; }
// }}}
//term {{{
// : unary_operator?
// [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
// TIME S* | FREQ S* ]
// | STRING S* | IDENT S* | URI S* | hexcolor | function
// ;
// NOTE: The reference to DIMENSION in term_nonnumeric is a deviation from standard
term(A) ::= term_numeric(B). { A =& B; }
term(A) ::= term_nonnumeric(B). { A =& B; }
term_numeric(A)::= unary_operator(B) term_numeric_number(C). {
A =& C;
if (B == '-') A->setNumericValue(-1 * A->getNumericValue());
}
term_numeric(A) ::= term_numeric_number(B). { A =& B; }
term_numeric_number(A) ::= NUMBER(B). {
A = new Structures_CSS_Value_Numeric(B);
}
term_numeric_number(A) ::= NUMBER(B) splus. {
A = new Structures_CSS_Value_Numeric(B);
}
term_numeric_number(A) ::= PERCENTAGE(B). {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= PERCENTAGE(B) splus. {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= LENGTH(B). {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= LENGTH(B) splus. {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= EMS(B). {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= EMS(B) splus. {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= EXS(B). {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= EXS(B) splus. {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= ANGLE(B). {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= ANGLE(B) splus. {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= TIME(B). {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= TIME(B) splus. {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= FREQ(B). {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= FREQ(B) splus. {
A = new Structures_CSS_Value_Unit(B);
}
term_numeric_number(A) ::= DIMENSION(B). {
A = Structures_CSS_Value_Generic::create(B);
}
term_numeric_number(A) ::= DIMENSION(B) splus. {
A = Structures_CSS_Value_Generic::create(B);
}
term_nonnumeric(A) ::= STRING(B). {
A = Structures_CSS_Value_Generic::create(B);
}
term_nonnumeric(A) ::= STRING(B) splus. {
A = Structures_CSS_Value_Generic::create(B);
}
term_nonnumeric(A) ::= IDENT(B). {
A = Structures_CSS_Value_Generic::create(B);
}
term_nonnumeric(A) ::= IDENT(B) splus. {
A = Structures_CSS_Value_Generic::create(B);
}
term_nonnumeric(A) ::= URI(B). {
B = preg_replace('/url\((.*)\)/', '\1', B);
if (B[0] == "'" && B[strlen(B) - 1] == "'" ||
B[0] == '"' && B[strlen(B) - 1] == '"') {
B = substr(B, 1, strlen(B) - 2);
B = strtr(B, array('\\"' => '"', "\\'" => "'", '\\\\' => '\\'));
}
A = new Structures_CSS_Value_URI(B);
}
term_nonnumeric(A) ::= URI(B) splus. {
B = preg_replace('/url\((.*)\)/', '\1', B);
if (B[0] == "'" && B[strlen(B) - 1] == "'" ||
B[0] == '"' && B[strlen(B) - 1] == '"') {
B = substr(B, 1, strlen(B) - 2);
B = strtr(B, array('\\"' => '"', "\\'" => "'", '\\\\' => '\\'));
}
A = new Structures_CSS_Value_URI(B);
}
term_nonnumeric(A) ::= hexcolor(B). { A =& B; }
term_nonnumeric(A) ::= function(B). {
if (strtolower(B['name']) == 'rgb' &&
count(B['params']) == 5 &&
B['params'][2] == ',' &&
B['params'][4] == ',' &&
B['params'][0] instanceof Structures_CSS_Value_Numeric &&
B['params'][1] instanceof Structures_CSS_Value_Numeric &&
B['params'][3] instanceof Structures_CSS_Value_Numeric
) {
A = new Structures_CSS_Value_Color(B['params'][0]->getNumericValue(), B['params'][1]->getNumericValue(), B['params'][3]->getNumericValue());
} else {
A =& new Structures_CSS_Value_Function(B);
}
}
// }}}
//hexcolor {{{
// : HASH S*
// ;
hexcolor(A) ::= HASH(B). { A = new Structures_CSS_Value_Color(B); }
hexcolor(A) ::= HASH(B) splus. { A = new Structures_CSS_Value_Color(B); }
// }}}
//function {{{
// : FUNCTION S* expr ')' S*
// ;
function(A) ::= CSSFUNCTION(B) splus expr(C) RPAREN splus. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
function(A) ::= CSSFUNCTION(B) splus expr(C) RPAREN. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
function(A) ::= CSSFUNCTION(B) expr(C) RPAREN splus. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
function(A) ::= CSSFUNCTION(B) expr(C) RPAREN. { A = array( 'name' => strtr(B, array('(' => '')), 'params' => C ); }
// }}}
stylesheet ::= placeholder.
placeholder ::= COMMENT.
//placeholder ::= ANGLE|COMMENT|CSSFUNCTION|DIMENSION|EMS|EXS|FREQ|INCLUDES|INVALID|LBRACE|LENGTH|NUMBER|PERCENTAGE|RBRACE|TIME|URI|FONT_FACE_SYM|ATKEYWORD.
///*
// * There is a constraint on the color that it must
// * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
// * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
// */