This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
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]]) }
|
||||
| IDENT {
|
||||
name = 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
|
||||
Reference in New Issue
Block a user