/* * CSS parser based on the grammar described at http://www.w3.org/TR/CSS2/grammar.html. * * The parser builds a tree representing the parsed CSS, composed of basic * JavaScript values, arrays and objects (basically JSON). It can be easily * used by various CSS processors, transformers, etc. * * Note that the parser does not handle errors in CSS according to the * specification -- many errors which it should recover from (e.g. malformed * declarations or unexpected end of stylesheet) are simply fatal. This is a * result of straightforward rewrite of the CSS grammar to PEG.js and it should * be fixed sometimes. */ /* ===== Syntactical Elements ===== */ start = stylesheet:stylesheet comment* { return stylesheet; } stylesheet = charset:(CHARSET_SYM STRING ";")? (S / CDO / CDC)* imports:(import (CDO S* / CDC S*)*)* rules:((ruleset / media / page) (CDO S* / CDC S*)*)* { var importsConverted = []; for (var i = 0; i < imports.length; i++) { importsConverted.push(imports[i][0]); } var rulesConverted = []; for (i = 0; i < rules.length; i++) { rulesConverted.push(rules[i][0]); } return { type: "stylesheet", charset: charset !== null ? charset[1] : null, imports: importsConverted, rules: rulesConverted }; } import = IMPORT_SYM S* href:(STRING / URI) S* media:media_list? ";" S* { return { type: "import_rule", href: href, media: media !== null ? media : [] }; } media = MEDIA_SYM S* media:media_list "{" S* rules:ruleset* "}" S* { return { type: "media_rule", media: media, rules: rules }; } media_list = head:medium tail:("," S* medium)* { var result = [head]; for (var i = 0; i < tail.length; i++) { result.push(tail[i][2]); } return result; } medium = ident:IDENT S* { return ident; } page = PAGE_SYM S* qualifier:pseudo_page? "{" S* declarationsHead:declaration? declarationsTail:(";" S* declaration?)* "}" S* { var declarations = declarationsHead !== null ? [declarationsHead] : []; for (var i = 0; i < declarationsTail.length; i++) { if (declarationsTail[i][2] !== null) { declarations.push(declarationsTail[i][2]); } } return { type: "page_rule", qualifier: qualifier, declarations: declarations }; } pseudo_page = ":" ident:IDENT S* { return ident; } operator = "/" S* { return "/"; } / "," S* { return ","; } combinator = "+" S* { return "+"; } / ">" S* { return ">"; } unary_operator = "+" / "-" property = ident:IDENT S* { return ident; } ruleset = selectorsHead:selector selectorsTail:("," S* selector)* "{" S* declarationsHead:declaration? declarationsTail:(";" S* declaration?)* "}" S* { var selectors = [selectorsHead]; for (var i = 0; i < selectorsTail.length; i++) { selectors.push(selectorsTail[i][2]); } var declarations = declarationsHead !== null ? [declarationsHead] : []; for (i = 0; i < declarationsTail.length; i++) { if (declarationsTail[i][2] !== null) { declarations.push(declarationsTail[i][2]); } } return { type: "ruleset", selectors: selectors, declarations: declarations }; } selector = left:simple_selector S* combinator:combinator right:selector { return { type: "selector", combinator: combinator, left: left, right: right }; } / left:simple_selector S* right:selector { return { type: "selector", combinator: " ", left: left, right: right }; } / selector:simple_selector S* { return selector; } simple_selector = element:element_name qualifiers:( id:HASH { return { type: "ID selector", id: id.substr(1) }; } / class / attrib / pseudo )* { return { type: "simple_selector", element: element, qualifiers: qualifiers }; } / qualifiers:( id:HASH { return { type: "ID selector", id: id.substr(1) }; } / class / attrib / pseudo )+ { return { type: "simple_selector", element: "*", qualifiers: qualifiers }; } class = "." class_:IDENT { return { type: "class_selector", "class": class_ }; } element_name = IDENT / '*' attrib = "[" S* attribute:IDENT S* operatorAndValue:( ('=' / INCLUDES / DASHMATCH) S* (IDENT / STRING) S* )? "]" { return { type: "attribute_selector", attribute: attribute, operator: operatorAndValue !== null ? operatorAndValue[0] : null, value: operatorAndValue !== null ? operatorAndValue[2] : null }; } pseudo = ":" value:( name:FUNCTION S* params:(IDENT S*)? ")" { return { type: "function", name: name, params: params !== null ? [params[0]] : [] }; } / IDENT ) { /* * The returned object has somewhat vague property names and values because * the rule matches both pseudo-classes and pseudo-elements (they look the * same at the syntactic level). */ return { type: "pseudo_selector", value: value }; } declaration = property:property ":" S* expression:expr important:prio? { return { type: "declaration", property: property, expression: expression, important: important !== null ? true : false }; } prio = IMPORTANT_SYM S* expr = head:term tail:(operator? term)* { var result = head; for (var i = 0; i < tail.length; i++) { result = { type: "expression", operator: tail[i][0], left: result, right: tail[i][1] }; } return result; } term = operator:unary_operator? value:( EMS S* / EXS S* / LENGTH S* / ANGLE S* / TIME S* / FREQ S* / PERCENTAGE S* / NUMBER S* ) { return { type: "value", value: (operator !== null ? operator : "") + value[0] }; } / value:URI S* { return { type: "uri", value: value }; } / function / hexcolor / value:STRING S* { return { type: "string", value: value }; } / value:IDENT S* { return { type: "ident", value: value }; } function = name:FUNCTION S* params:expr ")" S* { return { type: "function", name: name, params: params }; } hexcolor = value:HASH S* { return { type: "hexcolor", value: value}; } /* ===== Lexical Elements ===== */ /* Macros */ h = [0-9a-fA-F] nonascii = [\x80-\xFF] unicode = "\\" digits:$(h h? h? h? h? h?) ("\r\n" / [ \t\r\n\f])? { return String.fromCharCode(parseInt("0x" + digits)); } escape = unicode / "\\" char_:[^\r\n\f0-9a-fA-F] { return char_; } nmstart = [_a-zA-Z] / nonascii / escape nmchar = [_a-zA-Z0-9-] / nonascii / escape integer = parts:$[0-9]+ { return parseInt(parts); } float = parts:$([0-9]* "." [0-9]+) { return parseFloat(parts); } string1 = '"' chars:([^\n\r\f\\"] / "\\" nl:nl { return nl } / escape)* '"' { return chars.join(""); } string2 = "'" chars:([^\n\r\f\\'] / "\\" nl:nl { return nl } / escape)* "'" { return chars.join(""); } comment = "/*" [^*]* "*"+ ([^/*] [^*]* "*"+)* "/" ident = dash:"-"? nmstart:nmstart nmchars:nmchar* { return (dash !== null ? dash : "") + nmstart + nmchars.join(""); } name = nmchars:nmchar+ { return nmchars.join(""); } num = float / integer string = string1 / string2 url = chars:([!#$%&*-~] / nonascii / escape)* { return chars.join(""); } s = [ \t\r\n\f]+ w = s? nl = "\n" / "\r\n" / "\r" / "\f" A = [aA] / "\\" "0"? "0"? "0"? "0"? "41" ("\r\n" / [ \t\r\n\f])? { return "A"; } / "\\" "0"? "0"? "0"? "0"? "61" ("\r\n" / [ \t\r\n\f])? { return "a"; } C = [cC] / "\\" "0"? "0"? "0"? "0"? "43" ("\r\n" / [ \t\r\n\f])? { return "C"; } / "\\" "0"? "0"? "0"? "0"? "63" ("\r\n" / [ \t\r\n\f])? { return "c"; } D = [dD] / "\\" "0"? "0"? "0"? "0"? "44" ("\r\n" / [ \t\r\n\f])? { return "D"; } / "\\" "0"? "0"? "0"? "0"? "64" ("\r\n" / [ \t\r\n\f])? { return "d"; } E = [eE] / "\\" "0"? "0"? "0"? "0"? "45" ("\r\n" / [ \t\r\n\f])? { return "E"; } / "\\" "0"? "0"? "0"? "0"? "65" ("\r\n" / [ \t\r\n\f])? { return "e"; } G = [gG] / "\\" "0"? "0"? "0"? "0"? "47" ("\r\n" / [ \t\r\n\f])? { return "G"; } / "\\" "0"? "0"? "0"? "0"? "67" ("\r\n" / [ \t\r\n\f])? { return "g"; } / "\\" char_:[gG] { return char_; } H = h:[hH] / "\\" "0"? "0"? "0"? "0"? "48" ("\r\n" / [ \t\r\n\f])? { return "H"; } / "\\" "0"? "0"? "0"? "0"? "68" ("\r\n" / [ \t\r\n\f])? { return "h"; } / "\\" char_:[hH] { return char_; } I = i:[iI] / "\\" "0"? "0"? "0"? "0"? "49" ("\r\n" / [ \t\r\n\f])? { return "I"; } / "\\" "0"? "0"? "0"? "0"? "69" ("\r\n" / [ \t\r\n\f])? { return "i"; } / "\\" char_:[iI] { return char_; } K = [kK] / "\\" "0"? "0"? "0"? "0"? "4" [bB] ("\r\n" / [ \t\r\n\f])? { return "K"; } / "\\" "0"? "0"? "0"? "0"? "6" [bB] ("\r\n" / [ \t\r\n\f])? { return "k"; } / "\\" char_:[kK] { return char_; } L = [lL] / "\\" "0"? "0"? "0"? "0"? "4" [cC] ("\r\n" / [ \t\r\n\f])? { return "L"; } / "\\" "0"? "0"? "0"? "0"? "6" [cC] ("\r\n" / [ \t\r\n\f])? { return "l"; } / "\\" char_:[lL] { return char_; } M = [mM] / "\\" "0"? "0"? "0"? "0"? "4" [dD] ("\r\n" / [ \t\r\n\f])? { return "M"; } / "\\" "0"? "0"? "0"? "0"? "6" [dD] ("\r\n" / [ \t\r\n\f])? { return "m"; } / "\\" char_:[mM] { return char_; } N = [nN] / "\\" "0"? "0"? "0"? "0"? "4" [eE] ("\r\n" / [ \t\r\n\f])? { return "N"; } / "\\" "0"? "0"? "0"? "0"? "6" [eE] ("\r\n" / [ \t\r\n\f])? { return "n"; } / "\\" char_:[nN] { return char_; } O = [oO] / "\\" "0"? "0"? "0"? "0"? "4" [fF] ("\r\n" / [ \t\r\n\f])? { return "O"; } / "\\" "0"? "0"? "0"? "0"? "6" [fF] ("\r\n" / [ \t\r\n\f])? { return "o"; } / "\\" char_:[oO] { return char_; } P = [pP] / "\\" "0"? "0"? "0"? "0"? "50" ("\r\n" / [ \t\r\n\f])? { return "P"; } / "\\" "0"? "0"? "0"? "0"? "70" ("\r\n" / [ \t\r\n\f])? { return "p"; } / "\\" char_:[pP] { return char_; } R = [rR] / "\\" "0"? "0"? "0"? "0"? "52" ("\r\n" / [ \t\r\n\f])? { return "R"; } / "\\" "0"? "0"? "0"? "0"? "72" ("\r\n" / [ \t\r\n\f])? { return "r"; } / "\\" char_:[rR] { return char_; } S_ = [sS] / "\\" "0"? "0"? "0"? "0"? "53" ("\r\n" / [ \t\r\n\f])? { return "S"; } / "\\" "0"? "0"? "0"? "0"? "73" ("\r\n" / [ \t\r\n\f])? { return "s"; } / "\\" char_:[sS] { return char_; } T = [tT] / "\\" "0"? "0"? "0"? "0"? "54" ("\r\n" / [ \t\r\n\f])? { return "T"; } / "\\" "0"? "0"? "0"? "0"? "74" ("\r\n" / [ \t\r\n\f])? { return "t"; } / "\\" char_:[tT] { return char_; } U = [uU] / "\\" "0"? "0"? "0"? "0"? "55" ("\r\n" / [ \t\r\n\f])? { return "U"; } / "\\" "0"? "0"? "0"? "0"? "75" ("\r\n" / [ \t\r\n\f])? { return "u"; } / "\\" char_:[uU] { return char_; } X = [xX] / "\\" "0"? "0"? "0"? "0"? "58" ("\r\n" / [ \t\r\n\f])? { return "X"; } / "\\" "0"? "0"? "0"? "0"? "78" ("\r\n" / [ \t\r\n\f])? { return "x"; } / "\\" char_:[xX] { return char_; } Z = [zZ] / "\\" "0"? "0"? "0"? "0"? "5" [aA] ("\r\n" / [ \t\r\n\f])? { return "Z"; } / "\\" "0"? "0"? "0"? "0"? "7" [aA] ("\r\n" / [ \t\r\n\f])? { return "z"; } / "\\" char_:[zZ] { return char_; } /* Tokens */ S "whitespace" = comment* s CDO "" = comment* "-->" INCLUDES "~=" = comment* "~=" DASHMATCH "|=" = comment* "|=" STRING "string" = comment* string:string { return string; } IDENT "identifier" = comment* ident:ident { return ident; } HASH "hash" = comment* "#" name:name { return "#" + name; } IMPORT_SYM "@import" = comment* "@" I M P O R T PAGE_SYM "@page" = comment* "@" P A G E MEDIA_SYM "@media" = comment* "@" M E D I A CHARSET_SYM "@charset" = comment* "@charset " /* Note: We replace "w" with "s" here to avoid infinite recursion. */ IMPORTANT_SYM "!important" = comment* "!" (s / comment)* I M P O R T A N T { return "!important"; } EMS "length" = comment* num:num e:E m:M { return num + e + m; } EXS "length" = comment* num:num e:E x:X { return num + e + x; } LENGTH "length" = comment* num:num unit:(P X / C M / M M / I N / P T / P C) { return num + unit.join(""); } ANGLE "angle" = comment* num:num unit:(D E G / R A D / G R A D) { return num + unit.join(""); } TIME "time" = comment* num:num unit:(m:M s:S_ { return m + s; } / S_) { return num + unit; } FREQ "frequency" = comment* num:num unit:(H Z / K H Z) { return num + unit.join(""); } DIMENSION "dimension" = comment* num:num unit:ident { return num + unit; } PERCENTAGE "percentage" = comment* parts:$(num "%") { return parts; } NUMBER "number" = comment* num:num { return num; } URI "uri" = comment* U R L "(" w value:(string / url) w ")" { return value; } FUNCTION "function" = comment* name:ident "(" { return name; }