class Nokogiri::CSS::Parser token FUNCTION INCLUDES DASHMATCH LBRACE HASH PLUS GREATER S STRING IDENT token COMMA NUMBER PREFIXMATCH SUFFIXMATCH SUBSTRINGMATCH TILDE NOT_EQUAL token SLASH DOUBLESLASH NOT EQUAL RPAREN LSQUARE RSQUARE HAS rule selector: selector COMMA simple_selector_1toN { result = [val[0], val[2]].flatten } | prefixless_combinator_selector { result = val.flatten } | optional_S simple_selector_1toN { result = [val[1]].flatten } ; combinator: PLUS { result = :DIRECT_ADJACENT_SELECTOR } | GREATER { result = :CHILD_SELECTOR } | TILDE { result = :FOLLOWING_SELECTOR } | DOUBLESLASH { result = :DESCENDANT_SELECTOR } | SLASH { result = :CHILD_SELECTOR } ; xpath_attribute_name: '@' IDENT { result = val[1] } ; xpath_attribute: xpath_attribute_name { result = Node.new(:ATTRIB_NAME, [val[0]]) } ; simple_selector: element_name hcap_0toN { result = if val[1].nil? val[0] else Node.new(:CONDITIONAL_SELECTOR, [val[0], val[1]]) end } | function | function pseudo { result = Node.new(:CONDITIONAL_SELECTOR, val) } | function attrib { result = Node.new(:CONDITIONAL_SELECTOR, val) } | hcap_1toN { result = Node.new(:CONDITIONAL_SELECTOR, [Node.new(:ELEMENT_NAME, ['*']), val[0]]) } | xpath_attribute ; prefixless_combinator_selector: combinator simple_selector_1toN { result = Node.new(val[0], [nil, val[1]]) } ; simple_selector_1toN: simple_selector combinator simple_selector_1toN { result = Node.new(val[1], [val[0], val[2]]) } | simple_selector S simple_selector_1toN { result = Node.new(:DESCENDANT_SELECTOR, [val[0], val[2]]) } | simple_selector ; class: '.' IDENT { result = Node.new(:CLASS_CONDITION, [unescape_css_identifier(val[1])]) } ; element_name: namespaced_ident | '*' { result = Node.new(:ELEMENT_NAME, val) } ; namespaced_ident: namespace '|' IDENT { result = Node.new(:ELEMENT_NAME, [[val[0], val[2]].compact.join(':')]) } | IDENT { name = @namespaces.key?('xmlns') ? "xmlns:#{val[0]}" : val[0] result = Node.new(:ELEMENT_NAME, [name]) } ; namespace: IDENT { result = val[0] } | ; attrib: LSQUARE attrib_name attrib_val_0or1 RSQUARE { result = Node.new(:ATTRIBUTE_CONDITION, [val[1]] + (val[2] || [])) } | LSQUARE function attrib_val_0or1 RSQUARE { result = Node.new(:ATTRIBUTE_CONDITION, [val[1]] + (val[2] || [])) } | LSQUARE NUMBER RSQUARE { result = Node.new(:PSEUDO_CLASS, [Node.new(:FUNCTION, ['nth-child(', val[1]])]) } ; attrib_name: namespace '|' IDENT { result = Node.new(:ATTRIB_NAME, [[val[0], val[2]].compact.join(':')]) } | IDENT { result = Node.new(:ATTRIB_NAME, [val[0]]) } | xpath_attribute ; function: FUNCTION RPAREN { result = Node.new(:FUNCTION, [val[0].strip]) } | FUNCTION expr RPAREN { result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten) } | FUNCTION nth RPAREN { result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten) } | NOT expr RPAREN { result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten) } | HAS selector RPAREN { result = Node.new(:FUNCTION, [val[0].strip, val[1]].flatten) } ; expr: NUMBER COMMA expr { result = [val[0], val[2]] } | STRING COMMA expr { result = [val[0], val[2]] } | IDENT COMMA expr { result = [val[0], val[2]] } | xpath_attribute COMMA expr { result = [val[0], val[2]] } | NUMBER | STRING | IDENT { case val[0] when 'even' result = Node.new(:NTH, ['2','n','+','0']) when 'odd' result = Node.new(:NTH, ['2','n','+','1']) when 'n' result = Node.new(:NTH, ['1','n','+','0']) else result = val end } | xpath_attribute ; nth: NUMBER IDENT PLUS NUMBER # 5n+3 -5n+3 { if val[1] == 'n' result = Node.new(:NTH, val) else raise Racc::ParseError, "parse error on IDENT '#{val[1]}'" end } | IDENT PLUS NUMBER { # n+3, -n+3 if val[0] == 'n' val.unshift("1") result = Node.new(:NTH, val) elsif val[0] == '-n' val[0] = 'n' val.unshift("-1") result = Node.new(:NTH, val) else raise Racc::ParseError, "parse error on IDENT '#{val[1]}'" end } | NUMBER IDENT { # 5n, -5n, 10n-1 n = val[1] if n[0, 2] == 'n-' val[1] = 'n' val << "-" # b is contained in n as n is the string "n-b" val << n[2, n.size] result = Node.new(:NTH, val) elsif n == 'n' val << "+" val << "0" result = Node.new(:NTH, val) else raise Racc::ParseError, "parse error on IDENT '#{val[1]}'" end } ; pseudo: ':' function { result = Node.new(:PSEUDO_CLASS, [val[1]]) } | ':' IDENT { result = Node.new(:PSEUDO_CLASS, [val[1]]) } ; hcap_0toN: hcap_1toN | ; hcap_1toN: attribute_id hcap_1toN { result = Node.new(:COMBINATOR, val) } | class hcap_1toN { result = Node.new(:COMBINATOR, val) } | attrib hcap_1toN { result = Node.new(:COMBINATOR, val) } | pseudo hcap_1toN { result = Node.new(:COMBINATOR, val) } | negation hcap_1toN { result = Node.new(:COMBINATOR, val) } | attribute_id | class | attrib | pseudo | negation ; attribute_id: HASH { result = Node.new(:ID, [unescape_css_identifier(val[0])]) } ; attrib_val_0or1: eql_incl_dash IDENT { result = [val[0], unescape_css_identifier(val[1])] } | eql_incl_dash STRING { result = [val[0], unescape_css_string(val[1])] } | eql_incl_dash NUMBER { result = [val[0], val[1]] } | ; eql_incl_dash: EQUAL { result = :equal } | PREFIXMATCH { result = :prefix_match } | SUFFIXMATCH { result = :suffix_match } | SUBSTRINGMATCH { result = :substring_match } | NOT_EQUAL { result = :not_equal } | INCLUDES { result = :includes } | DASHMATCH { result = :dash_match } ; negation: NOT negation_arg RPAREN { result = Node.new(:NOT, [val[1]]) } ; negation_arg: element_name | element_name hcap_1toN | hcap_1toN ; optional_S: S | ; end ---- header require_relative "parser_extras" module Nokogiri module CSS # :nodoc: all class Parser < Racc::Parser end end end ---- inner def unescape_css_identifier(identifier) identifier.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/){ |m| $1 || [$2.hex].pack('U') } end def unescape_css_string(str) str.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/) do |m| if $1=="\n" '' else $1 || [$2.hex].pack('U') end end end