class CSS expect 13 rule stylesheet : charset style_space import_rule css_rest charset : /* empty */ | CHARSET_SYM space STRING space ';' { @charset = eval(val[2]) } import_rule : /* empty */ | import style_space import_rule { @imports << val[0] } css_rest : | css_rest css_body style_space css_body : ruleset { @ruleset << val[0] } | media { @media << val[0] } | page { @pages << val[0] } | font_face { @font_faces << val[0] } style_space : | style_space style_space_set style_space_set : S | CDO | CDC import : IMPORT_SYM space file ';' space { uri = (val[2] =~ URI) ? val[2] : eval(val[2]) result = Import.new(uri) } | IMPORT_SYM space file space mediums ';' space { uri = (val[2] =~ URI) ? val[2] : eval(val[2]) result = Import.new(uri, val[4]) } media : MEDIA_SYM space mediums '{' space rulesets '}' space { result = Media.new(val[2], val[5]) } # | MEDIA_SYM space mediums '{' space '}' space mediums : medium { result = val } | medium ',' space mediums { result = val[3].concat([val[0]]) } medium : IDENT space page : PAGE_SYM space IDENT pseudo_page space '{' space declarations '}' space { result = Page.new(val[2..3].join(""), val[7]) } | PAGE_SYM space pseudo_page space '{' space declarations '}' space { result = Page.new(val[2], val[6]) } pseudo_page : /* empty */ | ':' IDENT { result = val.join("") } font_face : FONT_FACE_SYM space '{' space declarations '}' space { result = FontFace.new(val[4]) } operator : /* empty */ | '/' space | ',' space combinator : /* empty */ | '+' space | '>' space unary_operator : '-' | '+' property : IDENT space rulesets : { result = [] } | rulesets ruleset { result = val[0].concat([val[1]]) } ruleset : selectors '{' space declarations '}' space { result = Rule.new(val[0], val[3]) } selectors : selector { result = val } | selectors ',' space selector { result = val[0].concat([val[3]]) } selector : simple_selector | selector combinator simple_selector { result = val.join("") } simple_selector : element_name space { result = val.join("") } | element_name ne_selector space { result = val.join("") } | ne_selector space { result = val.join("") } ne_selector : HASH | class | attrib | pseudo class : '.' IDENT { result = val.join("") } element_name : IDENT | '*' attrib : '[' space IDENT space attrib_a ']' { result = val.join("") } attrib_a : /* empty */ | attrib_sym space attrib_v space { result = val.join("") } attrib_sym : '=' | INCLUDES | DASHMATCH attrib_v : IDENT | STRING pseudo : ':' IDENT { result = val.join("") } | ':' FUNCTION space IDENT space ')' { result = val.join("") } declarations : declaration { result = val[0] ? val : [] } | declaration ';' space declarations { result = val[3].concat(val[0] ? [val[0]] : []) } declaration : /* empty */ | property ':' space expr prio { result = Declaration.new(val[0], [val[3]].flatten, val[4] ? true : false) } prio : /* empty */ | IMPORTANT_SYM space expr : term | term operator expr { result = val.values_at(0, 2) } term : unary_operator numeric { result = val.join("") } | numeric | STRING space | IDENT space | URI space | RGB space | UNICODERANGE space | hexcolor | error space { @error << val[0] yyerrok } numeric : NUMBER space | PERCENTAGE space | LENGTH space | EMS space | EXS space | ANGLE space | TIME space | FREQ space | function function : FUNCTION space expr ')' space { result = val.join("") } hexcolor : HASH space { # Invalid hex color unless val[0] =~ /^#([0-9a-f]{3}|[0-9a-f]{6})$/i @error << val[0] end } file : STRING | URI space : /* empty */ | space S { result = val.join("") } end ---- inner nl = "(?:\\n|\\r\\n|\\r|\\f)" h = "(?:[0-9a-f])" nonascii = "(?:[\\200-\\377])" unicode = "(?:\\\\#{h}{1,6}[ \\t\\r\\n\\f]?)" escape = "(?:#{unicode}|\\\\[ -~\\200-\\377])" nmstart = "(?:[a-z]|#{nonascii}|#{escape})" nmchar = "(?:[a-z0-9-]|#{nonascii}|#{escape})" string1 = "(?:\"([\\t !\#$%&(-~]|\\\\#{nl}|\\'|#{nonascii}|#{escape})*\")" string2 = "(?:'([\\t !\#$%&(-~]|\\\\#{nl}|\\\"|#{nonascii}|#{escape})*')" ident = "(?:#{nmstart}#{nmchar}*)" name = "(?:#{nmchar}+)" num = "(?:[0-9]+|[0-9]*\\.[0-9]+)" string = "(?:#{string1}|#{string2})" url = "(?:([!\#$%&*-~]|#{nonascii}|#{escape})*)" w = "(?:[ \\t\\r\\n\\f]*)" range = "(?:\\?{1,6}|#{h}(\\?{0,5}|#{h}(\\?{0,4}|#{h}(\\?{0,3}|#{h}(\\?{0,2}|#{h}(\\??|#{h}))))))" S = /[ \t\r\n\f]+/io COMMENT = /\/\*[^*]*\*+([^\/][^*]*\*+)*\//ion CDO = //ion INCLUDES = /~=/ion DASHMATCH = /\|=/ion STRING = /#{string}/ion IDENT = /#{ident}/ion HASH = /##{name}/ion IMPORT_SYM = /@import/ion PAGE_SYM = /@page/ion MEDIA_SYM = /@media/ion FONT_FACE_SYM = /@font-face/ion CHARSET_SYM = /@charset/ion ATKEYWORD = /@#{ident}/ion IMPORTANT_SYM = /!#{w}important/ion EMS = /#{num}em/ion EXS = /#{num}ex/ion LENGTH = /#{num}(px|cm|mm|in|pt|pc)/ion ANGLE = /#{num}(deg|rad|grad)/ion TIME = /#{num}m?s/ion FREQ = /#{num}k?Hz/ion DIMEN = /#{num}#{ident}/ion PERCENTAGE = /#{num}%/ion NUMBER = /#{num}/ion URI = /url\(#{w}#{string}#{w}\)|url\(#{w}#{url}#{w}\)/ion FUNCTION = /#{ident}\(/ion UNICODERANGE = /U\+#{range}|U\+#{h}{1,6}-#{h}{1,6}/ion SCAN = [ :S, :COMMENT, :CDO, :CDC, :INCLUDES, :DASHMATCH, :UNICODERANGE, :URI, :FUNCTION, :STRING, :IDENT, :HASH, :IMPORT_SYM, :PAGE_SYM, :MEDIA_SYM, :FONT_FACE_SYM, :CHARSET_SYM, :ATKEYWORD, :IMPORTANT_SYM, :EMS, :EXS, :LENGTH, :ANGLE, :TIME, :FREQ, :DIMEN, :PERCENTAGE, :NUMBER, ] def parse require "strscan" #@yydebug = $DEBUG @q = [] s = StringScanner.new(@source) until s.empty? token = nil SCAN.each do |reg| token = s.scan(CSS.const_get(reg)) if token token = [reg, token] break end end if s.matched? @q << token unless token[0] == :COMMENT else chr = s.getch @q << [chr, chr] end end @q << [false, "end"] do_parse end def next_token @q.shift end